diff --git a/src/Playwright/Core/APIRequest.cs b/src/Playwright/Core/APIRequest.cs index a5d7797fa5..342f067df9 100644 --- a/src/Playwright/Core/APIRequest.cs +++ b/src/Playwright/Core/APIRequest.cs @@ -22,7 +22,12 @@ * SOFTWARE. */ +using System.Collections.Generic; +using System.IO; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright.Helpers; +using Microsoft.Playwright.Transport.Channels; namespace Microsoft.Playwright.Core; @@ -37,17 +42,34 @@ public APIRequest(PlaywrightImpl playwright) async Task IAPIRequest.NewContextAsync(APIRequestNewContextOptions options) { - var context = await _playwright._channel.NewRequestAsync( - options?.BaseURL, - options?.UserAgent, - options?.IgnoreHTTPSErrors, - options?.ExtraHTTPHeaders, - options?.HttpCredentials, - options?.Proxy, - options?.Timeout, - options?.StorageState, - options?.StorageStatePath) - .ConfigureAwait(false); + var args = new Dictionary() + { + ["baseURL"] = options?.BaseURL, + ["userAgent"] = options?.UserAgent, + ["ignoreHTTPSErrors"] = options?.IgnoreHTTPSErrors, + ["extraHTTPHeaders"] = options?.ExtraHTTPHeaders?.ToProtocol(), + ["httpCredentials"] = options?.HttpCredentials, + ["proxy"] = options?.Proxy, + ["timeout"] = options?.Timeout, + }; + string storageState = options?.StorageState; + if (!string.IsNullOrEmpty(options?.StorageStatePath)) + { + if (!File.Exists(options?.StorageStatePath)) + { + throw new PlaywrightException($"The specified storage state file does not exist: {options?.StorageStatePath}"); + } + + storageState = File.ReadAllText(options?.StorageStatePath); + } + if (!string.IsNullOrEmpty(storageState)) + { + args.Add("storageState", JsonSerializer.Deserialize(storageState, Helpers.JsonExtensions.DefaultJsonSerializerOptions)); + } + + var context = (await _playwright.SendMessageToServerAsync( + "newRequest", + args).ConfigureAwait(false)).Object; context._request = this; return context; } diff --git a/src/Playwright/Core/APIRequestContext.cs b/src/Playwright/Core/APIRequestContext.cs index 3a60651a38..90feac8490 100644 --- a/src/Playwright/Core/APIRequestContext.cs +++ b/src/Playwright/Core/APIRequestContext.cs @@ -54,7 +54,7 @@ public APIRequestContext(IChannelOwner parent, string guid, APIRequestContextIni IChannel IChannelOwner.Channel => _channel; [MethodImpl(MethodImplOptions.NoInlining)] - public ValueTask DisposeAsync() => new(_channel.DisposeAsync()); + public ValueTask DisposeAsync() => new(SendMessageToServerAsync("dispose")); [MethodImpl(MethodImplOptions.NoInlining)] public Task FetchAsync(IRequest request, APIRequestContextOptions options = null) @@ -121,19 +121,24 @@ public async Task FetchAsync(string url, APIRequestContextOptions jsonData = JsonSerializer.Serialize(options.DataObject, _connection.DefaultJsonSerializerOptionsKeepNulls); } - return await _channel.FetchAsync( - url, - queryParams, - options.Method, - options.Headers, - jsonData, - postData, - (FormData)options.Form, - (FormData)options.Multipart, - options.Timeout, - options?.FailOnStatusCode, - options?.IgnoreHTTPSErrors, - options?.MaxRedirects).ConfigureAwait(false); + var message = new Dictionary + { + ["url"] = url, + ["method"] = options?.Method, + ["failOnStatusCode"] = options?.FailOnStatusCode, + ["ignoreHTTPSErrors"] = options?.IgnoreHTTPSErrors, + ["maxRedirects"] = options?.MaxRedirects, + ["timeout"] = options.Timeout, + ["params"] = queryParams?.ToProtocol(), + ["headers"] = options.Headers?.ToProtocol(), + ["jsonData"] = jsonData, + ["postData"] = postData != null ? Convert.ToBase64String(postData) : null, + ["formData"] = ((FormData)options.Form)?.ToProtocol(throwWhenSerializingFilePayloads: true), + ["multipartData"] = ((FormData)options.Multipart)?.ToProtocol(), + }; + + var response = await SendMessageToServerAsync("fetch", message).ConfigureAwait(false); + return new APIResponse(this, response?.GetProperty("response").ToObject()); } private bool IsJsonContentType(IDictionary headers) @@ -193,7 +198,7 @@ private APIRequestContextOptions WithMethod(APIRequestContextOptions options, st public async Task StorageStateAsync(APIRequestContextStorageStateOptions options = null) { string state = JsonSerializer.Serialize( - await _channel.StorageStateAsync().ConfigureAwait(false), + await SendMessageToServerAsync("storageState").ConfigureAwait(false), JsonExtensions.DefaultJsonSerializerOptions); if (!string.IsNullOrEmpty(options?.Path)) diff --git a/src/Playwright/Core/APIResponse.cs b/src/Playwright/Core/APIResponse.cs index 9c6ed84fa6..5e9b12128a 100644 --- a/src/Playwright/Core/APIResponse.cs +++ b/src/Playwright/Core/APIResponse.cs @@ -27,6 +27,7 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright.Helpers; namespace Microsoft.Playwright.Core; @@ -61,12 +62,12 @@ public async Task BodyAsync() { try { - var result = await _context._channel.FetchResponseBodyAsync(FetchUid()).ConfigureAwait(false); - if (result == null) + var response = await _context.SendMessageToServerAsync("fetchResponseBody", new Dictionary { ["fetchUid"] = FetchUid() }).ConfigureAwait(false); + if (response?.TryGetProperty("binary", out var binary) == true) { - throw new PlaywrightException("Response has been disposed"); + return Convert.FromBase64String(binary.ToString()); } - return Convert.FromBase64String(result); + throw new PlaywrightException("Response has been disposed"); } catch (Exception e) when (DriverMessages.IsTargetClosedError(e)) { @@ -86,9 +87,13 @@ public async Task TextAsync() internal string FetchUid() => _initializer.FetchUid; - internal Task FetchLogAsync() => _context._channel.FetchResponseLogAsync(FetchUid()); + internal async Task FetchLogAsync() + { + var response = await _context.SendMessageToServerAsync("fetchLog", new Dictionary { ["fetchUid"] = FetchUid() }).ConfigureAwait(false); + return response.Value.GetProperty("log").ToObject(); + } - public ValueTask DisposeAsync() => new(_context._channel.DisposeAPIResponseAsync(FetchUid())); + public ValueTask DisposeAsync() => new(_context.SendMessageToServerAsync("disposeAPIResponse", new Dictionary { ["fetchUid"] = FetchUid() })); public override string ToString() { diff --git a/src/Playwright/Core/Accessibility.cs b/src/Playwright/Core/Accessibility.cs index a9ba4df3af..474cc36280 100644 --- a/src/Playwright/Core/Accessibility.cs +++ b/src/Playwright/Core/Accessibility.cs @@ -22,6 +22,7 @@ * SOFTWARE. */ +using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Playwright.Transport.Channels; @@ -37,9 +38,17 @@ public Accessibility(PageChannel channel) _channel = channel; } - public Task SnapshotAsync(AccessibilitySnapshotOptions options = default) + public async Task SnapshotAsync(AccessibilitySnapshotOptions options = default) { options ??= new(); - return _channel.AccessibilitySnapshotAsync(options.InterestingOnly, (options.Root as ElementHandle)?.ElementChannel); + if ((await _channel.Object.SendMessageToServerAsync("accessibilitySnapshot", new Dictionary + { + ["interestingOnly"] = options?.InterestingOnly, + ["root"] = (options.Root as ElementHandle)?.ElementChannel, + }).ConfigureAwait(false)).Value.TryGetProperty("rootAXNode", out var jsonElement)) + { + return jsonElement; + } + return null; } } diff --git a/src/Playwright/Core/Artifact.cs b/src/Playwright/Core/Artifact.cs index 15d2e94c03..269d75b426 100644 --- a/src/Playwright/Core/Artifact.cs +++ b/src/Playwright/Core/Artifact.cs @@ -22,9 +22,12 @@ * SOFTWARE. */ +using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright.Helpers; using Microsoft.Playwright.Transport; using Microsoft.Playwright.Transport.Channels; using Microsoft.Playwright.Transport.Protocol; @@ -56,7 +59,8 @@ public async Task PathAfterFinishedAsync() { throw new PlaywrightException("Path is not available when connecting remotely. Use SaveAsAsync() to save a local copy."); } - return await _channel.PathAfterFinishedAsync().ConfigureAwait(false); + return (await SendMessageToServerAsync("pathAfterFinished") + .ConfigureAwait(false)).GetString("value", true); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -64,11 +68,17 @@ public async Task SaveAsAsync(string path) { if (!_connection.IsRemote) { - await _channel.SaveAsAsync(path).ConfigureAwait(false); + await SendMessageToServerAsync( + "saveAs", + new Dictionary + { + ["path"] = path, + }).ConfigureAwait(false); return; } - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); - var stream = await _channel.SaveAsStreamAsync().ConfigureAwait(false); + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); + var stream = (await SendMessageToServerAsync("saveAsStream") + .ConfigureAwait(false)).GetObject("stream", _connection); await using (stream.ConfigureAwait(false)) { using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) @@ -81,13 +91,15 @@ public async Task SaveAsAsync(string path) [MethodImpl(MethodImplOptions.NoInlining)] public async Task CreateReadStreamAsync() { - var stream = await _channel.StreamAsync().ConfigureAwait(false); + var stream = (await SendMessageToServerAsync("stream") + .ConfigureAwait(false))?.GetObject("stream", _connection); return stream.StreamImpl; } - internal Task CancelAsync() => _channel.CancelAsync(); + internal Task CancelAsync() => SendMessageToServerAsync("cancel"); - internal Task FailureAsync() => _channel.FailureAsync(); + internal async Task FailureAsync() => (await SendMessageToServerAsync("failure") + .ConfigureAwait(false)).GetString("error", true); - internal Task DeleteAsync() => _channel.DeleteAsync(); + internal Task DeleteAsync() => SendMessageToServerAsync("delete"); } diff --git a/src/Playwright/Core/BindingCall.cs b/src/Playwright/Core/BindingCall.cs index 02d0a6c82f..7da41e4000 100644 --- a/src/Playwright/Core/BindingCall.cs +++ b/src/Playwright/Core/BindingCall.cs @@ -27,6 +27,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Playwright.Helpers; using Microsoft.Playwright.Transport; using Microsoft.Playwright.Transport.Channels; using Microsoft.Playwright.Transport.Protocol; @@ -96,15 +97,28 @@ internal async Task CallAsync(Delegate binding) } } - await _channel.ResolveAsync(ScriptsHelper.SerializedArgument(result)).ConfigureAwait(false); + await SendMessageToServerAsync("resolve", new Dictionary + { + ["result"] = ScriptsHelper.SerializedArgument(result), + }).ConfigureAwait(false); } catch (TargetInvocationException ex) { - await _channel.RejectAsync(ex.InnerException).ConfigureAwait(false); + await SendMessageToServerAsync( + "reject", + new Dictionary + { + ["error"] = ex.InnerException.ToObject(), + }).ConfigureAwait(false); } catch (Exception ex) { - await _channel.RejectAsync(ex).ConfigureAwait(false); + await SendMessageToServerAsync( + "reject", + new Dictionary + { + ["error"] = ex.ToObject(), + }).ConfigureAwait(false); } } } diff --git a/src/Playwright/Core/Browser.cs b/src/Playwright/Core/Browser.cs index 63fba13eb6..e05885af0b 100644 --- a/src/Playwright/Core/Browser.cs +++ b/src/Playwright/Core/Browser.cs @@ -24,9 +24,13 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Playwright.Helpers; using Microsoft.Playwright.Transport; using Microsoft.Playwright.Transport.Channels; using Microsoft.Playwright.Transport.Protocol; @@ -88,7 +92,10 @@ public async Task CloseAsync(BrowserCloseOptions options = default) } else { - await Channel.CloseAsync(options?.Reason).ConfigureAwait(false); + await SendMessageToServerAsync("close", new Dictionary + { + ["reason"] = options?.Reason, + }).ConfigureAwait(false); } await _closedTcs.Task.ConfigureAwait(false); } @@ -102,41 +109,74 @@ public async Task CloseAsync(BrowserCloseOptions options = default) public async Task NewContextAsync(BrowserNewContextOptions options = default) { options ??= new(); - var context = (await Channel.NewContextAsync( - acceptDownloads: options.AcceptDownloads, - bypassCSP: options.BypassCSP, - colorScheme: options.ColorScheme, - reducedMotion: options.ReducedMotion, - deviceScaleFactor: options.DeviceScaleFactor, - extraHTTPHeaders: options.ExtraHTTPHeaders, - geolocation: options.Geolocation, - hasTouch: options.HasTouch, - httpCredentials: options.HttpCredentials, - ignoreHTTPSErrors: options.IgnoreHTTPSErrors, - isMobile: options.IsMobile, - javaScriptEnabled: options.JavaScriptEnabled, - locale: options.Locale, - offline: options.Offline, - permissions: options.Permissions, - proxy: options.Proxy, - recordHarContent: options.RecordHarContent, - recordHarMode: options.RecordHarMode, - recordHarOmitContent: options.RecordHarOmitContent, - recordHarPath: options.RecordHarPath, - recordHarUrlFilter: options.RecordHarUrlFilter, - recordHarUrlFilterString: options.RecordHarUrlFilterString, - recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex, - recordVideo: GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize), - storageState: options.StorageState, - storageStatePath: options.StorageStatePath, - serviceWorkers: options.ServiceWorkers, - timezoneId: options.TimezoneId, - userAgent: options.UserAgent, - viewportSize: options.ViewportSize, - screenSize: options.ScreenSize, - baseUrl: options.BaseURL, - strictSelectors: options.StrictSelectors, - forcedColors: options.ForcedColors).ConfigureAwait(false)).Object; + + var args = new Dictionary + { + ["bypassCSP"] = options.BypassCSP, + ["deviceScaleFactor"] = options.DeviceScaleFactor, + ["serviceWorkers"] = options.ServiceWorkers, + ["geolocation"] = options.Geolocation, + ["hasTouch"] = options.HasTouch, + ["httpCredentials"] = options.HttpCredentials, + ["ignoreHTTPSErrors"] = options.IgnoreHTTPSErrors, + ["isMobile"] = options.IsMobile, + ["javaScriptEnabled"] = options.JavaScriptEnabled, + ["locale"] = options.Locale, + ["offline"] = options.Offline, + ["permissions"] = options.Permissions, + ["proxy"] = options.Proxy, + ["strictSelectors"] = options.StrictSelectors, + ["colorScheme"] = options.ColorScheme == ColorScheme.Null ? "no-override" : options.ColorScheme, + ["reducedMotion"] = options.ReducedMotion == ReducedMotion.Null ? "no-override" : options.ReducedMotion, + ["forcedColors"] = options.ForcedColors == ForcedColors.Null ? "no-override" : options.ForcedColors, + ["extraHTTPHeaders"] = options.ExtraHTTPHeaders?.Select(kv => new HeaderEntry { Name = kv.Key, Value = kv.Value }).ToArray(), + ["recordHar"] = PrepareHarOptions( + recordHarContent: options.RecordHarContent, + recordHarMode: options.RecordHarMode, + recordHarPath: options.RecordHarPath, + recordHarOmitContent: options.RecordHarOmitContent, + recordHarUrlFilter: options.RecordHarUrlFilter, + recordHarUrlFilterString: options.RecordHarUrlFilterString, + recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex), + ["recordVideo"] = GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize), + ["timezoneId"] = options.TimezoneId, + ["userAgent"] = options.UserAgent, + ["baseURL"] = options.BaseURL, + }; + + if (options.AcceptDownloads.HasValue) + { + args.Add("acceptDownloads", options.AcceptDownloads.Value ? "accept" : "deny"); + } + + var storageState = options.StorageState; + if (!string.IsNullOrEmpty(options.StorageStatePath)) + { + if (!File.Exists(options.StorageStatePath)) + { + throw new PlaywrightException($"The specified storage state file does not exist: {options.StorageStatePath}"); + } + + storageState = File.ReadAllText(options.StorageStatePath); + } + + if (!string.IsNullOrEmpty(storageState)) + { + args.Add("storageState", JsonSerializer.Deserialize(storageState, Helpers.JsonExtensions.DefaultJsonSerializerOptions)); + } + + if (options.ViewportSize?.Width == -1) + { + args.Add("noDefaultViewport", true); + } + else + { + args.Add("viewport", options.ViewportSize); + args.Add("screen", options.ScreenSize); + } + + var context = (await SendMessageToServerAsync("newContext", args).ConfigureAwait(false)).Object; + _browserType.DidCreateContext(context, options, null); return context; } @@ -234,5 +274,54 @@ internal void DidClose() [MethodImpl(MethodImplOptions.NoInlining)] public async Task NewBrowserCDPSessionAsync() - => (await Channel.NewBrowserCDPSessionAsync().ConfigureAwait(false)).Object; + => (await SendMessageToServerAsync( + "newBrowserCDPSession").ConfigureAwait(false)).Object; + + internal static Dictionary PrepareHarOptions( + HarContentPolicy? recordHarContent, + HarMode? recordHarMode, + string recordHarPath, + bool? recordHarOmitContent, + string recordHarUrlFilter, + string recordHarUrlFilterString, + Regex recordHarUrlFilterRegex) + { + if (string.IsNullOrEmpty(recordHarPath)) + { + return null; + } + var recordHarArgs = new Dictionary(); + recordHarArgs["path"] = recordHarPath; + if (recordHarContent.HasValue) + { + recordHarArgs["content"] = recordHarContent; + } + else if (recordHarOmitContent == true) + { + recordHarArgs["content"] = HarContentPolicy.Omit; + } + if (!string.IsNullOrEmpty(recordHarUrlFilter)) + { + recordHarArgs["urlGlob"] = recordHarUrlFilter; + } + else if (!string.IsNullOrEmpty(recordHarUrlFilterString)) + { + recordHarArgs["urlGlob"] = recordHarUrlFilterString; + } + else if (recordHarUrlFilterRegex != null) + { + recordHarArgs["urlRegexSource"] = recordHarUrlFilterRegex.ToString(); + recordHarArgs["urlRegexFlags"] = recordHarUrlFilterRegex.Options.GetInlineFlags(); + } + if (recordHarMode.HasValue) + { + recordHarArgs["mode"] = recordHarMode; + } + + if (recordHarArgs.Keys.Count > 0) + { + return recordHarArgs; + } + return null; + } } diff --git a/src/Playwright/Core/BrowserContext.cs b/src/Playwright/Core/BrowserContext.cs index 859ebb67a3..3202711518 100644 --- a/src/Playwright/Core/BrowserContext.cs +++ b/src/Playwright/Core/BrowserContext.cs @@ -260,7 +260,12 @@ internal void OnDialog(IDialog dialog) } [MethodImpl(MethodImplOptions.NoInlining)] - public Task AddCookiesAsync(IEnumerable cookies) => Channel.AddCookiesAsync(cookies); + public Task AddCookiesAsync(IEnumerable cookies) => SendMessageToServerAsync( + "addCookies", + new Dictionary + { + ["cookies"] = cookies, + }); [MethodImpl(MethodImplOptions.NoInlining)] public Task AddInitScriptAsync(string script = null, string scriptPath = null) @@ -270,14 +275,19 @@ public Task AddInitScriptAsync(string script = null, string scriptPath = null) script = ScriptsHelper.EvaluationScript(script, scriptPath); } - return Channel.AddInitScriptAsync(script); + return SendMessageToServerAsync( + "addInitScript", + new Dictionary + { + ["source"] = script, + }); } [MethodImpl(MethodImplOptions.NoInlining)] - public Task ClearCookiesAsync() => Channel.ClearCookiesAsync(); + public Task ClearCookiesAsync() => SendMessageToServerAsync("clearCookies"); [MethodImpl(MethodImplOptions.NoInlining)] - public Task ClearPermissionsAsync() => Channel.ClearPermissionsAsync(); + public Task ClearPermissionsAsync() => SendMessageToServerAsync("clearPermissions"); [MethodImpl(MethodImplOptions.NoInlining)] public async Task CloseAsync(BrowserContextCloseOptions options = default) @@ -293,7 +303,12 @@ await WrapApiCallAsync( { foreach (var harRecorder in _harRecorders) { - Artifact artifact = await Channel.HarExportAsync(harRecorder.Key).ConfigureAwait(false); + Artifact artifact = (await SendMessageToServerAsync( + "harExport", + new Dictionary + { + ["harId"] = harRecorder.Key, + }).ConfigureAwait(false)).GetObject("artifact", _connection); // Server side will compress artifact if content is attach or if file is .zip. var isCompressed = harRecorder.Value.Content == HarContentPolicy.Attach || harRecorder.Value.Path.EndsWith(".zip", StringComparison.Ordinal); var needCompressed = harRecorder.Value.Path.EndsWith(".zip", StringComparison.Ordinal); @@ -310,7 +325,10 @@ await WrapApiCallAsync( } }, true).ConfigureAwait(false); - await Channel.CloseAsync(options?.Reason).ConfigureAwait(false); + await SendMessageToServerAsync("close", new Dictionary + { + ["reason"] = options?.Reason, + }).ConfigureAwait(false); await _closeTcs.Task.ConfigureAwait(false); } @@ -325,7 +343,12 @@ internal void SetOptions(BrowserNewContextOptions contextOptions, string tracesD } [MethodImpl(MethodImplOptions.NoInlining)] - public Task> CookiesAsync(IEnumerable urls = null) => Channel.CookiesAsync(urls); + public async Task> CookiesAsync(IEnumerable urls = null) => (await SendMessageToServerAsync( + "cookies", + new Dictionary + { + ["urls"] = urls?.ToArray() ?? Array.Empty(), + }).ConfigureAwait(false))?.GetProperty("cookies").ToObject>(); [MethodImpl(MethodImplOptions.NoInlining)] public Task ExposeBindingAsync(string name, Action callback, BrowserContextExposeBindingOptions options = default) @@ -393,15 +416,29 @@ public Task ExposeFunctionAsync(string name, Func permissions, BrowserContextGrantPermissionsOptions options = default) - => Channel.GrantPermissionsAsync(permissions, options?.Origin); + => SendMessageToServerAsync("grantPermissions", new Dictionary + { + ["permissions"] = permissions?.ToArray(), + ["origin"] = options?.Origin, + }); [MethodImpl(MethodImplOptions.NoInlining)] public async Task NewCDPSessionAsync(IPage page) - => (await Channel.NewCDPSessionAsync(page as Page).ConfigureAwait(false)).Object; + => (await SendMessageToServerAsync( + "newCDPSession", + new Dictionary + { + ["page"] = new { guid = (page as Page).Guid }, + }).ConfigureAwait(false)).Object; [MethodImpl(MethodImplOptions.NoInlining)] public async Task NewCDPSessionAsync(IFrame frame) - => (await Channel.NewCDPSessionAsync(frame as Frame).ConfigureAwait(false)).Object; + => (await SendMessageToServerAsync( + "newCDPSession", + new Dictionary + { + ["frame"] = new { guid = (frame as Frame).Guid }, + }).ConfigureAwait(false)).Object; [MethodImpl(MethodImplOptions.NoInlining)] public async Task NewPageAsync() @@ -411,7 +448,7 @@ public async Task NewPageAsync() throw new PlaywrightException("Please use Browser.NewContextAsync()"); } - return (await Channel.NewPageAsync().ConfigureAwait(false)).Object; + return (await SendMessageToServerAsync("newPage").ConfigureAwait(false)).Object; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -440,19 +477,34 @@ public Task RouteAsync(Func url, Func handler, Brows [MethodImpl(MethodImplOptions.NoInlining)] public Task SetExtraHTTPHeadersAsync(IEnumerable> headers) - => Channel.SetExtraHTTPHeadersAsync(headers); + => SendMessageToServerAsync( + "setExtraHTTPHeaders", + new Dictionary + { + ["headers"] = headers.Select(kv => new HeaderEntry { Name = kv.Key, Value = kv.Value }), + }); [MethodImpl(MethodImplOptions.NoInlining)] - public Task SetGeolocationAsync(Geolocation geolocation) => Channel.SetGeolocationAsync(geolocation); + public Task SetGeolocationAsync(Geolocation geolocation) => SendMessageToServerAsync( + "setGeolocation", + new Dictionary + { + ["geolocation"] = geolocation, + }); [MethodImpl(MethodImplOptions.NoInlining)] - public Task SetOfflineAsync(bool offline) => Channel.SetOfflineAsync(offline); + public Task SetOfflineAsync(bool offline) => SendMessageToServerAsync( + "setOffline", + new Dictionary + { + ["offline"] = offline, + }); [MethodImpl(MethodImplOptions.NoInlining)] public async Task StorageStateAsync(BrowserContextStorageStateOptions options = default) { string state = JsonSerializer.Serialize( - await Channel.GetStorageStateAsync().ConfigureAwait(false), + await SendMessageToServerAsync("storageState").ConfigureAwait(false), JsonExtensions.DefaultJsonSerializerOptions); if (!string.IsNullOrEmpty(options?.Path)) @@ -543,7 +595,14 @@ public Task RunAndWaitForConsoleMessageAsync(Func action, internal void SetDefaultNavigationTimeoutImpl(float? timeout) { _timeoutSettings.SetDefaultNavigationTimeout(timeout); - WrapApiCallAsync(() => Channel.SetDefaultNavigationTimeoutNoReplyAsync(timeout), true).IgnoreException(); + WrapApiCallAsync( + () => SendMessageToServerAsync( + "setDefaultNavigationTimeoutNoReply", + new Dictionary + { + ["timeout"] = timeout, + }), + true).IgnoreException(); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -552,7 +611,14 @@ internal void SetDefaultNavigationTimeoutImpl(float? timeout) internal void SetDefaultTimeoutImpl(float? timeout) { _timeoutSettings.SetDefaultTimeout(timeout); - WrapApiCallAsync(() => Channel.SetDefaultTimeoutNoReplyAsync(timeout), true).IgnoreException(); + WrapApiCallAsync( + () => SendMessageToServerAsync( + "setDefaultTimeoutNoReply", + new Dictionary + { + ["timeout"] = timeout, + }), + true).IgnoreException(); } internal async Task OnRouteAsync(Route route) @@ -644,7 +710,9 @@ private Task UnrouteAsync(RouteHandler setting) private Task UpdateInterceptionAsync() { var patterns = RouteHandler.PrepareInterceptionPatterns(_routes); - return Channel.SetNetworkInterceptionPatternsAsync(patterns); + return SendMessageToServerAsync( + "setNetworkInterceptionPatterns", + patterns); } internal void OnClose() @@ -698,7 +766,13 @@ private Task ExposeBindingAsync(string name, Delegate callback, bool handle = fa _bindings.Add(name, callback); - return Channel.ExposeBindingAsync(name, handle); + return SendMessageToServerAsync( + "exposeBinding", + new Dictionary + { + ["name"] = name, + ["needsHandle"] = handle, + }); } private HarContentPolicy? RouteFromHarUpdateContentPolicyToHarContentPolicy(RouteFromHarUpdateContentPolicy? policy) @@ -717,14 +791,11 @@ private Task ExposeBindingAsync(string name, Delegate callback, bool handle = fa internal async Task RecordIntoHarAsync(string har, Page page, BrowserContextRouteFromHAROptions options) { var contentPolicy = RouteFromHarUpdateContentPolicyToHarContentPolicy(options?.UpdateContent); - var harId = await Channel.HarStartAsync( - page, - har, - options?.Url, - options?.UrlString, - options?.UrlRegex, - contentPolicy, - options?.UpdateMode).ConfigureAwait(false); + var harId = (await SendMessageToServerAsync("harStart", new Dictionary + { + { "page", page?.Channel }, + { "options", Core.Browser.PrepareHarOptions(contentPolicy ?? HarContentPolicy.Attach, options.UpdateMode ?? HarMode.Minimal, har, null, options.Url, options.UrlString, options.UrlRegex) }, + }).ConfigureAwait(false)).GetString("harId", false); _harRecorders.Add(harId, new() { Path = har, Content = contentPolicy ?? HarContentPolicy.Attach }); } diff --git a/src/Playwright/Core/BrowserType.cs b/src/Playwright/Core/BrowserType.cs index 753f308810..c1b92c5b32 100644 --- a/src/Playwright/Core/BrowserType.cs +++ b/src/Playwright/Core/BrowserType.cs @@ -60,25 +60,29 @@ internal BrowserType(IChannelOwner parent, string guid, BrowserTypeInitializer i public async Task LaunchAsync(BrowserTypeLaunchOptions options = default) { options ??= new BrowserTypeLaunchOptions(); - Browser browser = (await _channel.LaunchAsync( - headless: options.Headless, - channel: options.Channel, - executablePath: options.ExecutablePath, - passedArguments: options.Args, - proxy: options.Proxy, - downloadsPath: options.DownloadsPath, - tracesDir: options.TracesDir, - chromiumSandbox: options.ChromiumSandbox, - firefoxUserPrefs: options.FirefoxUserPrefs, - handleSIGINT: options.HandleSIGINT, - handleSIGTERM: options.HandleSIGTERM, - handleSIGHUP: options.HandleSIGHUP, - timeout: options.Timeout, - env: options.Env, - devtools: options.Devtools, - slowMo: options.SlowMo, - ignoreDefaultArgs: options.IgnoreDefaultArgs, - ignoreAllDefaultArgs: options.IgnoreAllDefaultArgs).ConfigureAwait(false)).Object; + Browser browser = (await SendMessageToServerAsync( + "launch", + new Dictionary + { + { "channel", options.Channel }, + { "executablePath", options.ExecutablePath }, + { "args", options.Args }, + { "ignoreAllDefaultArgs", options.IgnoreAllDefaultArgs }, + { "ignoreDefaultArgs", options.IgnoreDefaultArgs }, + { "handleSIGHUP", options.HandleSIGHUP }, + { "handleSIGINT", options.HandleSIGINT }, + { "handleSIGTERM", options.HandleSIGTERM }, + { "headless", options.Headless }, + { "devtools", options.Devtools }, + { "env", options.Env.ToProtocol() }, + { "proxy", options.Proxy }, + { "downloadsPath", options.DownloadsPath }, + { "tracesDir", options.TracesDir }, + { "firefoxUserPrefs", options.FirefoxUserPrefs }, + { "chromiumSandbox", options.ChromiumSandbox }, + { "slowMo", options.ChromiumSandbox }, + { "timeout", options.Timeout }, + }).ConfigureAwait(false)).Object; DidLaunchBrowser(browser); return browser; } @@ -87,56 +91,74 @@ public async Task LaunchAsync(BrowserTypeLaunchOptions options = defau public async Task LaunchPersistentContextAsync(string userDataDir, BrowserTypeLaunchPersistentContextOptions options = default) { options ??= new BrowserTypeLaunchPersistentContextOptions(); - var context = (await _channel.LaunchPersistentContextAsync( - userDataDir, - headless: options.Headless, - channel: options.Channel, - executablePath: options.ExecutablePath, - args: options.Args, - proxy: options.Proxy, - downloadsPath: options.DownloadsPath, - tracesDir: options.TracesDir, - chromiumSandbox: options.ChromiumSandbox, - firefoxUserPrefs: options.FirefoxUserPrefs, - handleSIGINT: options.HandleSIGINT, - handleSIGTERM: options.HandleSIGTERM, - handleSIGHUP: options.HandleSIGHUP, - timeout: options.Timeout, - env: options.Env, - devtools: options.Devtools, - slowMo: options.SlowMo, - acceptDownloads: options.AcceptDownloads, - ignoreHTTPSErrors: options.IgnoreHTTPSErrors, - bypassCSP: options.BypassCSP, - viewportSize: options.ViewportSize, - screenSize: options.ScreenSize, - userAgent: options.UserAgent, - deviceScaleFactor: options.DeviceScaleFactor, - isMobile: options.IsMobile, - hasTouch: options.HasTouch, - javaScriptEnabled: options.JavaScriptEnabled, - timezoneId: options.TimezoneId, - geolocation: options.Geolocation, - locale: options.Locale, - permissions: options.Permissions, - extraHTTPHeaders: options.ExtraHTTPHeaders, - offline: options.Offline, - httpCredentials: options.HttpCredentials, - colorScheme: options.ColorScheme, - reducedMotion: options.ReducedMotion, - recordHarContent: options.RecordHarContent, - recordHarMode: options.RecordHarMode, - recordHarPath: options.RecordHarPath, - recordHarOmitContent: options.RecordHarOmitContent, - recordHarUrlFilter: options.RecordHarUrlFilter, - recordHarUrlFilterString: options.RecordHarUrlFilterString, - recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex, - recordVideo: Browser.GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize), - serviceWorkers: options.ServiceWorkers, - ignoreDefaultArgs: options.IgnoreDefaultArgs, - ignoreAllDefaultArgs: options.IgnoreAllDefaultArgs, - baseUrl: options.BaseURL, - forcedColors: options.ForcedColors).ConfigureAwait(false)).Object; + var channelArgs = new Dictionary + { + ["userDataDir"] = userDataDir, + ["headless"] = options.Headless, + ["channel"] = options.Channel, + ["executablePath"] = options.ExecutablePath, + ["args"] = options.Args, + ["downloadsPath"] = options.DownloadsPath, + ["tracesDir"] = options.TracesDir, + ["proxy"] = options.Proxy, + ["chromiumSandbox"] = options.ChromiumSandbox, + ["firefoxUserPrefs"] = options.FirefoxUserPrefs, + ["handleSIGINT"] = options.HandleSIGINT, + ["handleSIGTERM"] = options.HandleSIGTERM, + ["handleSIGHUP"] = options.HandleSIGHUP, + ["timeout"] = options.Timeout, + ["env"] = options.Env.ToProtocol(), + ["devtools"] = options.Devtools, + ["slowMo"] = options.SlowMo, + ["ignoreHTTPSErrors"] = options.IgnoreHTTPSErrors, + ["bypassCSP"] = options.BypassCSP, + ["strictSelectors"] = options.StrictSelectors, + ["serviceWorkers"] = options.ServiceWorkers, + ["screensize"] = options.ScreenSize, + ["userAgent"] = options.UserAgent, + ["deviceScaleFactor"] = options.DeviceScaleFactor, + ["isMobile"] = options.IsMobile, + ["hasTouch"] = options.HasTouch, + ["javaScriptEnabled"] = options.JavaScriptEnabled, + ["timezoneId"] = options.TimezoneId, + ["geolocation"] = options.Geolocation, + ["locale"] = options.Locale, + ["permissions"] = options.Permissions, + ["extraHTTPHeaders"] = options.ExtraHTTPHeaders.ToProtocol(), + ["offline"] = options.Offline, + ["httpCredentials"] = options.HttpCredentials, + ["colorScheme"] = options.ColorScheme == ColorScheme.Null ? "no-override" : options.ColorScheme, + ["reducedMotion"] = options.ReducedMotion == ReducedMotion.Null ? "no-override" : options.ReducedMotion, + ["forcedColors"] = options.ForcedColors == ForcedColors.Null ? "no-override" : options.ForcedColors, + ["recordVideo"] = Browser.GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize), + ["ignoreDefaultArgs"] = options.IgnoreDefaultArgs, + ["ignoreAllDefaultArgs"] = options.IgnoreAllDefaultArgs, + ["baseURL"] = options.BaseURL, + ["recordHar"] = Browser.PrepareHarOptions( + recordHarContent: options.RecordHarContent, + recordHarMode: options.RecordHarMode, + recordHarPath: options.RecordHarPath, + recordHarOmitContent: options.RecordHarOmitContent, + recordHarUrlFilter: options.RecordHarUrlFilter, + recordHarUrlFilterString: options.RecordHarUrlFilterString, + recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex), + }; + + if (options.AcceptDownloads.HasValue) + { + channelArgs.Add("acceptDownloads", options.AcceptDownloads.Value ? "accept" : "deny"); + } + + if (options.ViewportSize?.Width == -1) + { + channelArgs.Add("noDefaultViewport", true); + } + else + { + channelArgs.Add("viewport", options.ViewportSize); + } + + var context = (await SendMessageToServerAsync("launchPersistentContext", channelArgs).ConfigureAwait(false)).Object; // TODO: unite with a single browser context options type which is derived from channels DidCreateContext( @@ -254,7 +276,13 @@ public async Task ConnectOverCDPAsync(string endpointURL, BrowserTypeC throw new ArgumentException("Connecting over CDP is only supported in Chromium."); } options ??= new BrowserTypeConnectOverCDPOptions(); - JsonElement result = await _channel.ConnectOverCDPAsync(endpointURL, headers: options.Headers, slowMo: options.SlowMo, timeout: options.Timeout).ConfigureAwait(false); + JsonElement result = await SendMessageToServerAsync("connectOverCDP", new Dictionary + { + { "endpointURL", endpointURL }, + { "headers", options.Headers.ToProtocol() }, + { "slowMo", options.SlowMo }, + { "timeout", options.Timeout }, + }).ConfigureAwait(false); Browser browser = result.GetProperty("browser").ToObject(_channel.Connection.DefaultJsonSerializerOptions); DidLaunchBrowser(browser); if (result.TryGetProperty("defaultContext", out JsonElement defaultContextValue)) diff --git a/src/Playwright/Core/CDPSession.cs b/src/Playwright/Core/CDPSession.cs index aca4da9c14..5e73b08883 100644 --- a/src/Playwright/Core/CDPSession.cs +++ b/src/Playwright/Core/CDPSession.cs @@ -58,11 +58,19 @@ internal override void OnMessage(string method, JsonElement? serverParams) } [MethodImpl(MethodImplOptions.NoInlining)] - public Task DetachAsync() => _channel.DetachAsync(); + public Task DetachAsync() => SendMessageToServerAsync("detach"); [MethodImpl(MethodImplOptions.NoInlining)] - public Task SendAsync(string method, Dictionary? args = null) - => _channel.SendAsync(method, args); + public async Task SendAsync(string method, Dictionary? args = null) + { + var newArgs = new Dictionary() { { "method", method } }; + if (args != null) + { + newArgs["params"] = args; + } + var result = await SendMessageToServerAsync("send", newArgs).ConfigureAwait(false); + return result?.GetProperty("result"); + } private void OnCDPEvent(string name, JsonElement? @params) { diff --git a/src/Playwright/Core/Dialog.cs b/src/Playwright/Core/Dialog.cs index 5ceec2af2c..9e15123ae8 100644 --- a/src/Playwright/Core/Dialog.cs +++ b/src/Playwright/Core/Dialog.cs @@ -22,6 +22,7 @@ * SOFTWARE. */ +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Playwright.Transport; @@ -56,8 +57,13 @@ public Dialog(IChannelOwner parent, string guid, DialogInitializer initializer) IChannel IChannelOwner.Channel => _channel; [MethodImpl(MethodImplOptions.NoInlining)] - public Task AcceptAsync(string promptText) => _channel.AcceptAsync(promptText ?? string.Empty); + public Task AcceptAsync(string promptText) => SendMessageToServerAsync( + "accept", + new Dictionary + { + ["promptText"] = promptText ?? string.Empty, + }); [MethodImpl(MethodImplOptions.NoInlining)] - public Task DismissAsync() => _channel.DismissAsync(); + public Task DismissAsync() => SendMessageToServerAsync("dismiss"); } diff --git a/src/Playwright/Core/ElementHandle.cs b/src/Playwright/Core/ElementHandle.cs index b346d2f901..5a138d2134 100644 --- a/src/Playwright/Core/ElementHandle.cs +++ b/src/Playwright/Core/ElementHandle.cs @@ -60,24 +60,40 @@ internal override void OnMessage(string method, JsonElement? serverParams) } public async Task WaitForSelectorAsync(string selector, ElementHandleWaitForSelectorOptions options = default) - => (await _channel.WaitForSelectorAsync( - selector: selector, - state: options?.State, - timeout: options?.Timeout, - strict: options?.Strict).ConfigureAwait(false))?.Object; + => (await SendMessageToServerAsync( + "waitForSelector", + new Dictionary + { + ["selector"] = selector, + ["timeout"] = options?.Timeout, + ["state"] = options?.State, + ["strict"] = options?.Strict, + }).ConfigureAwait(false)).Object; public Task WaitForElementStateAsync(ElementState state, ElementHandleWaitForElementStateOptions options = default) - => _channel.WaitForElementStateAsync(state, timeout: options?.Timeout); + => SendMessageToServerAsync("waitForElementState", new Dictionary + { + ["state"] = state, + ["timeout"] = options?.Timeout, + }); public Task PressAsync(string key, ElementHandlePressOptions options = default) - => _channel.PressAsync( - key, - delay: options?.Delay, - timeout: options?.Timeout, - noWaitAfter: options?.NoWaitAfter); + => SendMessageToServerAsync("press", new Dictionary + { + ["key"] = key, + ["delay"] = options?.Delay, + ["timeout"] = options?.Timeout, + ["noWaitAfter"] = options?.NoWaitAfter, + }); public Task TypeAsync(string text, ElementHandleTypeOptions options = default) - => _channel.TypeAsync(text, delay: options?.Delay, timeout: options?.Timeout, noWaitAfter: options?.NoWaitAfter); + => SendMessageToServerAsync("type", new Dictionary + { + ["text"] = text, + ["delay"] = options?.Delay, + ["timeout"] = options?.Timeout, + ["noWaitAfter"] = options?.NoWaitAfter, + }); public async Task ScreenshotAsync(ElementHandleScreenshotOptions options = default) { @@ -87,17 +103,28 @@ public async Task ScreenshotAsync(ElementHandleScreenshotOptions options options.Type = DetermineScreenshotType(options.Path); } - byte[] result = await _channel.ScreenshotAsync( - options.Path, - options.OmitBackground, - options.Type, - options.Quality, - options.Mask, - options.MaskColor, - options.Animations, - options.Caret, - options.Scale, - options.Timeout).ConfigureAwait(false); + var args = new Dictionary + { + ["type"] = options.Type, + ["omitBackground"] = options.OmitBackground, + ["path"] = options.Path, + ["timeout"] = options.Timeout, + ["animations"] = options.Animations, + ["caret"] = options.Caret, + ["scale"] = options.Scale, + ["quality"] = options.Quality, + ["maskColor"] = options.MaskColor, + }; + if (options.Mask != null) + { + args["mask"] = options.Mask.Select(locator => new Dictionary + { + ["frame"] = ((Locator)locator)._frame._channel, + ["selector"] = ((Locator)locator)._selector, + }).ToArray(); + } + + var result = (await SendMessageToServerAsync("screenshot", args).ConfigureAwait(false))?.GetProperty("binary").GetBytesFromBase64(); if (!string.IsNullOrEmpty(options.Path)) { @@ -109,52 +136,71 @@ public async Task ScreenshotAsync(ElementHandleScreenshotOptions options } public Task FillAsync(string value, ElementHandleFillOptions options = default) - => _channel.FillAsync( - value, - noWaitAfter: options?.NoWaitAfter, - force: options?.Force, - timeout: options?.Timeout); + => SendMessageToServerAsync("fill", new Dictionary + { + ["value"] = value, + ["timeout"] = options?.Timeout, + ["force"] = options?.Force, + ["noWaitAfter"] = options?.NoWaitAfter, + }); - public async Task