Move Playwright install into pipeline module#5390
Conversation
Adds InstallPlaywrightModule to TUnit.Pipeline so Playwright browser installation runs concurrently with other pipeline work rather than blocking the entire CI job upfront. RunPlaywrightTestsModule depends on it via [DependsOn<InstallPlaywrightModule>].
There was a problem hiding this comment.
Code Review
The PR achieves its stated goal of moving Playwright installation into the pipeline module system, which is a good architectural direction. However, there are a few issues worth addressing before merging.
⚠️ Loss of Browser Caching
The most impactful concern: the original CI workflow had an actions/cache@v5 step that cached the Playwright browser binaries between runs. That cache is now removed entirely, and InstallPlaywrightModule unconditionally runs npx playwright install --with-deps on every CI run.
Playwright browser downloads are large (~200–400MB per browser per OS). This will meaningfully increase CI time on every run compared to the previous cache-hit path. If caching isn't feasible inside the pipeline module, consider at minimum keeping the actions/cache step in the YAML (without the install steps), and checking for cache hits inside the module before running the install command.
⚠️ Unintentional Removal of RunOnLinuxOnly, RunOnWindowsOnly
The diff removes these two attributes from RunPlaywrightTestsModule:
-[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
+[NotInParallel("NetworkTests")]
+[DependsOn<InstallPlaywrightModule>]Looking at sibling modules, this pattern is intentional — it means "skip on macOS":
RunRpcTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]RunAspireTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]RunAspNetTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]
Removing these attributes means Playwright tests (and InstallPlaywrightModule) will now also run on macos-latest. Was that intentional? If not, these attributes should be restored — either on RunPlaywrightTestsModule directly, or also on InstallPlaywrightModule to avoid installing browsers on macOS unnecessarily.
ℹ️ Bash Shell on Windows
InstallPlaywrightModule uses context.Shell.Bash.Command(new BashCommandOptions(...)), which explicitly invokes bash. The original CI step used the runner's default shell (pwsh on Windows). GitHub Actions Windows runners have Git Bash available, so this will likely work in practice, but it's a subtle behavioral difference worth being aware of if this module is ever used outside the CI context.
✅ What's Good
- Moving the install step into a typed module is the right direction — it keeps infrastructure logic close to the code that needs it and makes the dependency explicit via
[DependsOn<InstallPlaywrightModule>]. - Removing 21 lines from the YAML is a real simplification.
- The module implementation itself is clean and minimal.
Summary: The caching regression and the RunOnLinuxOnly, RunOnWindowsOnly removal are the two items that should be addressed before merging.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
TIP This summary will be updated as you push new changes. Give us feedback
Re-adds actions/cache@v5 for Playwright browsers in CI. InstallPlaywrightModule now checks if the browser cache directory is populated and skips --with-deps when browsers are already restored from cache.
playwright install --with-deps is idempotent; it detects cached browsers and skips re-downloading. The IsBrowserCachePresent() check duplicated Playwright's own logic and introduced a TOCTOU anti-pattern. The YAML cache step still restores browser binaries between CI runs.
There was a problem hiding this comment.
Follow-up Review
The second commit ("Restore Playwright browser caching and conditional install") addressed the primary caching concern from my previous review — the actions/cache@v5 step is back in the YAML and InstallPlaywrightModule now checks for local cache presence via IsBrowserCachePresent() before deciding whether to run --with-deps. Good fix.
⚠️ Still Unresolved: Missing RunOnLinuxOnly, RunOnWindowsOnly
The removal of these attributes from RunPlaywrightTestsModule has not been addressed, and now InstallPlaywrightModule also lacks them. Since the CI matrix includes macos-latest, Playwright tests (and browser installation) will now run on macOS as well.
Every comparable module in the pipeline restricts to Linux + Windows:
RunAspNetTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]RunAspireTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]RunRpcTestsModule:[RunOnLinuxOnly, RunOnWindowsOnly]
If running Playwright on macOS is intentional, the PR description should say so. If not, both modules need the attributes restored:
// InstallPlaywrightModule.cs
[RunOnLinuxOnly, RunOnWindowsOnly]
public class InstallPlaywrightModule : Module<CommandResult>
// RunPlaywrightTestsModule.cs
[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
[DependsOn<InstallPlaywrightModule>]
public class RunPlaywrightTestsModule : TestBaseModuleℹ️ Cache Presence Check is Shallow
IsBrowserCachePresent() checks only whether any subdirectory exists in the cache path. A partial or corrupt cache restore (which continue-on-error: true in the YAML allows) would pass this check but produce an incomplete install, leading to test failures rather than a clean re-install. A more defensive version would look for a known-good browser directory (e.g. chromium-*) or simply always run npx playwright install and rely on Playwright's own idempotency checks — Playwright already skips downloads when browsers are current.
ℹ️ Bash Shell on Windows (Unchanged)
context.Shell.Bash.Command(...) still uses bash explicitly. Git Bash is available on GitHub Actions Windows runners so this works in practice, but it differs from the original CI step which used the runner default shell (pwsh). No change needed unless this module is reused outside CI.
✅ What's Good
- The caching regression from the first commit is now fixed.
- The two-commit approach (introduce feature → restore caching) is clear in git history.
- Module logic itself is clean.
The RunOnLinuxOnly, RunOnWindowsOnly question is the only blocking item — either confirm macOS Playwright runs are intentional, or restore the attributes.
There was a problem hiding this comment.
Review
This is a clean, well-scoped change that correctly moves Playwright installation into the pipeline so it can run concurrently with other work (build, unit tests, etc.) rather than blocking the entire CI job upfront. The intent and structure are sound. A few things worth noting:
Bug: [RunOnLinuxOnly, RunOnWindowsOnly] removed from RunPlaywrightTestsModule
The original module had [NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]. The PR drops the two platform attributes but does not add them to InstallPlaywrightModule. This means on macOS runners (if ever added), both modules would now execute, whereas previously they were skipped. More importantly, InstallPlaywrightModule uses context.Shell.Bash.Command(...) which is only available on Unix-like systems. On Windows CI runners, bash may not be in PATH in the same way (or Playwright's --with-deps installs Linux apt packages that don't apply).
If the intent is that Playwright tests only run on Linux and Windows (not macOS), the platform constraints should be preserved — either on InstallPlaywrightModule itself, or by restoring the attributes on RunPlaywrightTestsModule. Looking at the analogous RunAspNetTestsModule and RunAspireTestsModule, they keep [RunOnLinuxOnly, RunOnWindowsOnly] — this pattern should be consistent.
Observation: InstallPlaywrightModule always installs --with-deps
The original YAML distinguished between a cache hit (npx playwright install, browsers only) and a miss (npx playwright install --with-deps, browsers + system deps). The new module always runs --with-deps. On Linux this installs OS-level dependencies via apt, which takes extra time even when browsers are cached. This is intentional if simplicity is preferred over performance, but worth documenting or considering whether the module should probe the cache state to mirror the original behavior.
Minor: context.Shell.Bash cross-platform assumptions
Shell.Bash.Command works on Linux/macOS. On Windows it invokes Git Bash or WSL depending on the ModularPipelines version. Since InstallPlaywrightModule has no platform guard, this could silently fail or do the wrong thing on Windows. The existing PublishAndRunMockTestsAOTModule avoids this by explicitly gating on RuntimeInformation.IsOSPlatform(OSPlatform.Linux) via ModuleConfiguration.Create().WithSkipWhen(...). Consider adding a similar guard or restoring the attribute:
[RunOnLinuxOnly, RunOnWindowsOnly]
public class InstallPlaywrightModule : Module<CommandResult>or using a Configure() override for finer control.
The concurrency improvement is real
The core idea is good — moving install into the pipeline's dependency graph means it overlaps with build/pack/other test steps instead of being a serial gate in YAML. The [DependsOn<InstallPlaywrightModule>] dependency correctly enforces ordering without sacrificing the parallelism gain.
Summary
The main concern is the missing platform constraint on InstallPlaywrightModule. If RunPlaywrightTestsModule was previously restricted to Linux and Windows only, that constraint needs to travel to InstallPlaywrightModule as well (since it will be executed because of the dependency chain). Everything else is clean and the approach is architecturally sound.
Final Review — Remaining Unresolved IssueThis PR has been reviewed three times now. Two concerns have been satisfactorily addressed (browser caching regression, working directory fix). One has not been addressed across any of the four commits: Still Unresolved: Missing Platform Guards on Both ModulesThe current state of the merged code: // InstallPlaywrightModule.cs — no platform attributes
public class InstallPlaywrightModule : Module<CommandResult>
// RunPlaywrightTestsModule.cs — platform attributes removed
[NotInParallel("NetworkTests")]
[DependsOn<InstallPlaywrightModule>]
public class RunPlaywrightTestsModule : TestBaseModuleThe previous state: [NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
public class RunPlaywrightTestsModule : TestBaseModuleEvery comparable module in the pipeline applies
Why this matters:
Fix: // InstallPlaywrightModule.cs
[RunOnLinuxOnly, RunOnWindowsOnly]
public class InstallPlaywrightModule : Module<CommandResult>
// RunPlaywrightTestsModule.cs
[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
[DependsOn<InstallPlaywrightModule>]
public class RunPlaywrightTestsModule : TestBaseModuleIf running Playwright on macOS is intentionally new behavior introduced by this PR, please update the PR description to say so — as it stands there is no mention of it, and it breaks the pattern established by all sibling modules. The rest of the PR (concurrent install via |
There was a problem hiding this comment.
Fourth Review — Same Blocking Issue Remains Unresolved
This PR has now accumulated 5 commits and 3 prior review cycles. The working directory fix and the browser caching regression have both been properly addressed. However, the single blocking issue flagged in every prior review has still not been addressed.
Still Missing: Platform Guards on Both Modules
Current state of the PR head:
// InstallPlaywrightModule.cs — no platform attributes
public class InstallPlaywrightModule : Module<CommandResult>
// RunPlaywrightTestsModule.cs — [RunOnLinuxOnly, RunOnWindowsOnly] removed, never restored
[NotInParallel("NetworkTests")]
[DependsOn<InstallPlaywrightModule>]
public class RunPlaywrightTestsModule : TestBaseModuleEvery comparable network-test module in the pipeline carries [RunOnLinuxOnly, RunOnWindowsOnly]:
RunAspNetTestsModule—[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]RunAspireTestsModuleRunRpcTestsModule
RunPlaywrightTestsModule had these attributes before this PR. They were removed and never restored.
Why this matters:
InstallPlaywrightModuleusescontext.Shell.Bash.Command(...)— on Windows this delegates to Git Bash/WSL andnpx playwright install --with-depsinstalls Linux apt packages that don't apply on Windows. This is an existing runtime risk.- If the CI matrix ever expands to
macos-latest, both modules will execute on macOS when they previously did not.--with-depson macOS will fail because it tries to invoke apt/dpkg. - The
[DependsOn<InstallPlaywrightModule>]coupling means whereverRunPlaywrightTestsModuleruns,InstallPlaywrightModulealso runs — so the install module must carry the same platform constraints.
Required fix (two lines):
// InstallPlaywrightModule.cs
[RunOnLinuxOnly, RunOnWindowsOnly]
public class InstallPlaywrightModule : Module<CommandResult>
// RunPlaywrightTestsModule.cs
[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
[DependsOn<InstallPlaywrightModule>]
public class RunPlaywrightTestsModule : TestBaseModuleThe structural improvement in this PR (moving installation into the pipeline so it runs concurrently with build/pack/tests) is genuinely good. This is a two-line fix. Everything else looks correct and clean.
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.23.7 to 1.28.7. <details> <summary>Release notes</summary> _Sourced from [TUnit.Core's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.28.7 <!-- Release notes generated using configuration in .github/release.yml at v1.28.7 --> ## What's Changed ### Other Changes * fix: prevent StringBuilder race in console interceptor during parallel tests by @thomhurst in thomhurst/TUnit#5414 ### Dependencies * chore(deps): update tunit to 1.28.5 by @thomhurst in thomhurst/TUnit#5415 **Full Changelog**: thomhurst/TUnit@v1.28.5...v1.28.7 ## 1.28.5 <!-- Release notes generated using configuration in .github/release.yml at v1.28.5 --> ## What's Changed ### Other Changes * perf: eliminate redundant builds in CI pipeline by @thomhurst in thomhurst/TUnit#5405 * perf: eliminate store.ToArray() allocation on mock behavior execution hot path by @thomhurst in thomhurst/TUnit#5409 * fix: omit non-class/struct constraints on explicit interface mock implementations by @thomhurst in thomhurst/TUnit#5413 ### Dependencies * chore(deps): update tunit to 1.28.0 by @thomhurst in thomhurst/TUnit#5406 **Full Changelog**: thomhurst/TUnit@v1.28.0...v1.28.5 ## 1.28.0 <!-- Release notes generated using configuration in .github/release.yml at v1.28.0 --> ## What's Changed ### Other Changes * fix: resolve build warnings in solution by @thomhurst in thomhurst/TUnit#5386 * Perf: Optimize MockEngine hot paths (~30-42% faster) by @thomhurst in thomhurst/TUnit#5391 * Move Playwright install into pipeline module by @thomhurst in thomhurst/TUnit#5390 * perf: optimize solution build performance by @thomhurst in thomhurst/TUnit#5393 * perf: defer per-class JIT via lazy test registration + parallel resolution by @thomhurst in thomhurst/TUnit#5395 * Perf: Generate typed HandleCall<T1,...> overloads to eliminate argument boxing by @thomhurst in thomhurst/TUnit#5399 * perf: filter generated attributes to TUnit-related types only by @thomhurst in thomhurst/TUnit#5402 * fix: generate valid mock class names for generic interfaces with non-built-in type args by @thomhurst in thomhurst/TUnit#5404 ### Dependencies * chore(deps): update tunit to 1.27.0 by @thomhurst in thomhurst/TUnit#5392 * chore(deps): update dependency path-to-regexp to v8 by @thomhurst in thomhurst/TUnit#5378 **Full Changelog**: thomhurst/TUnit@v1.27.0...v1.28.0 ## 1.27.0 <!-- Release notes generated using configuration in .github/release.yml at v1.27.0 --> ## What's Changed ### Other Changes * Fix Dependabot security vulnerabilities in docs site by @thomhurst in thomhurst/TUnit#5372 * fix: use 0.0.0-scrubbed sentinel version in snapshot scrubber to avoid false Dependabot alerts by @thomhurst in thomhurst/TUnit#5374 * Speed up Engine.Tests by removing ProcessorCount parallelism cap by @thomhurst in thomhurst/TUnit#5379 * ci: add concurrency groups to cancel redundant workflow runs by @thomhurst in thomhurst/TUnit#5373 * Add scope-aware initialization and disposal OpenTelemetry spans to trace timeline and HTML report by @Copilot in thomhurst/TUnit#5339 * Add WithInnerExceptions() for fluent AggregateException assertion chaining by @thomhurst in thomhurst/TUnit#5380 * Drop net6.0 and net7.0 TFMs, keep net8.0+ and netstandard2.x by @thomhurst in thomhurst/TUnit#5387 * Remove all [Obsolete] members and migrate callers by @thomhurst in thomhurst/TUnit#5384 * Add AssertionResult.Failed overload that accepts an Exception by @thomhurst in thomhurst/TUnit#5388 ### Dependencies * chore(deps): update dependency mockolate to 2.3.0 by @thomhurst in thomhurst/TUnit#5370 * chore(deps): update tunit to 1.25.0 by @thomhurst in thomhurst/TUnit#5371 * chore(deps): update dependency minimatch to v9.0.9 by @thomhurst in thomhurst/TUnit#5375 * chore(deps): update dependency path-to-regexp to v0.2.5 by @thomhurst in thomhurst/TUnit#5376 * chore(deps): update dependency minimatch to v10 by @thomhurst in thomhurst/TUnit#5377 * chore(deps): update dependency picomatch to v4 by @thomhurst in thomhurst/TUnit#5382 * chore(deps): update dependency svgo to v4 by @thomhurst in thomhurst/TUnit#5383 * chore(deps): update dependency path-to-regexp to v1 [security] by @thomhurst in thomhurst/TUnit#5385 **Full Changelog**: thomhurst/TUnit@v1.25.0...v1.27.0 ## 1.25.0 <!-- Release notes generated using configuration in .github/release.yml at v1.25.0 --> ## What's Changed ### Other Changes * Fix missing `default` constraint on explicit interface implementations with unconstrained generics by @thomhurst in thomhurst/TUnit#5363 * feat(mocks): add ReturnsAsync typed factory overload with method parameters by @thomhurst in thomhurst/TUnit#5367 * Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types by @thomhurst in thomhurst/TUnit#5366 * refactor(mocks): use file-scoped types for generated implementation details by @thomhurst in thomhurst/TUnit#5369 * Compress HTML report JSON data and minify CSS by @thomhurst in thomhurst/TUnit#5368 ### Dependencies * chore(deps): update tunit to 1.24.31 by @thomhurst in thomhurst/TUnit#5356 * chore(deps): update dependency mockolate to 2.2.0 by @thomhurst in thomhurst/TUnit#5357 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5365 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5364 **Full Changelog**: thomhurst/TUnit@v1.24.31...v1.25.0 ## 1.24.31 <!-- Release notes generated using configuration in .github/release.yml at v1.24.31 --> ## What's Changed ### Other Changes * Fix Aspire 13.2.0+ timeout caused by ProjectRebuilderResource being awaited by @Copilot in thomhurst/TUnit#5335 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5349 * Fix nullable IParsable type recognition in source generator and analyzer by @Copilot in thomhurst/TUnit#5354 * fix: resolve race condition in HookExecutionOrderTests by @thomhurst in thomhurst/TUnit#5355 * Fix MaxExternalSpansPerTest cap bypass when Activity.Parent chain is broken by @Copilot in thomhurst/TUnit#5352 ### Dependencies * chore(deps): update tunit to 1.24.18 by @thomhurst in thomhurst/TUnit#5340 * chore(deps): update dependency stackexchange.redis to 2.12.14 by @thomhurst in thomhurst/TUnit#5343 * chore(deps): update verify to 31.15.0 by @thomhurst in thomhurst/TUnit#5346 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5348 **Full Changelog**: thomhurst/TUnit@v1.24.18...v1.24.31 ## 1.24.18 <!-- Release notes generated using configuration in .github/release.yml at v1.24.18 --> ## What's Changed ### Other Changes * feat(mocks): shorter, more readable generated mock type names by @thomhurst in thomhurst/TUnit#5334 * Fix DisposeAsync() ordering for nested property injection by @Copilot in thomhurst/TUnit#5337 ### Dependencies * chore(deps): update tunit to 1.24.13 by @thomhurst in thomhurst/TUnit#5331 **Full Changelog**: thomhurst/TUnit@v1.24.13...v1.24.18 ## 1.24.13 <!-- Release notes generated using configuration in .github/release.yml at v1.24.13 --> ## What's Changed ### Other Changes * perf(mocks): optimize MockEngine for lower allocation and faster verification by @thomhurst in thomhurst/TUnit#5319 * Remove defunct `UseTestingPlatformProtocol` reference for vscode by @erwinkramer in thomhurst/TUnit#5328 * perf(aspnetcore): prevent thread pool starvation during parallel WebApplicationTest server init by @thomhurst in thomhurst/TUnit#5329 * fix TUnit0073 for when type from from another assembly by @SimonCropp in thomhurst/TUnit#5322 * Fix implicit conversion operators bypassed in property injection casts by @Copilot in thomhurst/TUnit#5317 * fix(mocks): skip non-virtual 'new' methods when discovering mockable members by @thomhurst in thomhurst/TUnit#5330 * feat(mocks): IFoo.Mock() discovery with generic fallback and ORP resolution by @thomhurst in thomhurst/TUnit#5327 ### Dependencies * chore(deps): update tunit to 1.24.0 by @thomhurst in thomhurst/TUnit#5315 * chore(deps): update aspire to 13.2.1 by @thomhurst in thomhurst/TUnit#5323 * chore(deps): update verify to 31.14.0 by @thomhurst in thomhurst/TUnit#5325 ## New Contributors * @erwinkramer made their first contribution in thomhurst/TUnit#5328 **Full Changelog**: thomhurst/TUnit@v1.24.0...v1.24.13 ## 1.24.0 <!-- Release notes generated using configuration in .github/release.yml at v1.24.0 --> ## What's Changed ### Other Changes * perf: optimize TUnit.Mocks hot paths by @thomhurst in thomhurst/TUnit#5304 * fix: resolve System.Memory version conflict on .NET Framework (net462) by @thomhurst in thomhurst/TUnit#5303 * fix: resolve CS0460/CS0122/CS0115 when mocking concrete classes from external assemblies by @thomhurst in thomhurst/TUnit#5310 * feat(mocks): parameterless Returns() and ReturnsAsync() for async methods by @thomhurst in thomhurst/TUnit#5309 * Fix typo in NUnit manual migration guide by @aa-ko in thomhurst/TUnit#5312 * refactor(mocks): unify Mock.Of<T>() and Mock.OfPartial<T>() into single API by @thomhurst in thomhurst/TUnit#5311 * refactor(mocks): clean up Mock API surface by @thomhurst in thomhurst/TUnit#5314 * refactor(mocks): remove generic/untyped overloads from public API by @thomhurst in thomhurst/TUnit#5313 ### Dependencies * chore(deps): update tunit to 1.23.7 by @thomhurst in thomhurst/TUnit#5305 * chore(deps): update dependency mockolate to 2.1.1 by @thomhurst in thomhurst/TUnit#5307 ## New Contributors * @aa-ko made their first contribution in thomhurst/TUnit#5312 **Full Changelog**: thomhurst/TUnit@v1.23.7...v1.24.0 Commits viewable in [compare view](thomhurst/TUnit@v1.23.7...v1.28.7). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
InstallPlaywrightModuletoTUnit.Pipelinewhich runsnpx playwright install --with-depsvia bashRunPlaywrightTestsModulegains[DependsOn<InstallPlaywrightModule>]so it waits for installation before running testsdotnet.yml— installation now happens concurrently with other pipeline work (build, unit tests, etc.) rather than blocking upfrontTest Plan
InstallPlaywrightModulecompletes beforeRunPlaywrightTestsModulestarts