You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
EXPERIMENT / PROTOTYPE ONLY — this pull request exists for design validation. It is not a shipping commitment, does not establish a supported package or API, and must not be interpreted as a product roadmap promise.
Pull request
Summary
This prototype adds an optional Microsoft.Testing.Platform.Browser package that runs pure-managed browser-WASM MTP applications through dotnet test, following dotnet/sdk#55906. It builds on the authenticated HTTP transport from #10143 and single-threaded browser/WASI TRX support from #10324 while keeping browser dependencies and servicing cadence out of core MTP.
A project opts in with a Microsoft.Testing.Platform.Browser package reference and an explicit Chromium-family executable. MTP remains authoritative for discovery, execution, filtering, progress/results, cancellation, and artifact production. The optional package owns browser/page/host lifecycle only; the browser app sends results and artifacts directly to the SDK HTTP gateway.
Design and decision record
The maintained cross-repository design discussion is dotnet/sdk#55906. The implementation boundary is:
The package uses Playwright's private stdio protocol and never opens an unauthenticated TCP DevTools endpoint. Bootstrap bearer data is expanded from the SDK-owned response file in memory and is not placed in URLs or logs.
xHarness was investigated but not reused at runtime. Its Selenium/WebDriver server, URL query arguments, DOM tests_done, and console/result parsing conflict with direct MTP HTTP reporting. Its browser startup, container flags, retry, timeout, and cleanup experience informed this implementation.
Protocol additions are capability-gated and backward compatible:
protocol 1.5.0 handshake properties advertise HTTP control and artifact limits;
existing control serializers are reused over a second authenticated HTTP client;
artifact payloads use bounded raw TLV chunks with SDK-owned paths and quotas;
missing transfer capability remains compatibility-warning behavior, while a negotiated transfer failure is terminal.
BrowserTestingPackages.slnf is the conventional package-only build graph: eight package roots plus the non-packable MSTest.GlobalConfigsGenerator build dependency. Acceptance is built separately at evaluated NetCurrent. Package version lookups in AcceptanceTestBase are lazy, so reduced feeds do not fail on unrelated absent packages; missing or duplicate packages still fail exactly when requested.
CI uses existing capacity-backed public pools:
Windows: windows.vs2026preview.scout.amd64.open, required by the repo SDK's MSBuild 18.7 minimum and UWP package assets;
Linux: build.ubuntu.2204.amd64.open, with the SDK-owned CI-only InstallBrowser.sh prerequisite (fingerprint-verified Google signed APT repository when Chrome is absent).
No system-tool/cache deletion or SDK component omission is used.
The strict composed suite exercises normal run, --list-tests, matching/zero/project filters, managed cancellation (exit 3), intentional test failure aggregation, >1 MiB two-chunk artifact export and byte integrity, quota rejection, package/runtime layout, desktop non-interference, and process cleanup.
Current CI evidence from TestFX build 1632026, testing merge commit f1c7147a236bb16641e85058956d91138a228f1f with SDK 66309ab55fce95ee42e5e2236c02f7371332dbf8:
Linux job: passed, test run 45287224, 11/11 passed, 0 failed/incomplete/not-applicable; Chrome 155.0.8059.39; diagnostic artifact (2,075,499 bytes).
Windows job: passed, test run 45287840, 11/11 passed, 0 failed/incomplete/not-applicable; Edge 153.0.4234.48; diagnostic artifact (2,147,863 bytes).
Both downloaded TestFX artifacts pass the SDK-owned exact-11-case validator, record succeeded=true, and record exit 0 for all six composed commands.
Aligned SDK build 1632034 independently passed its browser jobs: Linux run 45287292 and Windows run 45287874, each 11/11 passed with zero failed/incomplete/not-applicable.
These are four independent green composed jobs: TestFX Windows/Linux and SDK Windows/Linux. No screenshot is included because the product behavior is process/protocol based; the linked TRX runs and narrow diagnostic artifacts are the clearer reproducible evidence.
Validation and regression proof
Focused repository validation:
build.cmd -c Release -pack -projects BrowserTestingPackages.slnf -mt 0 -bl -bln BrowserTestingPackages-fresh.binlog from a new detached checkout at bbbbfd23d4de8ed70a261b52a326804492b4b037: passed, exit 0, 0 warnings/errors. Exactly eight non-symbol shipping nupkgs were produced and every nuspec repository commit matched that exact source SHA.
direct MTP run filtered to AcceptanceTestBaseTests: 1/1 passed, 0 skipped. It proves unused absent packages are not resolved, required absent packages fail with found 0, and duplicate packages fail with found 2.
direct MTP run filtered to BrowserPackage_ContainsPrivateCrossPlatformPlaywrightRuntime while Shipping contained exactly the required eight dev nupkgs: 1/1 passed, 0 skipped; the original 43-package local feed was restored afterward.
BuildDriverTests: 2/2 passed, covering nonzero build.cmd failure propagation/binlog creation and root-wrapper forwarding of -mt 0.
strict shared composed acceptance in TestFX build 1632026: Linux 11/11, Windows 11/11, no skips.
strict shared composed acceptance in SDK build 1632034: Linux 11/11, Windows 11/11, no skips.
The required-test manifest contains 11 exact TRX names and the shared validator requires every name exactly once, all Passed, zero skipped, and a valid TRX. Every generated fixture uses a unique short NuGet cache and compares all eight restored project.assets.json SHA-512 values with the staged nupkg bytes.
Immutable aligned package evidence remains at source b6c7cac92f7eeb788ae026471239ff110207bc24 under artifacts/validation/Microsoft.Testing.Platform.Browser/b6c7cac92-aligned/Shipping; the Browser nupkg SHA-256 is 2D078CB340E45A0E6C1CC5894BFF3148E29218683F36D5D038F24B3C78E67580.
At this body update, the two required browser jobs and all GitHub policy checks are green. The TestFX aggregate build 1632026 still has an unrelated Windows Release leg in progress; Windows Debug, Linux Release, Windows application-model acceptance, and both browser jobs have passed. The SDK aggregate 1632034 likewise still has unrelated platform legs in progress; both browser jobs have passed.
Remaining external release gates are official signing/publication approval, third-party license/SBOM/provenance review, dependency flow/merge of the matching SDK implementation, release-branch/backport decisions, runtime validation before advertising untested OS/architecture combinations, and an identified servicing owner for Playwright/Node/browser cadence. The package remains experimental and is emitted to Shipping only for acceptance/cross-repository validation.
Bundle the private cross-platform Playwright runtime from clean builds, allow launcher runtime roll-forward, and make startup cancellation and teardown reliably close owned browser resources.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve framework-provided ComputeRunArguments host contracts, including quoted empty arguments, while keeping explicit browser host overrides. Replace raw page globals with the documented testingPlatformBrowser contract version 1 and launcher-private completion binding.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep browser URLs on the validated loopback origin, use the invoking SDK muxer, avoid Playwright payload copies in unit tests, coordinate package-owned host assets, and secure the launch-info handoff with private per-run directories and handle-based validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a one-shot, origin-validated fatal-error method to the versioned browser page contract so framework bootstrap and completion negotiation failures terminate the launcher immediately instead of waiting for the completion timeout.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Activate wrapping only for SDK-marked dotnet test invocations with HTTP bootstrap contract version 1, reject browser-inapplicable file and report options, and cover ordinary run, filtered/skipped/failing execution, and multi-target activation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require SDK invocation IDs for per-evaluation launcher configs, clean legacy and invocation-specific files, and link post-launch cancellation into the completion race and cleanup path.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the WebAssembly SDK and package-owned static host assets for the primary browser acceptance model, document the generated managed entry point and direct MTP HTTP transport, and recognize WasmAppHost App url readiness without matching debug URLs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A static module import is resolved before this supervisor body runs. If _framework/dotnet.js is missing or fails to load, neither complete nor reportFatalError is called, so the launcher waits for the full completion timeout (600 seconds by default) instead of reporting the bootstrap failure promptly. Load the module dynamically inside the guarded try and terminate through the versioned fatal-error API when bootstrap fails.
Because this monitor is a background thread, the runtime may terminate it before the delayed process-group kill runs. On parent EOF, cancellation can make the worker's main path return in under ten seconds even when a host/browser cleanup operation failed and left descendants alive; once the last foreground thread exits, this sleeping monitor disappears and the POSIX group is never killed. Coordinate parent-loss cleanup with the worker lifecycle so the worker cannot exit until the fallback group kill has run (while preserving the normal supervisor/worker shutdown handshake).
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 4a728fc83. Added both field-ID types and all constants to the Platform, HangDump, Retry, TrxReport, and Extensions.MSBuild internal API baselines; the tracked projects rebuild cleanly.
A page can close while Chromium remains connected (for example, application code can call window.close()). BrowserRunMonitor observes only _completion, host exit, and browser disconnection, so that case waits for the full 600-second completion timeout instead of failing immediately. Treat the page Close event as a terminal failure just like Crash.
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
If the artifact request failed because the HTTP transport disconnected, forwarding this diagnostic over the same client throws (typically “transport is not connected”) before CreateArtifactTransferExceptionAsync can return. That secondary failure replaces the useful artifact-path/root-cause exception. Treat display forwarding as best-effort here while still propagating caller cancellation.
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Artifact transfer is enabled even when the peer omits its file-count limit, then the host invents a 10,000-file limit. That accepts a partially declared capability and can exceed the SDK's actual quota before failing mid-transfer. Require a positive FileArtifactTransferMaxFileCount alongside the other advertised limits.
Prevent duplicate artifact transfers from concurrent notifications
ConcurrentDictionary.GetOrAdd may run this async value factory more than once for the same path. Concurrent artifact notifications can therefore upload duplicate bytes, consume quota twice, and leave an orphan SDK artifact even though only one task is cached. Cache a Lazy<Task<string>> (or synchronize creation per path) so each path starts exactly one transfer.
Refresh validation evidence and CI status for the current pin
eng/browser-testing/sdk.json:3
The composed-validation pin has moved to df5a595…, while the PR description still claims validation against SDK 5b0e5fbe…, identifies b41d763f0 as the current TestFX head, and says final CI is green. The current PR head is 303a5d8c… and checks are still queued/in progress, so refresh the immutable evidence and CI statement after this exact pin/head completes validation.
Update protocol documentation for version 1.5 capabilities
Protocol 1.5 now implements HTTP reverse control and chunked artifact transfer, but the canonical docs/mstest-runner-protocol/004-protocol-dotnet-test-pipe.md still lists versions only through 1.4, documents serializer IDs only through 14, and calls browser reverse control future work. Update that protocol specification with the new capability keys, serializers, and fallback behavior in this PR.
A page can close without crashing or disconnecting Chromium (for example, via window.close()). That leaves both browserCompletion and WaitForExitAsync pending, so the run reports only after the completion timeout (10 minutes by default) instead of failing immediately. Subscribe to IPage.Close and complete _completion exceptionally, just as the crash handler does.
Update validation evidence for the current commit pairing
eng/browser-testing/sdk.json:3
The validation evidence in the PR description is for SDK commit 5b0e5fbe3a and TestFx head b41d763f0, but this pin is now df5a595… and the current PR head is 303a5d8…; the current composed browser run was still in progress when reviewed. Update the description with evidence for this exact pairing before retaining the “Final CI is green” claim.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The cross-repository protocol and process-lifecycle changes require human review, and artifact caching plus current-head validation evidence remain unresolved.
Update validation claims to match the current source and CI state
eng/browser-testing/sdk.json:3
The PR body’s validation claim no longer matches this pin or the current source state: it says the current head is b41d763f0, the aligned SDK is 5b0e5fbe3a, and final CI is green, but the actual head is 580258bd5a7d..., this file now selects 6868a2467c1f..., and the current Windows/Linux composed checks (build 1631986) are still in progress. The historical b6c7cac92/5b0e5fbe3a evidence therefore does not validate the source and counterpart selected by this revision. Please update the body after the current composed run completes (including current TestFx/SDK SHAs and accessible evidence), or explicitly label the existing results as historical and current-head validation as pending.
Final test-triage resolution — Azure Pipelines build 1632474
Build 1632474 (PR #11305 merge) completed successfully (buildResult: succeeded). Out of 115,385 normalized test records (114,589 passed, 790 skipped), 6 failures were observed, all on the Windows Release leg, all from Windows App Model Diagnostics artifacts. No timeline signals, no diagnostic/crash artifacts, and no slow-test regressions were reported.
Root cause: not test-product defects. Every failure is a self-validating "probe" asset intentionally designed to fail on a first attempt and pass on retry, exercising the Retry/AppContainer-host extension itself:
Deliberately fails attempt 1 via Assert.AreNotEqual keyed on TESTINGPLATFORM_DOTNETTEST_ATTEMPTNUMBER; comment states "This probe deliberately fails the first retry attempt and passes the next one."
Writes a marker file and calls Assert.Fail("The first AppContainer host fails so Retry must activate a second host.") on first run only
Failed → Passed (retry)
NativeTests.Outcome (×4 occurrences, generated from a parameterized test-asset template in PackagedAppNativeDotnetTestTests.cs)
Template computes fail = outcome == "Failed" || (outcome == "Retry" && !marker exists) and calls Assert.IsFalse(fail, "expected host failure") — these are intentional "host failure" simulation scenarios used to validate the native sidecar retry/outcome reporting path
Each later passed on its corresponding retry attempt within the same run
All 6 "failures" have a matching passed retry record in the same build (same sourceFile/retry-directory lineage), confirming expected fail-then-pass design behavior rather than flakiness or regression. No stable product assertion failure, no deterministic race, and no platform-specific defect signature was found. diagnostics.json is empty and timeline.compact.json has no records, so there is no crash/hang or infrastructure signal to pursue.
Conclusion: No actionable test defect. No issue created — these are expected-by-design retry/host-failure test probes validating the MSTest/MTP Retry extension and native-sidecar outcome-reporting path, not genuine regressions.
🤖 Automated content by GitHub Copilot. Generated by the Pipeline Test Triage workflow. · copilot · auto · 49.3 AIC · ⌖ 11.2 AIC · ⊞ 9.9K
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
EXPERIMENT / PROTOTYPE ONLY — this pull request exists for design validation. It is not a shipping commitment, does not establish a supported package or API, and must not be interpreted as a product roadmap promise.
Pull request
Summary
This prototype adds an optional
Microsoft.Testing.Platform.Browserpackage that runs pure-managed browser-WASM MTP applications throughdotnet test, following dotnet/sdk#55906. It builds on the authenticated HTTP transport from #10143 and single-threaded browser/WASI TRX support from #10324 while keeping browser dependencies and servicing cadence out of core MTP.Current aligned source state:
c81b63af69e367a826f614f5728e9f0c50d576fa66309ab55fce95ee42e5e2236c02f7371332dbf8b6c7cac92f7eeb788ae026471239ff110207bc24A project opts in with a
Microsoft.Testing.Platform.Browserpackage reference and an explicit Chromium-family executable. MTP remains authoritative for discovery, execution, filtering, progress/results, cancellation, and artifact production. The optional package owns browser/page/host lifecycle only; the browser app sends results and artifacts directly to the SDK HTTP gateway.Design and decision record
The maintained cross-repository design discussion is dotnet/sdk#55906. The implementation boundary is:
The package uses Playwright's private stdio protocol and never opens an unauthenticated TCP DevTools endpoint. Bootstrap bearer data is expanded from the SDK-owned response file in memory and is not placed in URLs or logs.
xHarness was investigated but not reused at runtime. Its Selenium/WebDriver server, URL query arguments, DOM
tests_done, and console/result parsing conflict with direct MTP HTTP reporting. Its browser startup, container flags, retry, timeout, and cleanup experience informed this implementation.Protocol additions are capability-gated and backward compatible:
1.5.0handshake properties advertise HTTP control and artifact limits;BrowserTestingPackages.slnfis the conventional package-only build graph: eight package roots plus the non-packableMSTest.GlobalConfigsGeneratorbuild dependency. Acceptance is built separately at evaluatedNetCurrent. Package version lookups inAcceptanceTestBaseare lazy, so reduced feeds do not fail on unrelated absent packages; missing or duplicate packages still fail exactly when requested.CI uses existing capacity-backed public pools:
windows.vs2026preview.scout.amd64.open, required by the repo SDK's MSBuild 18.7 minimum and UWP package assets;build.ubuntu.2204.amd64.open, with the SDK-owned CI-onlyInstallBrowser.shprerequisite (fingerprint-verified Google signed APT repository when Chrome is absent).No system-tool/cache deletion or SDK component omission is used.
Demonstration
Canonical consumer shape:
The strict composed suite exercises normal run,
--list-tests, matching/zero/project filters, managed cancellation (exit3), intentional test failure aggregation, >1 MiB two-chunk artifact export and byte integrity, quota rejection, package/runtime layout, desktop non-interference, and process cleanup.Current CI evidence from TestFX build 1632026, testing merge commit
f1c7147a236bb16641e85058956d91138a228f1fwith SDK66309ab55fce95ee42e5e2236c02f7371332dbf8:45287224, 11/11 passed, 0 failed/incomplete/not-applicable; Chrome155.0.8059.39; diagnostic artifact (2,075,499 bytes).45287840, 11/11 passed, 0 failed/incomplete/not-applicable; Edge153.0.4234.48; diagnostic artifact (2,147,863 bytes).Both downloaded TestFX artifacts pass the SDK-owned exact-11-case validator, record
succeeded=true, and record exit0for all six composed commands.Aligned SDK build 1632034 independently passed its browser jobs: Linux run
45287292and Windows run45287874, each 11/11 passed with zero failed/incomplete/not-applicable.These are four independent green composed jobs: TestFX Windows/Linux and SDK Windows/Linux. No screenshot is included because the product behavior is process/protocol based; the linked TRX runs and narrow diagnostic artifacts are the clearer reproducible evidence.
Validation and regression proof
Focused repository validation:
build.cmd -c Release -pack -projects BrowserTestingPackages.slnf -mt 0 -bl -bln BrowserTestingPackages-fresh.binlogfrom a new detached checkout atbbbbfd23d4de8ed70a261b52a326804492b4b037: passed, exit0, 0 warnings/errors. Exactly eight non-symbol shipping nupkgs were produced and every nuspec repository commit matched that exact source SHA.build.cmd -c Release -projects test\IntegrationTests\Microsoft.Testing.Platform.Acceptance.IntegrationTests\Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj -mt 0 -bl -bln AcceptanceTestBaseLazy-final.binlog: passed, exit0, 0 warnings/errors.AcceptanceTestBaseTests: 1/1 passed, 0 skipped. It proves unused absent packages are not resolved, required absent packages fail withfound 0, and duplicate packages fail withfound 2.BrowserPackage_ContainsPrivateCrossPlatformPlaywrightRuntimewhile Shipping contained exactly the required eight dev nupkgs: 1/1 passed, 0 skipped; the original 43-package local feed was restored afterward.BuildDriverTests: 2/2 passed, covering nonzerobuild.cmdfailure propagation/binlog creation and root-wrapper forwarding of-mt 0.1632026: Linux 11/11, Windows 11/11, no skips.1632034: Linux 11/11, Windows 11/11, no skips.The required-test manifest contains 11 exact TRX names and the shared validator requires every name exactly once, all Passed, zero skipped, and a valid TRX. Every generated fixture uses a unique short NuGet cache and compares all eight restored
project.assets.jsonSHA-512 values with the staged nupkg bytes.Immutable aligned package evidence remains at source
b6c7cac92f7eeb788ae026471239ff110207bc24underartifacts/validation/Microsoft.Testing.Platform.Browser/b6c7cac92-aligned/Shipping; the Browser nupkg SHA-256 is2D078CB340E45A0E6C1CC5894BFF3148E29218683F36D5D038F24B3C78E67580.At this body update, the two required browser jobs and all GitHub policy checks are green. The TestFX aggregate build
1632026still has an unrelated Windows Release leg in progress; Windows Debug, Linux Release, Windows application-model acceptance, and both browser jobs have passed. The SDK aggregate1632034likewise still has unrelated platform legs in progress; both browser jobs have passed.Remaining external release gates are official signing/publication approval, third-party license/SBOM/provenance review, dependency flow/merge of the matching SDK implementation, release-branch/backport decisions, runtime validation before advertising untested OS/architecture combinations, and an identified servicing owner for Playwright/Node/browser cadence. The package remains experimental and is emitted to Shipping only for acceptance/cross-repository validation.