SeleniumWebDriver

This commit is contained in:
Haiping Chen 2024-04-19 13:40:30 -05:00
parent 70b3e96c0a
commit 27e6bd4d8a
10 changed files with 431 additions and 2 deletions

View file

@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.41.2" />
<PackageReference Include="Selenium.WebDriver" Version="4.19.0" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,88 @@
using OpenQA.Selenium.Chrome;
using System.IO;
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
public class SeleniumInstance : IDisposable
{
Dictionary<string, IWebDriver> _contexts = new Dictionary<string, IWebDriver>();
public Dictionary<string, IWebDriver> 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<IWebDriver> InitInstance(string id)
{
return await InitContext(id);
}
public async Task<IWebDriver> 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<INavigation> 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();
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,18 @@
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
public partial class SeleniumWebDriver
{
public async Task<string> 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;
}
}

View file

@ -0,0 +1,27 @@
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
public partial class SeleniumWebDriver
{
public async Task<BrowserActionResult> 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;
}
}

View file

@ -0,0 +1,33 @@
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
public partial class SeleniumWebDriver
{
public async Task<BrowserActionResult> 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;
}
}

View file

@ -0,0 +1,119 @@
using OpenQA.Selenium;
using System.Collections.ObjectModel;
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
public partial class SeleniumWebDriver
{
/// <summary>
/// Using attributes or text to locate element and return the selector
/// </summary>
/// <param name="message"></param>
/// <param name="location"></param>
/// <returns></returns>
public async Task<BrowserActionResult> LocateElement(MessageInfo message, ElementLocatingArgs location)
{
var result = new BrowserActionResult();
var driver = await _instance.InitInstance(message.ContextId);
IWebElement locator = driver.FindElement(By.TagName("body"));
ReadOnlyCollection<IWebElement> 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;
}
}

View file

@ -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<SeleniumWebDriver> logger)
{
_services = services;
_instance = instance;
_logger = logger;
}
public Task<BrowserActionResult> ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ChangeCheckbox(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ChangeListValue(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> CheckRadioButton(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ClickButton(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ClickElement(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task CloseBrowser(string contextId)
{
throw new NotImplementedException();
}
public Task CloseCurrentPage(string contextId)
{
throw new NotImplementedException();
}
public Task<T> EvaluateScript<T>(string contextId, string script)
{
throw new NotImplementedException();
}
public Task<string> ExtractData(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> InputUserPassword(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> InputUserText(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ScreenshotAsync(string contextId, string path)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> ScrollPageAsync(BrowserActionParams actionParams)
{
throw new NotImplementedException();
}
public Task<BrowserActionResult> SendHttpRequest(string contextId, HttpRequestParams actionParams)
{
throw new NotImplementedException();
}
}

View file

@ -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;

View file

@ -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<IWebBrowser, PlaywrightWebDriver>();
services.AddSingleton<PlaywrightInstance>();
// services.AddScoped<IWebBrowser, PlaywrightWebDriver>();
// services.AddSingleton<PlaywrightInstance>();
services.AddScoped<IWebBrowser, SeleniumWebDriver>();
services.AddSingleton<SeleniumInstance>();
services.AddScoped<WebDriverService>();
services.AddScoped<IConversationHook, WebDriverConversationHook>();
}