BotSharp/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs

242 lines
8 KiB
C#
Raw Normal View History

using System.IO;
2024-01-03 19:40:41 +00:00
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
2024-01-03 04:43:37 +00:00
public class PlaywrightInstance : IDisposable
{
IPlaywright _playwright;
2024-08-05 00:15:27 +00:00
IServiceProvider _services;
public IServiceProvider Services => _services;
Dictionary<string, IBrowserContext> _contexts = new Dictionary<string, IBrowserContext>();
2024-06-15 04:38:38 +00:00
Dictionary<string, List<IPage>> _pages = new Dictionary<string, List<IPage>>();
/// <summary>
/// ContextId and BrowserContext
/// </summary>
2024-04-09 03:38:56 +00:00
public Dictionary<string, IBrowserContext> Contexts => _contexts;
2024-01-03 04:43:37 +00:00
2024-06-15 04:38:38 +00:00
/// <summary>
/// ContextId and Pages
/// </summary>
public Dictionary<string, List<IPage>> Pages => _pages;
2024-08-05 00:15:27 +00:00
public void SetServiceProvider(IServiceProvider services)
{
_services = services;
}
2024-11-07 05:21:51 +00:00
public IPage? GetPage(string contextId)
2024-02-06 05:05:52 +00:00
{
2024-08-04 22:48:52 +00:00
return _contexts[contextId].Pages.LastOrDefault();
2024-02-06 05:05:52 +00:00
}
2024-01-03 04:43:37 +00:00
2024-06-30 13:05:57 +00:00
public async Task<IBrowserContext> GetContext(string ctxId)
2024-01-06 03:24:13 +00:00
{
2024-06-30 13:05:57 +00:00
return _contexts[ctxId];
}
2024-06-30 13:05:57 +00:00
public async Task<IBrowserContext> InitContext(string ctxId, BrowserActionArgs args)
{
2024-06-15 04:38:38 +00:00
if (_contexts.ContainsKey(ctxId))
return _contexts[ctxId];
2024-04-09 03:38:56 +00:00
2024-06-30 13:05:57 +00:00
if (_playwright == null)
{
_playwright = await Playwright.CreateAsync();
}
2024-12-23 19:53:04 +00:00
if (!string.IsNullOrEmpty(args.RemoteHostUrl))
{
var browser = await _playwright.Chromium.ConnectOverCDPAsync(args.RemoteHostUrl);
_contexts[ctxId] = browser.Contexts[0];
}
else
{
2024-12-23 19:53:04 +00:00
string userDataDir = args.UserDataDir ?? $"{Path.GetTempPath()}\\playwright\\{ctxId}";
_contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(userDataDir, new BrowserTypeLaunchPersistentContextOptions
2024-06-26 12:48:15 +00:00
{
2024-12-23 19:53:04 +00:00
Headless = args.Headless,
Channel = "chrome",
ViewportSize = new ViewportSize
{
Width = 1600,
Height = 900
},
IgnoreDefaultArgs =
[
"--enable-automation",
],
Args =
[
"--disable-infobars",
"--test-type"
// "--start-maximized"
]
});
}
2024-06-15 04:38:38 +00:00
_pages[ctxId] = new List<IPage>();
2024-02-02 04:16:57 +00:00
2024-06-15 04:38:38 +00:00
_contexts[ctxId].Page += async (sender, page) =>
{
2024-06-15 04:38:38 +00:00
_pages[ctxId].Add(page);
page.Close += async (sender, e) =>
{
2024-06-15 04:38:38 +00:00
_pages[ctxId].Remove(e);
Serilog.Log.Information($"Page is closed: {e.Url}");
};
2024-06-15 04:38:38 +00:00
Serilog.Log.Information($"New page is created: {page.Url}");
2024-06-21 18:23:18 +00:00
/*page.Response += async (sender, e) =>
{
Serilog.Log.Information($"Response: {e.Url}");
if (e.Headers.ContainsKey("content-type") && e.Headers["content-type"].Contains("application/json"))
{
var json = await e.JsonAsync();
Serilog.Log.Information(json.ToString());
}
};*/
};
2024-06-15 04:38:38 +00:00
_contexts[ctxId].Close += async (sender, e) =>
{
Serilog.Log.Warning($"Playwright browser context is closed");
2024-06-15 04:38:38 +00:00
_pages.Remove(ctxId);
_contexts.Remove(ctxId);
};
2024-04-09 03:38:56 +00:00
2024-06-15 04:38:38 +00:00
return _contexts[ctxId];
}
2024-12-29 19:16:39 +00:00
public async Task<IPage> NewPage(MessageInfo message, PageActionArgs args)
{
2024-07-22 20:45:01 +00:00
var context = await GetContext(message.ContextId);
2024-06-30 13:05:57 +00:00
var page = await context.NewPageAsync();
// 许多网站为了防止信息被爬取,会添加一些防护手段。其中之一是检测 window.navigator.webdriver 属性。
// 当使用 Playwright 打开浏览器时,该属性会被设置为 true从而被网站识别为自动化工具。通过以下方式屏蔽这个属性让网站无法识别是否使用了 Playwright
var js = @"Object.defineProperties(navigator, {webdriver:{get:()=>false}});";
await page.AddInitScriptAsync(js);
2024-06-21 18:23:18 +00:00
2024-12-29 19:16:39 +00:00
if (!args.EnableResponseCallback)
2024-08-12 14:43:39 +00:00
{
return page;
}
2024-08-05 00:15:27 +00:00
page.Response += async (sender, e) =>
{
2024-12-29 19:16:39 +00:00
await HandleFetchResponse(e, message, args);
};
2024-08-31 12:03:25 +00:00
2024-12-29 19:16:39 +00:00
return page;
}
2024-10-18 16:32:41 +00:00
2024-12-29 19:16:39 +00:00
public async Task HandleFetchResponse(IResponse response, MessageInfo message, PageActionArgs args)
{
if (response.Status != 204 &&
response.Headers.ContainsKey("content-type") &&
(response.Request.ResourceType == "fetch" || response.Request.ResourceType == "xhr") &&
(args.ExcludeResponseUrls == null || !args.ExcludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))) &&
(args.IncludeResponseUrls == null || args.IncludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))))
{
Serilog.Log.Information($"{response.Request.Method}: {response.Url}");
try
{
var result = new WebPageResponseData
{
Url = response.Url.ToLower(),
PostData = response.Request?.PostData ?? string.Empty,
ResponseInMemory = args.ResponseInMemory
};
2024-08-31 12:03:25 +00:00
2024-12-30 11:15:30 +00:00
var html = await response.TextAsync();
2024-12-29 19:16:39 +00:00
if (response.Headers["content-type"].Contains("application/json"))
{
if (response.Status == 200 && response.Ok)
2024-12-30 11:15:30 +00:00
{
if (!string.IsNullOrWhiteSpace(html))
{
var json = await response.JsonAsync();
result.ResponseData = JsonSerializer.Serialize(json);
}
2024-08-05 00:15:27 +00:00
}
}
2024-12-29 19:16:39 +00:00
else
2024-08-05 00:15:27 +00:00
{
2024-12-29 19:16:39 +00:00
result.ResponseData = html;
2024-08-05 00:15:27 +00:00
}
2024-12-29 19:16:39 +00:00
if (args.ResponseContainer != null && args.ResponseInMemory)
2024-08-05 00:15:27 +00:00
{
2024-12-29 19:16:39 +00:00
args.ResponseContainer.Add(result);
2024-08-05 00:15:27 +00:00
}
2024-12-29 19:16:39 +00:00
Serilog.Log.Warning($"Response status: {response.Status} {response.StatusText}, OK: {response.Ok}");
var webPageResponseHooks = _services.GetServices<IWebPageResponseHook>();
foreach (var hook in webPageResponseHooks)
{
hook.OnDataFetched(message, result);
}
}
catch (ObjectDisposedException ex)
{
Serilog.Log.Information(ex.Message);
}
catch (Exception ex)
{
Serilog.Log.Error($"{response.Url}\r\n" + ex.ToString());
}
}
}
2024-06-15 04:38:38 +00:00
/// <summary>
/// Wait page and network until timeout in seconds
/// </summary>
/// <param name="ctxId"></param>
/// <param name="timeout">seconds</param>
/// <returns></returns>
2024-09-09 03:02:52 +00:00
public async Task Wait(string ctxId, int timeout = 10, bool waitNetworkIdle = true)
{
2024-06-15 04:38:38 +00:00
foreach (var page in _pages[ctxId])
{
2024-02-15 21:55:50 +00:00
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
2024-09-09 03:02:52 +00:00
if (waitNetworkIdle)
2024-06-15 04:38:38 +00:00
{
2024-09-09 03:02:52 +00:00
await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions
{
Timeout = 1000 * timeout
});
}
}
2024-02-12 21:49:18 +00:00
await Task.Delay(100);
}
2024-06-15 04:38:38 +00:00
public async Task Close(string ctxId)
{
2024-06-15 04:38:38 +00:00
if (_contexts.ContainsKey(ctxId))
{
2024-06-15 04:38:38 +00:00
await _contexts[ctxId].CloseAsync();
2024-01-06 03:24:13 +00:00
}
}
2024-06-15 04:38:38 +00:00
public async Task CloseCurrentPage(string ctxId)
2024-04-09 03:38:56 +00:00
{
2024-06-15 04:38:38 +00:00
var pages = _pages[ctxId].ToArray();
for (var i = 0; i < pages.Length; i++)
2024-04-09 03:38:56 +00:00
{
2024-06-22 22:04:03 +00:00
var page = _pages[ctxId].FirstOrDefault();
if (page != null)
{
await page.CloseAsync();
}
2024-04-09 03:38:56 +00:00
}
}
2024-01-03 04:43:37 +00:00
public void Dispose()
{
_contexts.Clear();
2024-05-17 23:45:18 +00:00
_playwright?.Dispose();
2024-01-03 04:43:37 +00:00
}
}