diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
index 7c4d6a76..234e898b 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs
new file mode 100644
index 00000000..43586be4
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs
@@ -0,0 +1,88 @@
+using OpenQA.Selenium.Chrome;
+using System.IO;
+
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public class SeleniumInstance : IDisposable
+{
+ Dictionary _contexts = new Dictionary();
+
+ public Dictionary Contexts => _contexts;
+
+ public INavigation GetPage(string id)
+ {
+ InitInstance(id).Wait();
+ return _contexts[id].Navigate();
+ }
+
+ public string GetPageContent(string id)
+ {
+ InitInstance(id).Wait();
+ return _contexts[id].PageSource;
+ }
+
+ public async Task InitInstance(string id)
+ {
+ return await InitContext(id);
+ }
+
+ public async Task InitContext(string id)
+ {
+ if (_contexts.ContainsKey(id))
+ return _contexts[id];
+
+ string tempFolderPath = $"{Path.GetTempPath()}\\_selenium\\{id}";
+
+ var options = new ChromeOptions();
+ options.AddArgument("disable-infobars");
+ options.AddArgument($"--user-data-dir={tempFolderPath}");
+ var selenium = new ChromeDriver(options);
+ selenium.Manage().Window.Maximize();
+ selenium.Navigate().GoToUrl("about:blank");
+ _contexts[id] = selenium;
+
+ return _contexts[id];
+ }
+
+ public async Task NewPage(string id)
+ {
+ await InitContext(id);
+ var selenium = _contexts[id];
+ selenium.Navigate().GoToUrl("about:blank");
+ return _contexts[id].Navigate();
+ }
+
+ public async Task Wait(string id)
+ {
+ if (_contexts.ContainsKey(id))
+ {
+ _contexts[id].Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
+ }
+ await Task.Delay(100);
+ }
+
+ public async Task Close(string id)
+ {
+ if (_contexts.ContainsKey(id))
+ {
+ _contexts[id].Quit();
+ _contexts.Remove(id);
+ }
+ }
+
+ public async Task CloseCurrentPage(string id)
+ {
+ if (_contexts.ContainsKey(id))
+ {
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach(var context in _contexts)
+ {
+ context.Value.Quit();
+ }
+ _contexts.Clear();
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs
new file mode 100644
index 00000000..bb9afd87
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs
@@ -0,0 +1,43 @@
+using OpenQA.Selenium;
+using OpenQA.Selenium.Interactions;
+
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver
+{
+ public async Task DoAction(MessageInfo message, ElementActionArgs action, BrowserActionResult result)
+ {
+ var driver = await _instance.InitInstance(message.ContextId);
+ IWebElement element = default;
+ if (result.Selector.StartsWith("//"))
+ {
+ element = driver.FindElement(By.XPath(result.Selector));
+ }
+ else
+ {
+ element = driver.FindElement(By.CssSelector(result.Selector));
+ }
+
+
+ if (action.Action == BroswerActionEnum.Click)
+ {
+ if (action.Position == null)
+ {
+ element.Click();
+ }
+ else
+ {
+ var size = element.Size;
+ var actions = new Actions(driver);
+ actions.MoveToElement(element)
+ .MoveByOffset((int)action.Position.X - size.Width / 2, (int)action.Position.Y - size.Height / 2)
+ .Click()
+ .Perform();
+ }
+ }
+ else if (action.Action == BroswerActionEnum.InputText)
+ {
+ element.SendKeys(action.Content);
+ }
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs
new file mode 100644
index 00000000..ee5d47e7
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs
@@ -0,0 +1,18 @@
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver
+{
+ public async Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location, BrowserActionResult result)
+ {
+ var driver = await _instance.InitInstance(message.ContextId);
+ var locator = driver.FindElement(By.CssSelector(result.Selector));
+ var value = string.Empty;
+
+ if (!string.IsNullOrEmpty(location?.AttributeName))
+ {
+ value = locator.GetAttribute(location.AttributeName);
+ }
+
+ return value ?? string.Empty;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs
new file mode 100644
index 00000000..abe51210
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs
@@ -0,0 +1,27 @@
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver
+{
+ public async Task GoToPage(string contextId, string url, bool openNewTab = false)
+ {
+ var result = new BrowserActionResult();
+ try
+ {
+ var page = openNewTab ? await _instance.NewPage(contextId) :
+ _instance.GetPage(contextId);
+ page.GoToUrl(url);
+ await _instance.Wait(contextId);
+
+ result.Body = _instance.GetPageContent(contextId);
+ result.IsSuccess = true;
+ }
+ catch (Exception ex)
+ {
+ result.Message = ex.Message;
+ result.StackTrace = ex.StackTrace;
+ _logger.LogError(ex.Message);
+ }
+
+ return result;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs
new file mode 100644
index 00000000..3203b617
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs
@@ -0,0 +1,33 @@
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver
+{
+ public async Task LaunchBrowser(string contextId, string? url, bool openIfNotExist = true)
+ {
+ var result = new BrowserActionResult()
+ {
+ IsSuccess = true
+ };
+ var context = await _instance.InitInstance(contextId);
+
+ if (!string.IsNullOrEmpty(url))
+ {
+ // Check if the page is already open
+ var page = await _instance.NewPage(contextId);
+
+ try
+ {
+ page.GoToUrl(url);
+ result.IsSuccess = true;
+ }
+ catch (Exception ex)
+ {
+ result.Message = ex.Message;
+ result.StackTrace = ex.StackTrace;
+ _logger.LogError(ex.Message);
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs
new file mode 100644
index 00000000..4d96b9a6
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs
@@ -0,0 +1,119 @@
+using OpenQA.Selenium;
+using System.Collections.ObjectModel;
+
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver
+{
+ ///
+ /// Using attributes or text to locate element and return the selector
+ ///
+ ///
+ ///
+ ///
+ public async Task LocateElement(MessageInfo message, ElementLocatingArgs location)
+ {
+ var result = new BrowserActionResult();
+ var driver = await _instance.InitInstance(message.ContextId);
+
+ IWebElement locator = driver.FindElement(By.TagName("body"));
+ ReadOnlyCollection elements = default;
+ string selector = string.Empty;
+ int count = 0;
+
+ // check if selector is specified
+ if (location.Selector != null)
+ {
+ selector = location.Selector;
+ elements = driver.FindElements(By.CssSelector(location.Selector));
+ count = elements.Count;
+ }
+
+ // try attribute
+ if (count == 0 && !string.IsNullOrEmpty(location.AttributeName))
+ {
+ selector = $"[{location.AttributeName}='{location.AttributeValue}']";
+ elements = driver.FindElements(By.CssSelector(selector));
+ count = elements.Count;
+ }
+
+ // Retrieve the page raw html and infer the element path
+ if (!string.IsNullOrEmpty(location.Text))
+ {
+ var regexExpression = location.MatchRule.ToLower() switch
+ {
+ "startwith" => $"^{location.Text}",
+ "endwith" => $"{location.Text}$",
+ "contains" => $"{location.Text}",
+ _ => $"^{location.Text}$"
+ };
+ var regex = new Regex(regexExpression, RegexOptions.IgnoreCase);
+
+ selector = $"//*[text() = '{location.Text}']";
+ elements = driver.FindElements(By.XPath(selector));
+ count = elements.Count;
+
+ // try placeholder
+ if (count == 0)
+ {
+ selector = $"[placeholder='{location.Text}']";
+ elements = driver.FindElements(By.CssSelector(selector));
+ count = elements.Count;
+ }
+ }
+
+ if (location.Index >= 0)
+ {
+ locator = elements[location.Index];
+ count = 1;
+ }
+
+ if (count == 0)
+ {
+ result.Message = $"Can't locate element by keyword {location.Text}";
+ _logger.LogError(result.Message);
+ }
+ else if (count == 1)
+ {
+ locator = elements[0];
+ result.Selector = selector;
+ var text = locator.Text;
+ result.Body = text;
+ result.IsSuccess = true;
+ }
+ else if (count > 1)
+ {
+ if (location.FailIfMultiple)
+ {
+ result.Message = $"Multiple elements are found by {locator}";
+ _logger.LogError(result.Message);
+
+ /*foreach (var element in await locator.AllAsync())
+ {
+ var content = await element.InnerHTMLAsync();
+ _logger.LogError(content);
+ }*/
+ }
+ else
+ {
+ result.Selector = locator.ToString();
+ result.IsSuccess = true;
+ }
+ }
+
+ // Hightlight the element
+ if (result.IsSuccess && location.Highlight)
+ {
+ /*var handle = await page.QuerySelectorAsync(result.Selector);
+
+ await page.EvaluateAsync($@"
+ (element) => {{
+ element.style.outline = '2px solid red';
+ }}", handle);
+
+ result.IsHighlighted = true;*/
+ }
+
+ return result;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs
new file mode 100644
index 00000000..26ce8ea7
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs
@@ -0,0 +1,94 @@
+namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
+
+public partial class SeleniumWebDriver : IWebBrowser
+{
+ private readonly IServiceProvider _services;
+ private readonly SeleniumInstance _instance;
+ private readonly ILogger _logger;
+ public SeleniumInstance Instance => _instance;
+
+ public Agent Agent => _agent;
+ private Agent _agent;
+
+ public SeleniumWebDriver(IServiceProvider services, SeleniumInstance instance, ILogger logger)
+ {
+ _services = services;
+ _instance = instance;
+ _logger = logger;
+ }
+
+ public Task ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ChangeCheckbox(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ChangeListValue(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task CheckRadioButton(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ClickButton(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ClickElement(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task CloseBrowser(string contextId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task CloseCurrentPage(string contextId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task EvaluateScript(string contextId, string script)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ExtractData(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task InputUserPassword(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task InputUserText(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ScreenshotAsync(string contextId, string path)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ScrollPageAsync(BrowserActionParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendHttpRequest(string contextId, HttpRequestParams actionParams)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs
index 161e150a..619f0e23 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs
@@ -9,6 +9,7 @@ global using Microsoft.Playwright;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
+global using OpenQA.Selenium;
global using BotSharp.Abstraction.Browsing.Enums;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Plugins;
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs b/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs
index 2c7a4133..e767104b 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs
@@ -1,4 +1,5 @@
using BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
+using BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
using BotSharp.Plugin.WebDriver.Hooks;
namespace BotSharp.Plugin.Playwrights;
@@ -13,8 +14,12 @@ public class WebDriverPlugin : IBotSharpPlugin
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
- services.AddScoped();
- services.AddSingleton();
+ // services.AddScoped();
+ // services.AddSingleton();
+
+ services.AddScoped();
+ services.AddSingleton();
+
services.AddScoped();
services.AddScoped();
}