Run Playwright-dotnet automation tests on LambdaTest cloud grid
Perform automation testing on 3000+ real desktop and mobile devices online.
/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Playwright.Core;
using NUnit.Framework;
namespace Microsoft.Playwright.Tests
{
public sealed class BrowsercontextStorageStateTests : PageTestEx
{
[PlaywrightTest("browsercontext-storage-state.spec.ts", "should capture local storage")]
public async Task ShouldCaptureLocalStorage()
{
var page1 = await Context.NewPageAsync();
await page1.RouteAsync("**/*", (route) =>
{
route.FulfillAsync(new() { Body = "<html></html>" });
});
await page1.GotoAsync("https://www.example.com");
await page1.EvaluateAsync(@"() =>
{
localStorage['name1'] = 'value1';
}");
await page1.GotoAsync("https://www.domain.com");
await page1.EvaluateAsync(@"() =>
{
localStorage['name2'] = 'value2';
}");
string storage = await Context.StorageStateAsync();
// TODO: think about IVT-in the StorageState and serializing
string expected = @"{""cookies"":[],""origins"":[{""origin"":""https://www.example.com"",""localStorage"":[{""name"":""name1"",""value"":""value1""}]},{""origin"":""https://www.domain.com"",""localStorage"":[{""name"":""name2"",""value"":""value2""}]}]}";
Assert.AreEqual(expected, storage);
}
[PlaywrightTest("browsercontext-storage-state.spec.ts", "should set local storage")]
public async Task ShouldSetLocalStorage()
{
var context = await Browser.NewContextAsync(new()
{
StorageState = "{\"cookies\":[],\"origins\":[{\"origin\":\"https://www.example.com\",\"localStorage\":[{\"name\":\"name1\",\"value\":\"value1\"}]}]}",
});
var page = await context.NewPageAsync();
await page.RouteAsync("**/*", (route) =>
{
route.FulfillAsync(new() { Body = "<html></html>" });
});
await page.GotoAsync("https://www.example.com");
var localStorage = await page.EvaluateAsync<string[]>("Object.keys(window.localStorage)");
Assert.AreEqual(localStorage, new string[] { "name1" });
var name1Value = await page.EvaluateAsync<string>("window.localStorage.getItem('name1')");
Assert.AreEqual(name1Value, "value1");
}
[PlaywrightTest("browsercontext-storage-state.spec.ts", "should round-trip through the file")]
public async Task ShouldRoundTripThroughTheFile()
{
var page1 = await Context.NewPageAsync();
await page1.RouteAsync("**/*", (route) =>
{
route.FulfillAsync(new() { Body = "<html></html>" });
});
await page1.GotoAsync("https://www.example.com");
await page1.EvaluateAsync(@"() =>
{
localStorage['name1'] = 'value1';
document.cookie = 'username=John Doe';
}");
using var tempDir = new TempDirectory();
string path = Path.Combine(tempDir.Path, "storage-state.json");
string storage = await Context.StorageStateAsync(new() { Path = path });
Assert.AreEqual(storage, File.ReadAllText(path));
await using var context = await Browser.NewContextAsync(new() { StorageStatePath = path });
var page2 = await context.NewPageAsync();
await page2.RouteAsync("**/*", (route) =>
{
route.FulfillAsync(new() { Body = "<html></html>" });
});
await page2.GotoAsync("https://www.example.com");
Assert.AreEqual("value1", await page2.EvaluateAsync<string>("localStorage['name1']"));
Assert.AreEqual("username=John Doe", await page2.EvaluateAsync<string>("document.cookie"));
}
[PlaywrightTest("browsercontext-storage-state.spec.ts", "should capture cookies")]
public async Task ShouldCaptureCookies()
{
Server.SetRoute("/setcookie.html", context =>
{
context.Response.Cookies.Append("a", "b");
context.Response.Cookies.Append("empty", "");
return Task.CompletedTask;
});
await Page.GotoAsync(Server.Prefix + "/setcookie.html");
CollectionAssert.AreEqual(new[] { "a=b", "empty=" }, await Page.EvaluateAsync<string[]>(@"() =>
{
const cookies = document.cookie.split(';');
return cookies.map(cookie => cookie.trim()).sort();
}"));
var storageState = await Context.StorageStateAsync();
StringAssert.Contains(@"""name"":""a"",""value"":""b""", storageState);
StringAssert.Contains(@"""name"":""empty"",""value"":""""", storageState);
if (TestConstants.IsWebKit || TestConstants.IsFirefox)
{
StringAssert.Contains(@"""sameSite"":""None""", storageState);
}
else
{
StringAssert.Contains(@"""sameSite"":""Lax""", storageState);
}
StringAssert.DoesNotContain(@"""url"":null", storageState);
await using var context2 = await Browser.NewContextAsync(new() { StorageState = storageState });
var page2 = await context2.NewPageAsync();
await page2.GotoAsync(Server.EmptyPage);
CollectionAssert.AreEqual(new[] { "a=b", "empty=" }, await page2.EvaluateAsync<string[]>(@"() =>
{
const cookies = document.cookie.split(';');
return cookies.map(cookie => cookie.trim()).sort();
}"));
}
}
}
/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Playwright.Core;
using Microsoft.Playwright.Transport.Protocol;
namespace Microsoft.Playwright.Transport.Channels
{
internal class BrowserChannel : Channel<Browser>
{
public BrowserChannel(string guid, Connection connection, Browser owner) : base(guid, connection, owner)
{
}
internal event EventHandler Closed;
internal override void OnMessage(string method, JsonElement? serverParams)
{
switch (method)
{
case "close":
Closed?.Invoke(this, EventArgs.Empty);
break;
}
}
internal Task<BrowserContextChannel> NewContextAsync(
bool? acceptDownloads = null,
bool? bypassCSP = null,
ColorScheme? colorScheme = null,
ReducedMotion? reducedMotion = null,
ForcedColors? forcedColors = null,
float? deviceScaleFactor = null,
IEnumerable<KeyValuePair<string, string>> extraHTTPHeaders = null,
Geolocation geolocation = null,
bool? hasTouch = null,
HttpCredentials httpCredentials = null,
bool? ignoreHTTPSErrors = null,
bool? isMobile = null,
bool? javaScriptEnabled = null,
string locale = null,
bool? offline = null,
IEnumerable<string> permissions = null,
Proxy proxy = null,
bool? recordHarOmitContent = null,
string recordHarPath = null,
Dictionary<string, object> recordVideo = null,
string storageState = null,
string storageStatePath = null,
string timezoneId = null,
string userAgent = null,
ViewportSize viewportSize = default,
ScreenSize screenSize = default,
string baseUrl = default,
bool? strictSelectors = default)
{
var args = new Dictionary<string, object>
{
{ "acceptDownloads", acceptDownloads },
{ "bypassCSP", bypassCSP },
{ "colorScheme", colorScheme },
{ "reducedMotion", reducedMotion },
{ "deviceScaleFactor", deviceScaleFactor },
};
if (extraHTTPHeaders != null)
{
args["extraHTTPHeaders"] = extraHTTPHeaders.Select(kv => new HeaderEntry { Name = kv.Key, Value = kv.Value }).ToArray();
}
args.Add("geolocation", geolocation);
args.Add("hasTouch", hasTouch);
args.Add("httpCredentials", httpCredentials);
args.Add("ignoreHTTPSErrors", ignoreHTTPSErrors);
args.Add("isMobile", isMobile);
args.Add("javaScriptEnabled", javaScriptEnabled);
args.Add("locale", locale);
args.Add("offline", offline);
args.Add("permissions", permissions);
args.Add("proxy", proxy);
args.Add("strictSelectors", strictSelectors);
args.Add("forcedColors", forcedColors);
if (!string.IsNullOrEmpty(recordHarPath))
{
args.Add("recordHar", new
{
Path = recordHarPath,
OmitContent = recordHarOmitContent,
});
}
if (recordVideo != null)
{
args.Add("recordVideo", recordVideo);
}
if (!string.IsNullOrEmpty(storageStatePath))
{
if (!File.Exists(storageStatePath))
{
throw new PlaywrightException($"The specified storage state file does not exist: {storageStatePath}");
}
storageState = File.ReadAllText(storageStatePath);
}
if (!string.IsNullOrEmpty(storageState))
{
args.Add("storageState", JsonSerializer.Deserialize<StorageState>(storageState, Helpers.JsonExtensions.DefaultJsonSerializerOptions));
}
args.Add("timezoneId", timezoneId);
args.Add("userAgent", userAgent);
if (viewportSize?.Width == -1)
{
args.Add("noDefaultViewport", true);
}
else
{
args.Add("viewport", viewportSize);
args.Add("screen", screenSize);
}
args.Add("baseURL", baseUrl);
return Connection.SendMessageToServerAsync<BrowserContextChannel>(
Guid,
"newContext",
args);
}
internal Task CloseAsync() => Connection.SendMessageToServerAsync<BrowserContextChannel>(Guid, "close", null);
internal Task StartTracingAsync(IPage page, bool screenshots, string path, IEnumerable<string> categories)
{
var args = new Dictionary<string, object>
{
["screenshots"] = screenshots,
["path"] = path,
["page"] = page,
["categories"] = categories,
};
return Connection.SendMessageToServerAsync(Guid, "crStartTracing", args);
}
internal async Task<string> StopTracingAsync()
=> (await Connection.SendMessageToServerAsync(Guid, "crStopTracing", null).ConfigureAwait(false))?.GetProperty("binary").ToString();
}
}
/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Playwright.Transport;
using Microsoft.Playwright.Transport.Channels;
using Microsoft.Playwright.Transport.Protocol;
namespace Microsoft.Playwright.Core
{
internal class Browser : ChannelOwnerBase, IChannelOwner<Browser>, IBrowser
{
private readonly BrowserInitializer _initializer;
private readonly TaskCompletionSource<bool> _closedTcs = new();
internal Browser(IChannelOwner parent, string guid, BrowserInitializer initializer) : base(parent, guid)
{
Channel = new(guid, parent.Connection, this);
IsConnected = true;
Channel.Closed += (_, _) => DidClose();
_initializer = initializer;
}
public event EventHandler<IBrowser> Disconnected;
ChannelBase IChannelOwner.Channel => Channel;
IChannel<Browser> IChannelOwner<Browser>.Channel => Channel;
public IReadOnlyList<IBrowserContext> Contexts => BrowserContextsList.ToArray();
public bool IsConnected { get; private set; }
internal bool ShouldCloseConnectionOnClose { get; set; }
public string Version => _initializer.Version;
internal BrowserChannel Channel { get; }
internal List<BrowserContext> BrowserContextsList { get; } = new();
internal LocalUtils LocalUtils { get; set; }
public async Task CloseAsync()
{
try
{
if (ShouldCloseConnectionOnClose)
{
Channel.Connection.DoClose(DriverMessages.BrowserClosedExceptionMessage);
}
else
{
await Channel.CloseAsync().ConfigureAwait(false);
}
await _closedTcs.Task.ConfigureAwait(false);
}
catch (Exception e) when (DriverMessages.IsSafeCloseError(e))
{
// Swallow exception
}
}
public async Task<IBrowserContext> 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,
recordHarOmitContent: options.RecordHarOmitContent,
recordHarPath: options.RecordHarPath,
recordVideo: GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize),
storageState: options.StorageState,
storageStatePath: options.StorageStatePath,
timezoneId: options.TimezoneId,
userAgent: options.UserAgent,
viewportSize: options.ViewportSize,
screenSize: options.ScreenSize,
baseUrl: options.BaseURL,
strictSelectors: options.StrictSelectors,
forcedColors: options.ForcedColors).ConfigureAwait(false)).Object;
context.Options = options;
((Tracing)context.Tracing).LocalUtils = LocalUtils;
BrowserContextsList.Add(context);
return context;
}
public async Task<IPage> NewPageAsync(BrowserNewPageOptions options = default)
{
options ??= new();
var contextOptions = new BrowserNewContextOptions()
{
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,
ForcedColors = options.ForcedColors,
RecordHarPath = options.RecordHarPath,
RecordHarOmitContent = options.RecordHarOmitContent,
RecordVideoDir = options.RecordVideoDir,
RecordVideoSize = options.RecordVideoSize,
Proxy = options.Proxy,
StorageState = options.StorageState,
StorageStatePath = options.StorageStatePath,
BaseURL = options.BaseURL,
StrictSelectors = options.StrictSelectors,
};
var context = (BrowserContext)await NewContextAsync(contextOptions).ConfigureAwait(false);
var page = (Page)await context.NewPageAsync().ConfigureAwait(false);
page.OwnedContext = context;
context.Options = contextOptions;
context.OwnerPage = page;
return page;
}
public ValueTask DisposeAsync() => new ValueTask(CloseAsync());
internal static Dictionary<string, object> GetVideoArgs(string recordVideoDir, RecordVideoSize recordVideoSize)
{
Dictionary<string, object> recordVideoArgs = null;
if (recordVideoSize != null && string.IsNullOrEmpty(recordVideoDir))
{
throw new PlaywrightException("\"RecordVideoSize\" option requires \"RecordVideoDir\" to be specified");
}
if (!string.IsNullOrEmpty(recordVideoDir))
{
recordVideoArgs = new()
{
{ "dir", recordVideoDir },
};
if (recordVideoSize != null)
{
recordVideoArgs["size"] = recordVideoSize;
}
}
return recordVideoArgs;
}
internal void DidClose()
{
IsConnected = false;
Disconnected?.Invoke(this, this);
_closedTcs.TrySetResult(true);
}
}
}
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.