I see the command pattern coming into play here. Using this pattern, you could separate the actions that each browser would need to implement and have the flexibility to implement new APIs down the road. This also allows you to implement variations in your actions, e.g. you can have a command for MoveMouse and MoveMouseLikeHuman.
The Point
class is referenced via using System.Drawing;
First, we define our interfaces and base classes:
// The receiver interface defines the contract for receivers implemented
// by each external browser. Each one will need to implement this
// interface. Additional actions are added here and in turn implemented
// by external browser receiver implementations
public interface IBrowserReceiver
{
void MoveMouse(Point position);
void ClickButton(Point position);
void Delay(int delaySeconds);
// additional external browser actions defined here
// ...
}
// the command base is implemented by each command action
public abstract class BrowserCommandBase
{
protected readonly IBrowserReceiver Receiver;
protected BrowserCommandBase(IBrowserReceiver receiver)
{
this.Receiver = receiver ?? throw new ArgumentNullException(nameof(receiver));
}
public abstract void Execute();
}
// this class is implemented by each external browser implementation and is used
// to queue multiple commands and invoke them sequentially
public abstract class ExternalBrowserBase
{
private readonly Queue<BrowserCommandBase> commandQueue;
public ExternalBrowserType Type { get; }
protected ExternalBrowserBase(ExternalBrowserType type)
{
this.Type = type;
commandQueue = new Queue<BrowserCommandBase>();
}
public void QueueCommand(BrowserCommandBase command)
{
commandQueue.Enqueue(command);
}
public void ExecuteCommands()
{
foreach (BrowserCommandBase command in commandQueue)
{
command.Execute();
}
}
public void ClearCommands()
{
commandQueue.Clear();
}
}
… and your enum:
public enum ExternalBrowserType
{
ChromeSelenium,
FirefoxSelenium,
ChromiumPuppeteer
}
Next, we define our receiver implementations. These classes implement the receiver interface for the particular browser, allowing each browser to implement the actions as required for their specific API; i.e. MoveMouse will be implemented differently for the Selenium and the Puppeteer APIs
public class ChromeSeleniumReceiver : IBrowserReceiver
{
public void MoveMouse(Point position)
{
Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
}
public void ClickButton(Point position)
{
Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
}
public void Delay(int delaySeconds)
{
Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
}
}
public class ChromiumPuppeteerReceiver : IBrowserReceiver
{
public void MoveMouse(Point position)
{
Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
}
public void ClickButton(Point position)
{
Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
}
public void Delay(int delaySeconds)
{
Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
}
}
public class FirefoxSeleniumReceiver : IBrowserReceiver
{
public void MoveMouse(Point position)
{
Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
}
public void ClickButton(Point position)
{
Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
}
public void Delay(int delaySeconds)
{
Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
}
}
Command implementations:
The commands call into the Receiver
implemented by each external browser.
public class MoveMouseCommand : BrowserCommandBase
{
private readonly Point position;
public MoveMouseCommand(IBrowserReceiver receiver, Point position)
: base(receiver)
{
this.position = position;
}
public override void Execute()
{
this.Receiver.MoveMouse(position);
}
}
// moves the mouse gradually from the start point to the end point by
// repeatedly calling the move mouse command until the end point is reached
public class MoveMouseLikeHumanCommand : BrowserCommandBase
{
private readonly Point fromPosition;
private readonly Point toPosition;
public MoveMouseLikeHumanCommand(IBrowserReceiver receiver,
Point fromPosition, Point toPosition)
: base(receiver)
{
this.fromPosition = fromPosition;
this.toPosition = toPosition;
}
public override void Execute()
{
Point startPosition = fromPosition;
Point lastPosition = toPosition;
Point currentPosition = startPosition;
while (currentPosition != lastPosition)
{
this.Receiver.MoveMouse(currentPosition); // call move mouse command
currentPosition = GetNextMovePoint(startPosition, lastPosition);
startPosition = currentPosition;
}
this.Receiver.MoveMouse(lastPosition);
}
private Point GetNextMovePoint(Point start, Point end)
{
int newX = GetNextMoveValue(start.X, end.X);
int newY = GetNextMoveValue(start.Y, end.Y);
return new Point(newX, newY);
}
private static int GetNextMoveValue(int start, int end)
{
if (start < end)
{
return start + 1;
}
if (start > end)
{
return start - 1;
}
return start;
}
}
public class ClickButtonCommand : BrowserCommandBase
{
private readonly Point position;
public ClickButtonCommand(IBrowserReceiver receiver, Point position)
: base(receiver)
{
this.position = position;
}
public override void Execute()
{
this.Receiver.ClickButton(this.position);
}
}
public class DelayCommand : BrowserCommandBase
{
private readonly int delaySeconds;
public DelayCommand(IBrowserReceiver receiver, int delaySeconds) : base(receiver)
{
this.delaySeconds = delaySeconds;
}
public override void Execute()
{
base.Receiver.Delay(this.delaySeconds);
}
}
Last, we implement our external browsers that inherit from the ExternalBrowserBase
class.
public class ChromeSeleniumExternalBrowser : ExternalBrowserBase
{
public ChromeSeleniumExternalBrowser() : base(ExternalBrowserType.ChromeSelenium)
{
}
}
public class ChromiumPuppeteerExternalBrowser : ExternalBrowserBase
{
public ChromiumPuppeteerExternalBrowser() : base(ExternalBrowserType.ChromiumPuppeteer)
{
}
}
public class FirefoxSeleniumExternalBrowser : ExternalBrowserBase
{
public FirefoxSeleniumExternalBrowser() : base(ExternalBrowserType.FirefoxSelenium)
{
}
}
And we use it like so (sorry, my variable names are terrible):
ChromeSeleniumExternalBrowser:
ChromeSeleniumExternalBrowser chromeSelenium = new ChromeSeleniumExternalBrowser();
ChromeSeleniumReceiver chromeSeleniumReceiver = new ChromeSeleniumReceiver();
MoveMouseCommand chromeSeleniumMoveMouseCommand =
new MoveMouseCommand(chromeSeleniumReceiver, new Point(100, 110));
chromeSelenium.QueueCommand(chromeSeleniumMoveMouseCommand);
DelayCommand chromeSeleniumDelayCommand = new DelayCommand(chromeSeleniumReceiver, 5);
chromeSelenium.QueueCommand(chromeSeleniumDelayCommand);
ClickButtonCommand chromeSeleniumClickButtonCommand =
new ClickButtonCommand(chromeSeleniumReceiver, new Point(100, 100));
chromeSelenium.QueueCommand(chromeSeleniumClickButtonCommand);
MoveMouseLikeHumanCommand chromeSeleniumLerpMoveMouseCommand =
new MoveMouseLikeHumanCommand(chromeSeleniumReceiver,
new Point(100, 110), new Point(115, 120));
chromeSelenium.QueueCommand(chromeSeleniumLerpMoveMouseCommand);
chromeSelenium.ExecuteCommands();
// ChromeSeleniumExternalBrowser output:
// ChromeSeleniumReceiver : MoveMouse; {X=100,Y=110}
// ChromeSeleniumReceiver : Delay; delaying for 5 second(s)
// ChromeSeleniumReceiver : ClickButton; {X=100,Y=100}
// ChromeSeleniumReceiver : MoveMouse; {X=100,Y=110}
// ChromeSeleniumReceiver : MoveMouse; {X=101,Y=111}
// ChromeSeleniumReceiver : MoveMouse; {X=102,Y=112}
// ChromeSeleniumReceiver : MoveMouse; {X=103,Y=113}
// ChromeSeleniumReceiver : MoveMouse; {X=104,Y=114}
// ChromeSeleniumReceiver : MoveMouse; {X=105,Y=115}
// ChromeSeleniumReceiver : MoveMouse; {X=106,Y=116}
// ChromeSeleniumReceiver : MoveMouse; {X=107,Y=117}
// ChromeSeleniumReceiver : MoveMouse; {X=108,Y=118}
// ChromeSeleniumReceiver : MoveMouse; {X=109,Y=119}
// ChromeSeleniumReceiver : MoveMouse; {X=110,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=111,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=112,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=113,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=114,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=115,Y=120}
FirefoxSeleniumExternalBrowser:
FirefoxSeleniumExternalBrowser firefoxSeleniumPilot = new FirefoxSeleniumExternalBrowser();
FirefoxSeleniumReceiver firefoxSeleniumReceiver = new FirefoxSeleniumReceiver();
MoveMouseCommand firefoxSeleniumMoveMouseCommand =
new MoveMouseCommand(firefoxSeleniumReceiver, new Point(50, 55));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumMoveMouseCommand);
ClickButtonCommand firefoxSeleniumClickButtonCommand =
new ClickButtonCommand(firefoxSeleniumReceiver, new Point(50, 55));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumClickButtonCommand);
MoveMouseLikeHumanCommand firefoxSeleniumLerpMoveMouseCommand =
new MoveMouseLikeHumanCommand(firefoxSeleniumReceiver,
new Point(50, 65), new Point(59, 69));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumLerpMoveMouseCommand);
firefoxSeleniumPilot.ExecuteCommands();
// output:
// FirefoxSeleniumReceiver : MoveMouse; {X=50,Y=55}
// FirefoxSeleniumReceiver : ClickButton; {X=50,Y=55}
// FirefoxSeleniumReceiver : MoveMouse; {X=50,Y=65}
// FirefoxSeleniumReceiver : MoveMouse; {X=51,Y=66}
// FirefoxSeleniumReceiver : MoveMouse; {X=52,Y=67}
// FirefoxSeleniumReceiver : MoveMouse; {X=53,Y=68}
// FirefoxSeleniumReceiver : MoveMouse; {X=54,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=55,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=56,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=57,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=58,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=59,Y=69}
ChromiumPuppeteerExternalBrowser:
ChromiumPuppeteerExternalBrowser chromiumPuppeteerPilot =
new ChromiumPuppeteerExternalBrowser();
ChromiumPuppeteerReceiver chromiumPuppeteerReceiver = new ChromiumPuppeteerReceiver();
MoveMouseCommand chromiumPuppeteerMoveMouseCommand =
new MoveMouseCommand(chromiumPuppeteerReceiver, new Point(30, 40));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerMoveMouseCommand);
ClickButtonCommand chromiumPuppeteerClickButtonCommand =
new ClickButtonCommand(chromiumPuppeteerReceiver, new Point(30, 40));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerClickButtonCommand);
MoveMouseLikeHumanCommand chromiumPuppeteerLerpMoveMouseCommand =
new MoveMouseLikeHumanCommand(chromiumPuppeteerReceiver,
new Point(30, 40), new Point(39, 49));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerLerpMoveMouseCommand);
chromiumPuppeteerPilot.ExecuteCommands();
// output
// ChromiumPuppeteerReceiver : MoveMouse; {X=30,Y=40}
// ChromiumPuppeteerReceiver : ClickButton; {X=30,Y=40}
// ChromiumPuppeteerReceiver : MoveMouse; {X=30,Y=40}
// ChromiumPuppeteerReceiver : MoveMouse; {X=31,Y=41}
// ChromiumPuppeteerReceiver : MoveMouse; {X=32,Y=42}
// ChromiumPuppeteerReceiver : MoveMouse; {X=33,Y=43}
// ChromiumPuppeteerReceiver : MoveMouse; {X=34,Y=44}
// ChromiumPuppeteerReceiver : MoveMouse; {X=35,Y=45}
// ChromiumPuppeteerReceiver : MoveMouse; {X=36,Y=46}
// ChromiumPuppeteerReceiver : MoveMouse; {X=37,Y=47}
// ChromiumPuppeteerReceiver : MoveMouse; {X=38,Y=48}
// ChromiumPuppeteerReceiver : MoveMouse; {X=39,Y=49}