* Upgrade `playwright-sharp-tool`

https://www.nuget.org/packages/playwright-sharp-tool

Contributes towards https://github.com/dotnet/aspnetcore/issues/30882.

* Update to latest v1.16.1 from GitHub

https://github.com/microsoft/playwright-dotnet/releases/tag/v1.16.1

* Update to use Microsoft.Playwright

* Update references to reflect `Microsoft.Playwright`

* Update dotnet-tools.json

* Upgrade System.CommandLine

* Adapt to breaking API changes in Playwright

* Upgrade dotnet-serve

* Add Microsoft.Playwright.CLI

* Revert "Upgrade System.CommandLine"

This reverts commit cc46e9cfb7.

* Fix build

The build failed. Fix the build errors and run again.
C:\h\w\AA180980\w\9ED808A4\e\RunTests\RunTests.csproj : warning NU1604: Project dependency Microsoft.Playwright does not contain an inclusive lower bound. Include a lower bound in the dependency version to ensure consistent restore results.
C:\h\w\AA180980\w\9ED808A4\e\RunTests\TestRunner.cs(124,35): error CS7036: There is no argument given that corresponds to the required formal parameter 'arguments' of 'ProcessUtil.RunAsync(string, string, string?, string?, bool, IDictionary<string, string?>?, Action<string>?, Action<string>?, Action<int>?, CancellationToken)' [C:\h\w\AA180980\w\9ED808A4\e\RunTests\RunTests.csproj]

* Remove semaphore for websocket events

* Update playwrightSettings based on breaking changes

* npx based playwright install

* npm install playwright

* Install node in helix env

* Use DOTNET_ROLL_FORWARD with `dotnet` `playwright` tool

* Cleanup

* Update playwright tool usage

* DOTNET_ROLL_FORWARD in TestRunner

* Specify project dir

* Playwright install without cli

* Update TestRunner.cs

* Update TestRunner.cs

* Re-enable helix playwright tests

* Upgrade Playwright to 1.17.3, CLI to 1.2.2

* Fix spacing

* Remove DOTNET_ROLL_FORWARD

* PR Feedback

* Remove temp project workaround.

* Fix package lowerbound issue

* Update Versions.props

* Update BrowserManager.cs

* Remove `Installing Microsoft.Playwright.CLI`

* Disable playwright tests in helix
This commit is contained in:
Tanay Parikh 2022-01-11 16:21:28 -08:00 коммит произвёл GitHub
Родитель b4dc2153d7
Коммит 3129207847
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 157 добавлений и 180 удалений

Просмотреть файл

@ -3,15 +3,15 @@
"isRoot": true,
"tools": {
"dotnet-serve": {
"version": "1.7.139",
"version": "1.8.15",
"commands": [
"dotnet-serve"
]
},
"playwright-sharp-tool": {
"version": "0.170.2",
"Microsoft.Playwright.CLI": {
"version": "1.2.2",
"commands": [
"playwright-sharp"
"playwright"
]
}
}

Просмотреть файл

@ -192,7 +192,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="NuGet.Frameworks" />
<LatestPackageReference Include="NuGet.Versioning" />
<LatestPackageReference Include="Photino.NET" />
<LatestPackageReference Include="PlaywrightSharp" />
<LatestPackageReference Include="Microsoft.Playwright" />
<LatestPackageReference Include="Polly" />
<LatestPackageReference Include="Polly.Extensions.Http" />
<LatestPackageReference Include="Selenium.Support" />

Просмотреть файл

@ -260,7 +260,7 @@
<NewtonsoftJsonVersion>13.0.1</NewtonsoftJsonVersion>
<NSwagApiDescriptionClientVersion>13.0.4</NSwagApiDescriptionClientVersion>
<PhotinoNETVersion>1.1.6</PhotinoNETVersion>
<PlaywrightSharpVersion>0.192.0</PlaywrightSharpVersion>
<MicrosoftPlaywrightVersion>1.17.3</MicrosoftPlaywrightVersion>
<PollyExtensionsHttpVersion>3.0.0</PollyExtensionsHttpVersion>
<PollyVersion>7.2.2</PollyVersion>
<SeleniumSupportVersion>4.1.0</SeleniumSupportVersion>

Просмотреть файл

@ -8,6 +8,6 @@
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20158.1" />
<PackageReference Condition=" '$(InstallPlaywright)' == 'true' " Include="PlaywrightSharp" Version="0.192.0" />
<PackageReference Condition=" '$(InstallPlaywright)' == 'true' " Include="Microsoft.Playwright" Version="1.17.3" />
</ItemGroup>
</Project>

Просмотреть файл

@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
#if INSTALLPLAYWRIGHT
using PlaywrightSharp;
using Microsoft.Playwright;
#endif
namespace RunTests
@ -55,9 +55,6 @@ namespace RunTests
var playwrightBrowsers = Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH");
Console.WriteLine($"Setting PLAYWRIGHT_BROWSERS_PATH: {playwrightBrowsers}");
EnvironmentVariables.Add("PLAYWRIGHT_BROWSERS_PATH", playwrightBrowsers);
var playrightDriver = Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH");
Console.WriteLine($"Setting PLAYWRIGHT_DRIVER_PATH: {playrightDriver}");
EnvironmentVariables.Add("PLAYWRIGHT_DRIVER_PATH", playrightDriver);
#else
Console.WriteLine($"Skipping setting PLAYWRIGHT_BROWSERS_PATH");
#endif
@ -112,8 +109,10 @@ namespace RunTests
{
try
{
Console.WriteLine($"Installing Playwright to Browsers: {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")} Driver: {Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH")}");
await Playwright.InstallAsync(Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"), Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH"));
Console.WriteLine($"Installing Playwright Browsers to {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")}");
var exitCode = Microsoft.Playwright.Program.Main(new[] { "install" });
DisplayContents(Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"));
return true;
}

Просмотреть файл

@ -16,7 +16,6 @@ set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
set DOTNET_MULTILEVEL_LOOKUP=0
set InstallPlaywright=%$installPlaywright%
set PLAYWRIGHT_BROWSERS_PATH=%CD%\ms-playwright
set PLAYWRIGHT_DRIVER_PATH=%CD%\.playwright\win-x64\native\playwright.cmd
set "PATH=%HELIX_WORKITEM_ROOT%;%PATH%;%HELIX_WORKITEM_ROOT%\node\bin"
echo Set path to: "%PATH%"

Просмотреть файл

@ -18,7 +18,6 @@ export PATH="$PATH:$DIR:$DIR/node/bin"
# Set playwright stuff
export PLAYWRIGHT_BROWSERS_PATH="$DIR/ms-playwright"
if [[ "$helixQueue" == *"OSX"* ]]; then
export PLAYWRIGHT_DRIVER_PATH="$DIR/.playwright/osx/native/playwright.sh"
PLAYWRIGHT_NODE_PATH=$DIR/.playwright/osx/native/node
else
export PLAYWRIGHT_DRIVER_PATH="$DIR/.playwright/unix/native/playwright.sh"

Просмотреть файл

@ -8,7 +8,7 @@ using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.BrowserTesting;
using Microsoft.AspNetCore.Testing;
using PlaywrightSharp;
using Microsoft.Playwright;
using ProjectTemplates.Tests.Infrastructure;
using Templates.Test.Helpers;
using Xunit;
@ -136,20 +136,31 @@ public class BlazorServerTemplateTest : BlazorTemplateTest
private async Task TestBasicNavigation(Project project, IPage page)
{
var socket = BrowserContextInfo.Pages[page].WebSockets.SingleOrDefault() ??
(await page.WaitForEventAsync(PageEvent.WebSocket)).WebSocket;
var socket = await page.WaitForWebSocketAsync();
var framesReceived = 0;
var framesSent = 0;
void FrameReceived(object sender, IWebSocketFrame frame) { framesReceived++; }
void FrameSent(object sender, IWebSocketFrame frame) { framesSent++; }
socket.FrameReceived += FrameReceived;
socket.FrameSent += FrameSent;
// Receive render batch
await socket.WaitForEventAsync(WebSocketEvent.FrameReceived);
await socket.WaitForEventAsync(WebSocketEvent.FrameSent);
await page.WaitForWebSocketAsync(new() { Predicate = (s) => framesReceived == 1 });
await page.WaitForWebSocketAsync(new() { Predicate = (s) => framesSent == 1 });
// JS interop call to intercept navigation
await socket.WaitForEventAsync(WebSocketEvent.FrameReceived);
await socket.WaitForEventAsync(WebSocketEvent.FrameSent);
await page.WaitForWebSocketAsync(new() { Predicate = (s) => framesReceived == 2 });
await page.WaitForWebSocketAsync(new() { Predicate = (s) => framesSent == 2 });
socket.FrameReceived -= FrameReceived;
socket.FrameSent -= FrameSent;
await page.WaitForSelectorAsync("nav");
// <title> element gets project ID injected into it during template execution
Assert.Equal("Index", (await page.GetTitleAsync()).Trim());
Assert.Equal("Index", (await page.TitleAsync()).Trim());
// Initially displays the home page
await page.WaitForSelectorAsync("h1 >> text=Hello, world!");
@ -168,7 +179,7 @@ public class BlazorServerTemplateTest : BlazorTemplateTest
// Asynchronously loads and displays the table of weather forecasts
await page.WaitForSelectorAsync("table>tbody>tr");
Assert.Equal(5, (await page.QuerySelectorAllAsync("p+table>tbody>tr")).Count());
Assert.Equal(5, await page.Locator("p+table>tbody>tr").CountAsync());
}
[Theory]

Просмотреть файл

@ -11,7 +11,7 @@
<!-- https://github.com/dotnet/aspnetcore/issues/38818 -->
<BuildHelixPayload>false</BuildHelixPayload>
<!-- Properties that affect test runs -->
<!-- TestTemplateCreationFolder is the folder where the templates will be created. Will point out to $(OutputDir)$(TestTemplateCreationFolder) -->
<TestTemplateCreationFolder>TestTemplates\</TestTemplateCreationFolder>
@ -47,8 +47,8 @@
<Reference Include="Microsoft.Extensions.Configuration.Json" />
<Reference Include="AngleSharp" />
<Reference Include="System.Net.Http" />
<Reference Include="PlaywrightSharp" Condition="'$(IsPlaywrightAvailable)' == 'true'" />
<Reference Include="PlaywrightSharp" ExcludeAssets="build" Condition="'$(IsPlaywrightAvailable)' != 'true'" />
<Reference Include="Microsoft.Playwright" Condition="'$(IsPlaywrightAvailable)' == 'true'" />
<Reference Include="Microsoft.Playwright" ExcludeAssets="build" Condition="'$(IsPlaywrightAvailable)' != 'true'" />
<ProjectReference Include="$(RepoRoot)src\Framework\App.Runtime\src\Microsoft.AspNetCore.App.Runtime.csproj"
Private="false"
ReferenceOutputAssembly="false"

Просмотреть файл

@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json.Linq;
using PlaywrightSharp;
using Microsoft.Playwright;
using Templates.Test.Helpers;
namespace BlazorTemplates.Tests;
@ -56,7 +56,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
private static async Task<IPage> NavigateToPage(IBrowserContext browser, string listeningUri)
{
var page = await browser.NewPageAsync();
await page.GoToAsync(listeningUri, LifecycleEvent.Networkidle);
await page.GotoAsync(listeningUri, new() { WaitUntil = WaitUntilState.NetworkIdle });
return page;
}
@ -133,9 +133,9 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
// The PWA template supports offline use. By now, the browser should have cached everything it needs,
// so we can continue working even without the server.
await page.GoToAsync("about:blank");
await page.GotoAsync("about:blank");
await browser.SetOfflineAsync(true);
await page.GoToAsync(listeningUri);
await page.GotoAsync(listeningUri);
await TestBasicNavigation(project.ProjectName, page, skipFetchData: true);
await page.CloseAsync();
}
@ -181,9 +181,9 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
// The PWA template supports offline use. By now, the browser should have cached everything it needs,
// so we can continue working even without the server.
// Since this is the hosted project, backend APIs won't work offline, so we need to skip "fetchdata"
await page.GoToAsync("about:blank");
await page.GotoAsync("about:blank");
await browser.SetOfflineAsync(true);
await page.GoToAsync(listeningUri);
await page.GotoAsync(listeningUri);
await TestBasicNavigation(project.ProjectName, page, skipFetchData: true);
await page.CloseAsync();
}
@ -416,11 +416,11 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
// Initially displays the home page
await page.WaitForSelectorAsync("h1 >> text=Hello, world!");
Assert.Equal("Index", (await page.GetTitleAsync()).Trim());
Assert.Equal("Index", (await page.TitleAsync()).Trim());
// Can navigate to the counter page
await Task.WhenAll(
page.WaitForNavigationAsync("**/counter"),
page.WaitForNavigationAsync(new() { UrlString = "**/counter" }),
page.WaitForSelectorAsync("h1 >> text=Counter"),
page.WaitForSelectorAsync("p >> text=Current count: 0"),
page.ClickAsync("a[href=counter]"));
@ -433,12 +433,12 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
if (usesAuth)
{
await Task.WhenAll(
page.WaitForNavigationAsync("**/Identity/Account/Login**", LifecycleEvent.Networkidle),
page.WaitForNavigationAsync(new() { UrlString = "**/Identity/Account/Login**", WaitUntil = WaitUntilState.NetworkIdle }),
page.ClickAsync("text=Log in"));
await Task.WhenAll(
page.WaitForSelectorAsync("[name=\"Input.Email\"]"),
page.WaitForNavigationAsync("**/Identity/Account/Register**", LifecycleEvent.Networkidle),
page.WaitForNavigationAsync(new() { UrlString = "**/Identity/Account/Register**", WaitUntil = WaitUntilState.NetworkIdle }),
page.ClickAsync("text=Register as a new user"));
var userName = $"{Guid.NewGuid()}@example.com";
@ -450,12 +450,12 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
// We will be redirected to the RegisterConfirmation
await Task.WhenAll(
page.WaitForNavigationAsync("**/Identity/Account/RegisterConfirmation**", LifecycleEvent.Networkidle),
page.WaitForNavigationAsync(new() { UrlString = "**/Identity/Account/RegisterConfirmation**", WaitUntil = WaitUntilState.NetworkIdle }),
page.ClickAsync("#registerSubmit"));
// We will be redirected to the ConfirmEmail
await Task.WhenAll(
page.WaitForNavigationAsync("**/Identity/Account/ConfirmEmail**", LifecycleEvent.Networkidle),
page.WaitForNavigationAsync(new() { UrlString = "**/Identity/Account/ConfirmEmail**", WaitUntil = WaitUntilState.NetworkIdle }),
page.ClickAsync("text=Click here to confirm your account"));
// Now we can login
@ -466,21 +466,21 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest
await page.ClickAsync("#login-submit");
// Need to navigate to fetch page
await page.GoToAsync(new Uri(page.Url).GetLeftPart(UriPartial.Authority));
Assert.Equal(appName.Trim(), (await page.GetTitleAsync()).Trim());
await page.GotoAsync(new Uri(page.Url).GetLeftPart(UriPartial.Authority));
Assert.Equal(appName.Trim(), (await page.TitleAsync()).Trim());
}
if (!skipFetchData)
{
// Can navigate to the 'fetch data' page
await Task.WhenAll(
page.WaitForNavigationAsync("**/fetchdata"),
page.WaitForNavigationAsync(new() { UrlString = "**/fetchdata" }),
page.WaitForSelectorAsync("h1 >> text=Weather forecast"),
page.ClickAsync("text=Fetch data"));
// Asynchronously loads and displays the table of weather forecasts
await page.WaitForSelectorAsync("table>tbody>tr");
Assert.Equal(5, (await page.QuerySelectorAllAsync("p+table>tbody>tr")).Count());
Assert.Equal(5, await page.Locator("p+table>tbody>tr").CountAsync());
}
}

Просмотреть файл

@ -4,27 +4,18 @@
"BaseArtifactsFolder": ".",
"GlobalBrowserOptions": {
"ChromiumSandbox": true,
"DumpIO": true,
"IgnoreHTTPSErrors": true,
"Headless": true,
"Timeout": 30000
},
"GlobalContextOptions": {
"RecordVideo": {
"Dir": "videos"
},
"RecordHar": {
"Path": "har"
},
"RecordVideoDir": "videos",
"RecordHarPath": "har",
"IgnoreHTTPSErrors": true
},
"BrowserOptions": {
"Chromium": {
"BrowserKind": "Chromium",
"IsEnabled": true,
"Args": {
"--ignore-certificate-errors": true
}
"IsEnabled": true
},
"Firefox": {
"BrowserKind": "Firefox",

Просмотреть файл

@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using PlaywrightSharp;
using Microsoft.Playwright;
using Xunit;
using Xunit.Abstractions;
@ -109,7 +109,7 @@ public class AspNetProcess : IDisposable
public async Task VisitInBrowserAsync(IPage page)
{
_output.WriteLine($"Opening browser at {ListeningUri}...");
await page.GoToAsync(ListeningUri.AbsoluteUri);
await page.GotoAsync(ListeningUri.AbsoluteUri);
}

Просмотреть файл

@ -9,7 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using PlaywrightSharp;
using Microsoft.Playwright;
namespace Microsoft.AspNetCore.BrowserTesting;
@ -47,20 +47,12 @@ public class BrowserManager
async Task InitializeCore()
{
var driverPath = Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH");
if (!string.IsNullOrEmpty(driverPath))
{
Playwright = await PlaywrightSharp.Playwright.CreateAsync(_loggerFactory, driverExecutablePath: driverPath, debug: "pw:api");
}
else
{
Playwright = await PlaywrightSharp.Playwright.CreateAsync(_loggerFactory, debug: "pw:api");
}
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
foreach (var (browserName, options) in _browserManagerConfiguration.BrowserOptions)
{
if (!_launchBrowsers.ContainsKey(browserName))
{
var effectiveLaunchOptions = _browserManagerConfiguration.GetLaunchOptions(options.BrowserLaunchOptions);
var effectiveLaunchOptions = _browserManagerConfiguration.GetBrowserTypeLaunchOptions(options.BrowserLaunchOptions);
var browser = options.BrowserKind switch
{
@ -108,10 +100,10 @@ public class BrowserManager
contextInfo);
}
public Task<IBrowserContext> GetBrowserInstance(BrowserKind browserInstance, string contextName, BrowserContextOptions options, ContextInformation contextInfo) =>
public Task<IBrowserContext> GetBrowserInstance(BrowserKind browserInstance, string contextName, BrowserNewContextOptions options, ContextInformation contextInfo) =>
GetBrowserInstance(browserInstance.ToString(), contextName, options, contextInfo);
public Task<IBrowserContext> GetBrowserInstance(string browserInstance, string contextName, BrowserContextOptions options, ContextInformation contextInfo)
public Task<IBrowserContext> GetBrowserInstance(string browserInstance, string contextName, BrowserNewContextOptions options, ContextInformation contextInfo)
{
if (_launchBrowsers.TryGetValue(browserInstance, out var browser))
{
@ -126,9 +118,10 @@ public class BrowserManager
private async Task<IBrowserContext> AttachContextInfo(Task<IBrowserContext> browserContextTask, ContextInformation contextInfo)
{
var context = await browserContextTask;
context.DefaultTimeout = HasFailedTests ?
var defaultTimeout = HasFailedTests ?
_browserManagerConfiguration.TimeoutAfterFirstFailureInMilliseconds :
_browserManagerConfiguration.TimeoutInMilliseconds;
context.SetDefaultTimeout(defaultTimeout);
contextInfo.Attach(context);
return context;

Просмотреть файл

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using PlaywrightSharp;
using Microsoft.Playwright;
namespace Microsoft.AspNetCore.BrowserTesting;
@ -25,9 +25,9 @@ public class BrowserManagerConfiguration
public bool IsDisabled { get; private set; }
public LaunchOptions GlobalBrowserOptions { get; private set; }
public BrowserTypeLaunchOptions GlobalBrowserOptions { get; private set; }
public BrowserContextOptions GlobalContextOptions { get; private set; }
public BrowserNewContextOptions GlobalContextOptions { get; private set; }
public IDictionary<string, BrowserOptions> BrowserOptions { get; } =
new Dictionary<string, BrowserOptions>(StringComparer.OrdinalIgnoreCase);
@ -35,10 +35,10 @@ public class BrowserManagerConfiguration
public ISet<string> DisabledBrowsers { get; } =
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public IDictionary<string, BrowserContextOptions> ContextOptions { get; private set; } =
new Dictionary<string, BrowserContextOptions>(StringComparer.OrdinalIgnoreCase);
public IDictionary<string, BrowserNewContextOptions> ContextOptions { get; private set; } =
new Dictionary<string, BrowserNewContextOptions>(StringComparer.OrdinalIgnoreCase);
public LaunchOptions GetLaunchOptions(LaunchOptions browserLaunchOptions)
public BrowserTypeLaunchOptions GetBrowserTypeLaunchOptions(BrowserTypeLaunchOptions browserLaunchOptions)
{
if (browserLaunchOptions == null)
{
@ -50,7 +50,7 @@ public class BrowserManagerConfiguration
}
}
public BrowserContextOptions GetContextOptions(string browser)
public BrowserNewContextOptions GetContextOptions(string browser)
{
if (!BrowserOptions.TryGetValue(browser, out var browserOptions))
{
@ -66,10 +66,10 @@ public class BrowserManagerConfiguration
}
}
public BrowserContextOptions GetContextOptions(string browser, string contextName) =>
public BrowserNewContextOptions GetContextOptions(string browser, string contextName) =>
Combine(GetContextOptions(browser.ToString()), ContextOptions.TryGetValue(contextName, out var context) ? context : throw new InvalidOperationException("Invalid context name"));
public BrowserContextOptions GetContextOptions(string browser, string contextName, BrowserContextOptions options) =>
public BrowserNewContextOptions GetContextOptions(string browser, string contextName, BrowserNewContextOptions options) =>
Combine(GetContextOptions(browser, contextName), options);
private void Load(IConfiguration configuration)
@ -126,37 +126,37 @@ public class BrowserManagerConfiguration
}
}
private BrowserContextOptions LoadContextOptions(IConfiguration configuration) => EnsureFoldersExist(new BrowserContextOptions
private BrowserNewContextOptions LoadContextOptions(IConfiguration configuration) => EnsureFoldersExist(new BrowserNewContextOptions
{
Proxy = BindValue<ProxySettings>(configuration, nameof(BrowserContextOptions.Proxy)),
RecordVideo = BindValue<RecordVideoOptions>(configuration, nameof(BrowserContextOptions.RecordVideo)),
RecordHar = BindValue<RecordHarOptions>(configuration, nameof(BrowserContextOptions.RecordHar)),
Proxy = BindValue<Proxy>(configuration, nameof(BrowserNewContextOptions.Proxy)),
RecordVideoDir = configuration.GetValue<string>(nameof(BrowserNewContextOptions.RecordVideoDir)),
RecordVideoSize = BindValue<RecordVideoSize>(configuration, nameof(BrowserNewContextOptions.RecordVideoSize)),
RecordHarPath = configuration.GetValue<string>(nameof(BrowserNewContextOptions.RecordHarPath)),
RecordHarOmitContent = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.RecordHarOmitContent)),
ExtraHTTPHeaders = BindMultiValueMap(
configuration.GetSection(nameof(BrowserContextOptions.ExtraHTTPHeaders)),
configuration.GetSection(nameof(BrowserNewContextOptions.ExtraHTTPHeaders)),
argsMap => argsMap.ToDictionary(kvp => kvp.Key, kvp => string.Join(", ", kvp.Value))),
Locale = configuration.GetValue<string>(nameof(BrowserContextOptions.Locale)),
ColorScheme = configuration.GetValue<ColorScheme?>(nameof(BrowserContextOptions.ColorScheme)),
AcceptDownloads = configuration.GetValue<bool?>(nameof(BrowserContextOptions.AcceptDownloads)),
HasTouch = configuration.GetValue<bool?>(nameof(BrowserContextOptions.HasTouch)),
HttpCredentials = configuration.GetValue<Credentials>(nameof(BrowserContextOptions.HttpCredentials)),
DeviceScaleFactor = configuration.GetValue<decimal?>(nameof(BrowserContextOptions.DeviceScaleFactor)),
Offline = configuration.GetValue<bool?>(nameof(BrowserContextOptions.Offline)),
IsMobile = configuration.GetValue<bool?>(nameof(BrowserContextOptions.IsMobile)),
Locale = configuration.GetValue<string>(nameof(BrowserNewContextOptions.Locale)),
ColorScheme = configuration.GetValue<ColorScheme?>(nameof(BrowserNewContextOptions.ColorScheme)),
AcceptDownloads = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.AcceptDownloads)),
HasTouch = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.HasTouch)),
HttpCredentials = configuration.GetValue<HttpCredentials>(nameof(BrowserNewContextOptions.HttpCredentials)),
DeviceScaleFactor = configuration.GetValue<float?>(nameof(BrowserNewContextOptions.DeviceScaleFactor)),
Offline = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.Offline)),
IsMobile = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.IsMobile)),
// TODO: Map this properly
Permissions = configuration.GetValue<ContextPermission[]>(nameof(BrowserContextOptions.Permissions)),
Permissions = configuration.GetValue<IEnumerable<string>>(nameof(BrowserNewContextOptions.Permissions)),
Geolocation = BindValue<Geolocation>(configuration, nameof(BrowserContextOptions.Geolocation)),
TimezoneId = configuration.GetValue<string>(nameof(BrowserContextOptions.TimezoneId)),
IgnoreHTTPSErrors = configuration.GetValue<bool?>(nameof(BrowserContextOptions.IgnoreHTTPSErrors)),
JavaScriptEnabled = configuration.GetValue<bool?>(nameof(BrowserContextOptions.JavaScriptEnabled)),
BypassCSP = configuration.GetValue<bool?>(nameof(BrowserContextOptions.BypassCSP)),
UserAgent = configuration.GetValue<string>(nameof(BrowserContextOptions.UserAgent)),
Viewport = BindValue<ViewportSize>(configuration, nameof(BrowserContextOptions.Viewport)),
StorageStatePath = configuration.GetValue<string>(nameof(BrowserContextOptions.StorageStatePath)),
// TODO: Map this properly
StorageState = BindValue<StorageState>(configuration, nameof(BrowserContextOptions.StorageState))
Geolocation = BindValue<Geolocation>(configuration, nameof(BrowserNewContextOptions.Geolocation)),
TimezoneId = configuration.GetValue<string>(nameof(BrowserNewContextOptions.TimezoneId)),
IgnoreHTTPSErrors = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.IgnoreHTTPSErrors)),
JavaScriptEnabled = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.JavaScriptEnabled)),
BypassCSP = configuration.GetValue<bool?>(nameof(BrowserNewContextOptions.BypassCSP)),
UserAgent = configuration.GetValue<string>(nameof(BrowserNewContextOptions.UserAgent)),
ViewportSize = BindValue<ViewportSize>(configuration, nameof(BrowserNewContextOptions.ViewportSize)),
StorageStatePath = configuration.GetValue<string>(nameof(BrowserNewContextOptions.StorageStatePath)),
StorageState = configuration.GetValue<string>(nameof(BrowserNewContextOptions.StorageState))
});
private static T BindValue<T>(IConfiguration configuration, string key) where T : new()
@ -167,16 +167,16 @@ public class BrowserManagerConfiguration
return section.Exists() ? instance : default;
}
private BrowserContextOptions EnsureFoldersExist(BrowserContextOptions browserContextOptions)
private BrowserNewContextOptions EnsureFoldersExist(BrowserNewContextOptions browserContextOptions)
{
if (browserContextOptions?.RecordVideo?.Dir != null)
if (browserContextOptions?.RecordVideoDir != null)
{
browserContextOptions.RecordVideo.Dir = EnsureFolderExists(browserContextOptions.RecordVideo.Dir);
browserContextOptions.RecordVideoDir = EnsureFolderExists(browserContextOptions.RecordVideoDir);
}
if (browserContextOptions?.RecordHar?.Path != null)
if (browserContextOptions?.RecordHarPath != null)
{
browserContextOptions.RecordHar.Path = EnsureFolderExists(browserContextOptions.RecordHar.Path);
browserContextOptions.RecordHarPath = EnsureFolderExists(browserContextOptions.RecordHarPath);
}
return browserContextOptions;
@ -197,28 +197,25 @@ public class BrowserManagerConfiguration
}
}
private static LaunchOptions LoadBrowserLaunchOptions(IConfiguration configuration) => new LaunchOptions
private static BrowserTypeLaunchOptions LoadBrowserLaunchOptions(IConfiguration configuration) => new BrowserTypeLaunchOptions
{
IgnoreDefaultArgs = BindArgumentMap(configuration.GetSection(nameof(LaunchOptions.IgnoreAllDefaultArgs))),
ChromiumSandbox = configuration.GetValue<bool?>(nameof(LaunchOptions.ChromiumSandbox)),
HandleSIGHUP = configuration.GetValue<bool?>(nameof(LaunchOptions.HandleSIGHUP)),
HandleSIGTERM = configuration.GetValue<bool?>(nameof(LaunchOptions.HandleSIGTERM)),
HandleSIGINT = configuration.GetValue<bool?>(nameof(LaunchOptions.HandleSIGINT)),
IgnoreAllDefaultArgs = configuration.GetValue<bool?>(nameof(LaunchOptions.IgnoreAllDefaultArgs)),
SlowMo = configuration.GetValue<int?>(nameof(LaunchOptions.SlowMo)),
Env = configuration.GetValue<Dictionary<string, string>>(nameof(LaunchOptions.Env)),
DumpIO = configuration.GetValue<bool?>(nameof(LaunchOptions.DumpIO)),
IgnoreHTTPSErrors = configuration.GetValue<bool?>(nameof(LaunchOptions.IgnoreHTTPSErrors)),
DownloadsPath = configuration.GetValue<string>(nameof(LaunchOptions.DownloadsPath)),
ExecutablePath = configuration.GetValue<string>(nameof(LaunchOptions.ExecutablePath)),
Devtools = configuration.GetValue<bool?>(nameof(LaunchOptions.Devtools)),
UserDataDir = configuration.GetValue<string>(nameof(LaunchOptions.UserDataDir)),
IgnoreDefaultArgs = BindArgumentMap(configuration.GetSection(nameof(BrowserTypeLaunchOptions.IgnoreAllDefaultArgs))),
ChromiumSandbox = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.ChromiumSandbox)),
HandleSIGHUP = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.HandleSIGHUP)),
HandleSIGTERM = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.HandleSIGTERM)),
HandleSIGINT = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.HandleSIGINT)),
IgnoreAllDefaultArgs = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.IgnoreAllDefaultArgs)),
SlowMo = configuration.GetValue<int?>(nameof(BrowserTypeLaunchOptions.SlowMo)),
Env = configuration.GetValue<Dictionary<string, string>>(nameof(BrowserTypeLaunchOptions.Env)),
DownloadsPath = configuration.GetValue<string>(nameof(BrowserTypeLaunchOptions.DownloadsPath)),
ExecutablePath = configuration.GetValue<string>(nameof(BrowserTypeLaunchOptions.ExecutablePath)),
Devtools = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.Devtools)),
Args = BindMultiValueMap(
configuration.GetSection(nameof(LaunchOptions.Args)),
configuration.GetSection(nameof(BrowserTypeLaunchOptions.Args)),
argsMap => argsMap.SelectMany(argNameValue => argNameValue.Value.Prepend(argNameValue.Key)).ToArray()),
Headless = configuration.GetValue<bool?>(nameof(LaunchOptions.Headless)),
Timeout = configuration.GetValue<int?>(nameof(LaunchOptions.Timeout)),
Proxy = configuration.GetValue<ProxySettings>(nameof(LaunchOptions.Proxy))
Headless = configuration.GetValue<bool?>(nameof(BrowserTypeLaunchOptions.Headless)),
Timeout = configuration.GetValue<int?>(nameof(BrowserTypeLaunchOptions.Timeout)),
Proxy = configuration.GetValue<Proxy>(nameof(BrowserTypeLaunchOptions.Proxy))
};
private static T BindMultiValueMap<T>(IConfigurationSection processArgsMap, Func<Dictionary<string, HashSet<string>>, T> mapper)
@ -307,20 +304,14 @@ public class BrowserManagerConfiguration
true => configuration.Get<Dictionary<string, bool>>().Where(kvp => kvp.Value == true).Select(kvp => kvp.Key).ToArray()
};
private static BrowserContextOptions Combine(BrowserContextOptions defaultOptions, BrowserContextOptions overrideOptions) =>
private static BrowserNewContextOptions Combine(BrowserNewContextOptions defaultOptions, BrowserNewContextOptions overrideOptions) =>
new()
{
Proxy = overrideOptions?.Proxy != default ? overrideOptions.Proxy : defaultOptions.Proxy,
RecordVideo = overrideOptions?.RecordVideo != default ?
new() { Dir = overrideOptions.RecordVideo.Dir, Size = overrideOptions.RecordVideo.Size?.Clone() } :
defaultOptions != default ?
new() { Dir = defaultOptions.RecordVideo.Dir, Size = defaultOptions.RecordVideo.Size?.Clone() } :
default,
RecordHar = overrideOptions?.RecordHar != default ?
new() { Path = overrideOptions.RecordHar.Path, OmitContent = overrideOptions.RecordHar.OmitContent } :
defaultOptions?.RecordHar != default ?
new() { Path = defaultOptions.RecordHar.Path, OmitContent = defaultOptions.RecordHar.OmitContent } :
default,
RecordVideoDir = overrideOptions?.RecordVideoDir != default ? overrideOptions.RecordVideoDir : defaultOptions.RecordVideoDir,
RecordVideoSize = overrideOptions?.RecordVideoSize != default ? overrideOptions.RecordVideoSize : defaultOptions.RecordVideoSize,
RecordHarPath = overrideOptions?.RecordHarPath != default ? overrideOptions.RecordHarPath : defaultOptions.RecordHarPath,
RecordHarOmitContent = overrideOptions?.RecordHarOmitContent != default ? overrideOptions.RecordHarOmitContent : defaultOptions.RecordHarOmitContent,
ExtraHTTPHeaders = overrideOptions?.ExtraHTTPHeaders != default ? overrideOptions.ExtraHTTPHeaders : defaultOptions.ExtraHTTPHeaders,
Locale = overrideOptions?.Locale != default ? overrideOptions.Locale : defaultOptions.Locale,
ColorScheme = overrideOptions?.ColorScheme != default ? overrideOptions.ColorScheme : defaultOptions.ColorScheme,
@ -337,12 +328,12 @@ public class BrowserManagerConfiguration
JavaScriptEnabled = overrideOptions?.JavaScriptEnabled != default ? overrideOptions.JavaScriptEnabled : defaultOptions.JavaScriptEnabled,
BypassCSP = overrideOptions?.BypassCSP != default ? overrideOptions.BypassCSP : defaultOptions.BypassCSP,
UserAgent = overrideOptions?.UserAgent != default ? overrideOptions.UserAgent : defaultOptions.UserAgent,
Viewport = overrideOptions?.Viewport != default ? overrideOptions.Viewport : defaultOptions.Viewport,
ViewportSize = overrideOptions?.ViewportSize != default ? overrideOptions.ViewportSize : defaultOptions.ViewportSize,
StorageStatePath = overrideOptions?.StorageStatePath != default ? overrideOptions.StorageStatePath : defaultOptions.StorageStatePath,
StorageState = overrideOptions?.StorageState != default ? overrideOptions.StorageState : defaultOptions.StorageState
};
private static LaunchOptions Combine(LaunchOptions defaultOptions, LaunchOptions overrideOptions) =>
private static BrowserTypeLaunchOptions Combine(BrowserTypeLaunchOptions defaultOptions, BrowserTypeLaunchOptions overrideOptions) =>
new()
{
IgnoreDefaultArgs = overrideOptions.IgnoreDefaultArgs != default ? overrideOptions.IgnoreDefaultArgs : defaultOptions.IgnoreDefaultArgs,
@ -353,12 +344,9 @@ public class BrowserManagerConfiguration
IgnoreAllDefaultArgs = overrideOptions.IgnoreAllDefaultArgs != default ? overrideOptions.IgnoreAllDefaultArgs : defaultOptions.IgnoreAllDefaultArgs,
SlowMo = overrideOptions.SlowMo != default ? overrideOptions.SlowMo : defaultOptions.SlowMo,
Env = overrideOptions.Env != default ? overrideOptions.Env : defaultOptions.Env,
DumpIO = overrideOptions.DumpIO != default ? overrideOptions.DumpIO : defaultOptions.DumpIO,
IgnoreHTTPSErrors = overrideOptions.IgnoreHTTPSErrors != default ? overrideOptions.IgnoreHTTPSErrors : defaultOptions.IgnoreHTTPSErrors,
DownloadsPath = overrideOptions.DownloadsPath != default ? overrideOptions.DownloadsPath : defaultOptions.DownloadsPath,
ExecutablePath = overrideOptions.ExecutablePath != default ? overrideOptions.ExecutablePath : defaultOptions.ExecutablePath,
Devtools = overrideOptions.Devtools != default ? overrideOptions.Devtools : defaultOptions.Devtools,
UserDataDir = overrideOptions.UserDataDir != default ? overrideOptions.UserDataDir : defaultOptions.UserDataDir,
Args = overrideOptions.Args != default ? overrideOptions.Args : defaultOptions.Args,
Headless = overrideOptions.Headless != default ? overrideOptions.Headless : defaultOptions.Headless,
Timeout = overrideOptions.Timeout != default ? overrideOptions.Timeout : defaultOptions.Timeout,
@ -366,4 +354,4 @@ public class BrowserManagerConfiguration
};
}
public record BrowserOptions(BrowserKind BrowserKind, LaunchOptions BrowserLaunchOptions, BrowserContextOptions DefaultContextOptions);
public record BrowserOptions(BrowserKind BrowserKind, BrowserTypeLaunchOptions BrowserLaunchOptions, BrowserNewContextOptions DefaultContextOptions);

Просмотреть файл

@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.BrowserTesting;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Configuration;
using PlaywrightSharp;
using Microsoft.Playwright;
using Xunit;
using Xunit.Abstractions;

Просмотреть файл

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Logging;
using PlaywrightSharp;
using Microsoft.Playwright;
namespace Microsoft.AspNetCore.BrowserTesting;
@ -26,7 +26,7 @@ public class ContextInformation
context.Page += AttachToPage;
}
private void AttachToPage(object sender, PageEventArgs args)
private void AttachToPage(object sender, IPage page)
{
var logger = _factory.CreateLogger<PageInformation>();
if (_harPath != null)
@ -34,15 +34,14 @@ public class ContextInformation
logger.LogInformation($"Network trace will be saved at '{_harPath}'");
}
var pageInfo = new PageInformation(args.Page, logger);
Pages.Add(args.Page, pageInfo);
args.Page.Close += CleanupPage;
args.Page.Crash += CleanupPage;
var pageInfo = new PageInformation(page, logger);
Pages.Add(page, pageInfo);
page.Close += CleanupPage;
page.Crash += CleanupPage;
}
private void CleanupPage(object sender, EventArgs e)
private void CleanupPage(object sender, IPage page)
{
var page = (IPage)sender;
if (Pages.TryGetValue(page, out var info))
{
info.Dispose();
@ -50,23 +49,23 @@ public class ContextInformation
}
}
internal BrowserContextOptions ConfigureUniqueHarPath(BrowserContextOptions browserContextOptions)
internal BrowserNewContextOptions ConfigureUniqueHarPath(BrowserNewContextOptions browserContextOptions)
{
var uploadDir = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT");
if (browserContextOptions?.RecordHar?.Path != null)
if (browserContextOptions?.RecordHarPath != null)
{
var identifier = Guid.NewGuid().ToString("N");
browserContextOptions.RecordHar.Path = Path.Combine(
string.IsNullOrEmpty(uploadDir) ? browserContextOptions.RecordHar.Path : uploadDir,
browserContextOptions.RecordHarPath = Path.Combine(
string.IsNullOrEmpty(uploadDir) ? browserContextOptions.RecordHarPath : uploadDir,
$"{identifier}.har");
_harPath = browserContextOptions.RecordHar.Path;
_harPath = browserContextOptions.RecordHarPath;
}
if (browserContextOptions?.RecordVideo?.Dir != null)
if (browserContextOptions?.RecordVideoDir != null)
{
if (!string.IsNullOrEmpty(uploadDir))
{
browserContextOptions.RecordVideo.Dir = uploadDir;
browserContextOptions.RecordVideoDir = uploadDir;
}
}

Просмотреть файл

@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="PlaywrightSharp" Condition="'$(IsPlaywrightAvailable)' == 'true'" />
<Reference Include="PlaywrightSharp" ExcludeAssets="build" Condition="'$(IsPlaywrightAvailable)' != 'true'" />
<Reference Include="Microsoft.Playwright" Condition="'$(IsPlaywrightAvailable)' == 'true'" />
<Reference Include="Microsoft.Playwright" ExcludeAssets="build" Condition="'$(IsPlaywrightAvailable)' != 'true'" />
<Reference Include="Microsoft.AspNetCore.Testing" />
</ItemGroup>

Просмотреть файл

@ -5,13 +5,13 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PlaywrightSharp;
using Microsoft.Playwright;
namespace Microsoft.AspNetCore.BrowserTesting;
public class PageInformation : IDisposable
{
private readonly Page _page;
private readonly IPage _page;
private readonly ILogger<PageInformation> _logger;
public List<string> FailedRequests { get; } = new();
@ -22,7 +22,7 @@ public class PageInformation : IDisposable
public List<IWebSocket> WebSockets { get; set; } = new();
public PageInformation(Page page, ILogger<PageInformation> logger)
public PageInformation(IPage page, ILogger<PageInformation> logger)
{
page.Console += RecordConsoleMessage;
page.PageError += RecordPageError;
@ -34,16 +34,16 @@ public class PageInformation : IDisposable
_ = LogPageVideoPath();
}
private void CaptureWebSocket(object sender, WebSocketEventArgs e)
private void CaptureWebSocket(object sender, IWebSocket e)
{
WebSockets.Add(e.WebSocket);
WebSockets.Add(e);
}
private async Task LogPageVideoPath()
{
try
{
var path = _page.Video != null ? await _page.Video.GetPathAsync() : null;
var path = _page.Video != null ? await _page.Video.PathAsync() : null;
if (path != null)
{
_logger.LogInformation($"Page video recorded at: {path}");
@ -63,41 +63,40 @@ public class PageInformation : IDisposable
_page.RequestFailed -= RecordFailedRequest;
}
private void RecordFailedRequest(object sender, RequestFailedEventArgs e)
private void RecordFailedRequest(object sender, IRequest e)
{
try
{
_logger.LogError(e.FailureText);
_logger.LogError(e.Failure);
}
catch
{
}
FailedRequests.Add(e.FailureText);
FailedRequests.Add(e.Failure);
}
private void RecordPageError(object sender, PageErrorEventArgs e)
private void RecordPageError(object sender, string e)
{
// There needs to be a bit of experimentation with this, but message should be a good start.
try
{
_logger.LogError(e.Message);
_logger.LogError(e);
}
catch
{
}
PageErrors.Add(e.Message);
PageErrors.Add(e);
}
private void RecordConsoleMessage(object sender, ConsoleEventArgs e)
private void RecordConsoleMessage(object sender, IConsoleMessage message)
{
try
{
var message = e.Message;
var messageText = message.Text.Replace(Environment.NewLine, $"{Environment.NewLine} ");
var location = message.Location;
var logMessage = $"[{_page.Url}]{Environment.NewLine} {messageText}{Environment.NewLine} ({location.URL}:{location.LineNumber}:{location.ColumnNumber})";
var logMessage = $"[{_page.Url}]{Environment.NewLine} {messageText}{Environment.NewLine} ({location})";
_logger.Log(MapLogLevel(message.Type), logMessage);

Просмотреть файл

@ -48,7 +48,6 @@ internal class ProcessEx : IDisposable
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
// We greedily create a timeout exception message even though a timeout is unlikely to happen for two reasons:
// 1. To make it less likely for Process getters to throw exceptions like "System.InvalidOperationException: Process has exited, ..."
// 2. To ensure if/when exceptions are thrown from Process getters, these exceptions can easily be observed.