Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser] Migrate more Blazor features, prepare JavaScript API for Blazor cleanup #87959

Merged
merged 64 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b0f2999
Import functions from blazor
maraf Jun 8, 2023
ba25c1f
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jun 21, 2023
7dd3d71
Satellite assembly callback
maraf Jun 21, 2023
1c4081c
Lazy loading callback
maraf Jun 21, 2023
e3f8887
Fix imports. Use locateFile
maraf Jun 22, 2023
e8aa02c
Interop for lazy loading and satellite
maraf Jun 22, 2023
54b2c90
Dissolve startupOptions
maraf Jun 23, 2023
968829d
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jun 28, 2023
12ecc3d
WBT test for lazy loading
maraf Jun 28, 2023
b44c755
WBT for satellite loading.
maraf Jun 28, 2023
b792851
Refactor tests
maraf Jun 30, 2023
3a48997
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jun 30, 2023
e860e40
Fix checking is lazy assembly was already loaded
maraf Jun 30, 2023
3ceaf94
Introduce WasmDebugLevel to Wasm SDK.
maraf Jun 30, 2023
3fe3fc4
bootConfig.aspnetCoreBrowserTools -> env:__ASPNETCORE_BROWSER_TOOLS
maraf Jul 3, 2023
1347c2d
Alternatives for withStartupOptions
maraf Jul 3, 2023
919f238
Import JS intializers
maraf Jul 3, 2023
1e8ef5d
Pass runtime API to onRuntimeReady library initializer
maraf Jul 3, 2023
2c73ca2
Library initializer test
maraf Jul 3, 2023
3d17711
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jul 3, 2023
8fbb58f
Ensure loadedAssemblies has value
maraf Jul 3, 2023
3e8575d
Register new WBT for CI run
maraf Jul 3, 2023
be9831f
Fix resolving URL when baseURI contains a query string
maraf Jul 4, 2023
5c329b5
Resources in mono config
maraf Jul 4, 2023
cc65ac9
Unify GlobalizationMode and ICUDataMode
maraf Jul 4, 2023
6789edd
Include extensions in monoConfig
maraf Jul 4, 2023
37b50fa
Generate dotnet.d.ts
maraf Jul 4, 2023
9b2a5d4
Load libraryInitializers early and add onRuntimeConfigLoaded
maraf Jul 4, 2023
d8c1b93
Fix test
maraf Jul 4, 2023
1a90ef1
Fix application environment. Add tests
maraf Jul 4, 2023
89fa18d
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jul 7, 2023
4e0b921
Drop .NET 8.0 condition on default value for WasmDebugLevel
maraf Jul 7, 2023
b1aa977
Use funcs from loaderHelpers
maraf Jul 7, 2023
fa9dbbe
Check document and document.baseURI
maraf Jul 7, 2023
3a3ab7a
Licence headers
maraf Jul 7, 2023
b0fe374
Comments in tests
maraf Jul 7, 2023
eab8e45
File scoped namespace
maraf Jul 7, 2023
81a7474
Drop export for BootJsonData and ICUDataMode
maraf Jul 7, 2023
5f8b211
Update comment
maraf Jul 7, 2023
6a40be5
Remove extra text from test
maraf Jul 7, 2023
101af8e
Fix no-prototype-builtins
maraf Jul 7, 2023
bbfea47
Comment with marshaled signature
maraf Jul 7, 2023
d6939a9
Failed lazy load test. Fix double set result on error
maraf Jul 7, 2023
a33e227
Catch and log errors from library intializers
maraf Jul 7, 2023
975ae2f
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jul 10, 2023
e67550f
Split library initializers by required download place
maraf Jul 10, 2023
ce9b2cc
Fix build
maraf Jul 11, 2023
2680a37
Missing end lines
maraf Jul 11, 2023
0c3db1c
Fix boot json generator for new library initializers schema
maraf Jul 11, 2023
77ea58e
Make way to override content check for library initializer load place
maraf Jul 11, 2023
9af8dfc
Drop duplicate initialization of loaderHelpers.loadedAssemblies
maraf Jul 11, 2023
42d982d
Feedback
maraf Jul 11, 2023
872bc57
Move libraryInitializers and loadBootResource to loaderHelpers
maraf Jul 11, 2023
cd52d28
Abort startup on library initializer error. Replace getLibraryInitial…
maraf Jul 12, 2023
39732dc
Replace toAbsoluteBaseUri with locateFile
maraf Jul 12, 2023
88ceb2a
Update WBT
maraf Jul 12, 2023
72661c2
Export invokeLibraryInitializers early so onConfigLoaded callbacks ca…
maraf Jul 12, 2023
8d4656b
Use config fileName when checking file name
maraf Jul 12, 2023
5d895db
Export GlobalizationMode
maraf Jul 12, 2023
0d62773
Feedback
maraf Jul 12, 2023
9722b6b
Fix URL polyfill usage in locateFile
maraf Jul 13, 2023
e8b282d
Pass TargetFrameworkVersion to the GenerateWasmBootJson instead of bo…
maraf Jul 13, 2023
b2b5308
Merge remote-tracking branch 'upstream/main' into WasmSatelliteLazyLoad
maraf Jul 13, 2023
934e7e4
Backward compatibility (1)
maraf Jul 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ Wasm.Build.Tests.WasmNativeDefaultsTests
Wasm.Build.Tests.WasmRunOutOfAppBundleTests
Wasm.Build.Tests.WasmSIMDTests
Wasm.Build.Tests.WasmTemplateTests
Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests
Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests
Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.InteropServices" />
<Reference Include="System.Runtime.Loader" />
maraf marked this conversation as resolved.
Show resolved Hide resolved
<Reference Include="System.Threading" />
<Reference Include="System.Threading.Thread" />
<Reference Include="System.Threading.Channels" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
Expand Down Expand Up @@ -93,6 +95,41 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
}
}

public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
try
{
arg_1.ToManaged(out byte[]? dllBytes);
arg_2.ToManaged(out byte[]? pdbBytes);

if (dllBytes != null)
JSHostImplementation.LoadLazyAssembly(dllBytes, pdbBytes);
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
}

public static void LoadSatelliteAssembly(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0];
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
try
{
arg_1.ToManaged(out byte[]? dllBytes);

if (dllBytes != null)
JSHostImplementation.LoadSatelliteAssembly(dllBytes);
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
}

// The JS layer invokes this method when the JS wrapper for a JS owned object
// has been collected by the JS garbage collector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,10 @@ internal static unsafe partial class JavaScriptImports
public static partial JSObject GetDotnetInstance();
[JSImport("INTERNAL.dynamic_import")]
public static partial Task<JSObject> DynamicImport(string moduleName, string moduleUrl);

#if DEBUG
[JSImport("globalThis.console.log")]
public static partial void Log([JSMarshalAs<JSType.String>] string message);
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;

namespace System.Runtime.InteropServices.JavaScript
{
Expand Down Expand Up @@ -198,6 +200,21 @@ public static JSObject CreateCSOwnedProxy(nint jsHandle)
return res;
}

[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes)
{
if (pdbBytes == null)
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
else
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes));
}

[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
public static void LoadSatelliteAssembly(byte[] dllBytes)
{
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
}

#if FEATURE_WASM_THREADS
public static void InstallWebWorkerInterop(bool installJSSynchronizationContext, bool isMainThread)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ Copyright (c) .NET Foundation. All rights reserved.

<Target Name="_ResolveWasmConfiguration" DependsOnTargets="_ResolveGlobalizationConfiguration">
<PropertyGroup>
<_TargetingNET80OrLater>$([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '8.0'))</_TargetingNET80OrLater>
<_TargetingNET80OrLater>false</_TargetingNET80OrLater>
<_TargetingNET80OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '8.0'))">true</_TargetingNET80OrLater>

<_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport)</_BlazorEnableTimeZoneSupport>
<_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true</_BlazorEnableTimeZoneSupport>
Expand All @@ -180,11 +181,14 @@ Copyright (c) .NET Foundation. All rights reserved.
<_WasmEnableThreads Condition="'$(_WasmEnableThreads)' == ''">false</_WasmEnableThreads>

<_WasmEnableWebcil>$(WasmEnableWebcil)</_WasmEnableWebcil>
<_WasmEnableWebcil Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp' or '$(_TargetingNET80OrLater)' != 'true'">false</_WasmEnableWebcil>
<_WasmEnableWebcil Condition="'$(_TargetingNET80OrLater)' != 'true'">false</_WasmEnableWebcil>
<_WasmEnableWebcil Condition="'$(_WasmEnableWebcil)' == ''">true</_WasmEnableWebcil>
<_BlazorWebAssemblyStartupMemoryCache>$(BlazorWebAssemblyStartupMemoryCache)</_BlazorWebAssemblyStartupMemoryCache>
<_BlazorWebAssemblyJiterpreter>$(BlazorWebAssemblyJiterpreter)</_BlazorWebAssemblyJiterpreter>
<_BlazorWebAssemblyRuntimeOptions>$(BlazorWebAssemblyRuntimeOptions)</_BlazorWebAssemblyRuntimeOptions>
<_WasmDebugLevel>$(WasmDebugLevel)</_WasmDebugLevel>
<_WasmDebugLevel Condition="'$(_WasmDebugLevel)' == ''">0</_WasmDebugLevel>
<_WasmDebugLevel Condition="'$(_WasmDebugLevel)' == '0' and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</_WasmDebugLevel>

<!-- Workaround for https://github.com/dotnet/sdk/issues/12114-->
<PublishDir Condition="'$(AppendRuntimeIdentifierToOutputPath)' != 'true' AND '$(PublishDir)' == '$(OutputPath)$(RuntimeIdentifier)\$(PublishDirName)\'">$(OutputPath)$(PublishDirName)\</PublishDir>
Expand Down Expand Up @@ -343,6 +347,7 @@ Copyright (c) .NET Foundation. All rights reserved.
AssemblyPath="@(IntermediateAssembly)"
Resources="@(_WasmOutputWithHash)"
DebugBuild="true"
DebugLevel="$(_WasmDebugLevel)"
LinkerEnabled="false"
CacheBootResources="$(BlazorCacheBootResources)"
OutputPath="$(_WasmBuildBootJsonPath)"
Expand All @@ -355,7 +360,10 @@ Copyright (c) .NET Foundation. All rights reserved.
StartupMemoryCache="$(_BlazorWebAssemblyStartupMemoryCache)"
Jiterpreter="$(_BlazorWebAssemblyJiterpreter)"
RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)"
Extensions="@(WasmBootConfigExtension)" />
Extensions="@(WasmBootConfigExtension)"
TargetingNET80OrLater="$(_TargetingNET80OrLater)"
LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)"
LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" />

<ItemGroup>
<FileWrites Include="$(_WasmBuildBootJsonPath)" />
Expand Down Expand Up @@ -530,6 +538,7 @@ Copyright (c) .NET Foundation. All rights reserved.
AssemblyPath="@(IntermediateAssembly)"
Resources="@(_WasmPublishBootResourceWithHash)"
DebugBuild="false"
DebugLevel="$(_WasmDebugLevel)"
LinkerEnabled="$(PublishTrimmed)"
CacheBootResources="$(BlazorCacheBootResources)"
OutputPath="$(IntermediateOutputPath)blazor.publish.boot.json"
Expand All @@ -542,7 +551,10 @@ Copyright (c) .NET Foundation. All rights reserved.
StartupMemoryCache="$(_BlazorWebAssemblyStartupMemoryCache)"
Jiterpreter="$(_BlazorWebAssemblyJiterpreter)"
RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)"
Extensions="@(WasmBootConfigExtension)" />
Extensions="@(WasmBootConfigExtension)"
TargetingNET80OrLater="$(_TargetingNET80OrLater)"
LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)"
LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" />

<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)blazor.publish.boot.json" />
Expand Down
8 changes: 6 additions & 2 deletions src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal class BrowserRunner : IAsyncDisposable
public BrowserRunner(ITestOutputHelper testOutput) => _testOutput = testOutput;

// FIXME: options
public async Task<IPage> RunAsync(ToolCommand cmd, string args, bool headless = true, Action<IConsoleMessage>? onConsoleMessage = null)
public async Task<IPage> RunAsync(ToolCommand cmd, string args, bool headless = true, Action<IConsoleMessage>? onConsoleMessage = null, Func<string, string>? modifyBrowserUrl = null)
{
TaskCompletionSource<string> urlAvailable = new();
Action<string?> outputHandler = msg =>
Expand Down Expand Up @@ -89,10 +89,14 @@ public async Task<IPage> RunAsync(ToolCommand cmd, string args, bool headless =
Args = chromeArgs
});

string browserUrl = urlAvailable.Task.Result;
if (modifyBrowserUrl != null)
browserUrl = modifyBrowserUrl(browserUrl);

IPage page = await Browser.NewPageAsync();
if (onConsoleMessage is not null)
page.Console += (_, msg) => onConsoleMessage(msg);
await page.GotoAsync(urlAvailable.Task.Result);
await page.GotoAsync(browserUrl);
RunTask = runTask;
return page;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

#nullable enable

namespace Wasm.Build.Tests.TestAppScenarios;

public class AppSettingsTests : AppTestBase
{
public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}

[Theory]
[InlineData("Development")]
[InlineData("Production")]
public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment)
{
CopyTestAsset("WasmBasicTestApp", "AppSettingsTests");
PublishProject("Debug");

var result = await RunSdkStyleApp(new(
Configuration: "Debug",
ForPublish: true,
TestScenario: "AppSettingsTest",
BrowserQueryString: new Dictionary<string, string> { ["applicationEnvironment"] = applicationEnvironment }
));
Assert.Collection(
result.TestOutput,
m => Assert.Equal(GetFileExistenceMessage("/appsettings.json", true), m),
m => Assert.Equal(GetFileExistenceMessage("/appsettings.Development.json", applicationEnvironment == "Development"), m),
m => Assert.Equal(GetFileExistenceMessage("/appsettings.Production.json", applicationEnvironment == "Production"), m)
);
}

// Synchronize with AppSettingsTest
private static string GetFileExistenceMessage(string path, bool expected) => $"'{path}' exists '{expected}'";
}
133 changes: 133 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Authentication.ExtendedProtection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright;
using Xunit.Abstractions;

namespace Wasm.Build.Tests.TestAppScenarios;

public abstract class AppTestBase : BuildTestBase
{
protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}

protected string Id { get; set; }
protected string LogPath { get; set; }

protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null)
{
Id = $"{generatedProjectNamePrefix ?? assetName}_{Path.GetRandomFileName()}";
InitBlazorWasmProjectDir(Id);

LogPath = Path.Combine(s_buildEnv.LogRootPath, Id);
Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!));
}

protected void BuildProject(string configuration)
{
CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("build", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}");
result.EnsureSuccessful();
}

protected void PublishProject(string configuration)
{
CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("publish", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}");
result.EnsureSuccessful();
}

protected string GetBinLogFilePath(string suffix = null)
{
if (!string.IsNullOrEmpty(suffix))
suffix = "_" + suffix;

return Path.Combine(LogPath, $"{Id}{suffix}.binlog");
}

protected ToolCommand CreateDotNetCommand() => new DotNetCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(_projectDir!)
.WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir);

protected async Task<RunResult> RunSdkStyleApp(RunOptions options)
{
string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files";
string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), ".."));

using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(workingDirectory);

var tcs = new TaskCompletionSource<int>();

List<string> testOutput = new();
List<string> consoleOutput = new();
Regex exitRegex = new Regex("WASM EXIT (?<exitCode>[0-9]+)$");

await using var runner = new BrowserRunner(_testOutput);

IPage page = null;

string queryString = "?test=" + options.TestScenario;
if (options.BrowserQueryString != null)
queryString += "&" + string.Join("&", options.BrowserQueryString.Select(kvp => $"{kvp.Key}={kvp.Value}"));

page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + queryString);

void OnConsoleMessage(IConsoleMessage msg)
{
if (EnvironmentVariables.ShowBuildOutput)
Console.WriteLine($"[{msg.Type}] {msg.Text}");

_testOutput.WriteLine($"[{msg.Type}] {msg.Text}");
consoleOutput.Add(msg.Text);

const string testOutputPrefix = "TestOutput -> ";
if (msg.Text.StartsWith(testOutputPrefix))
testOutput.Add(msg.Text.Substring(testOutputPrefix.Length));

var exitMatch = exitRegex.Match(msg.Text);
if (exitMatch.Success)
tcs.TrySetResult(int.Parse(exitMatch.Groups["exitCode"].Value));

if (msg.Text.StartsWith("Error: Missing test scenario"))
throw new Exception(msg.Text);

if (options.OnConsoleMessage != null)
options.OnConsoleMessage(msg, page);
}

TimeSpan timeout = TimeSpan.FromMinutes(2);
await Task.WhenAny(tcs.Task, Task.Delay(timeout));
if (!tcs.Task.IsCompleted)
throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit");

int wasmExitCode = tcs.Task.Result;
if (options.ExpectedExitCode != null && wasmExitCode != options.ExpectedExitCode)
throw new Exception($"Expected exit code {options.ExpectedExitCode} but got {wasmExitCode}");

return new(wasmExitCode, testOutput, consoleOutput);
}

protected record RunOptions(
string Configuration,
string TestScenario,
Dictionary<string, string> BrowserQueryString = null,
bool ForPublish = false,
Action<IConsoleMessage, IPage> OnConsoleMessage = null,
int? ExpectedExitCode = 0
);

protected record RunResult(
int ExitCode,
IReadOnlyCollection<string> TestOutput,
IReadOnlyCollection<string> ConsoleOutput
);
}
Loading