Skip to content

Add BrowserStack SDK + Playwright NUnit sample#1

Merged
Jimesh-browserstack merged 6 commits into
mainfrom
browserstack-sdk
May 20, 2026
Merged

Add BrowserStack SDK + Playwright NUnit sample#1
Jimesh-browserstack merged 6 commits into
mainfrom
browserstack-sdk

Conversation

@Jimesh-browserstack
Copy link
Copy Markdown
Collaborator

@Jimesh-browserstack Jimesh-browserstack commented May 19, 2026

Summary

Customer-facing starting point for running NUnit tests on BrowserStack via Playwright .NET and the BrowserStack .NET SDK. Coexists with browserstack/csharp-playwright-browserstack (the legacy NUnit reference) as the modern, SDK-only variant; mirrors the layout of browserstack/xunit-reqnroll-playwright-browserstack but adapted for NUnit (no Reqnroll/BDD layer).

Review round 2 — aligned with nunit-browserstack (Selenium) conventions (ed3541a + cadbcfa)

Follow-up changes after first-round review to match the established browserstack/nunit-browserstack shape:

  • browserstack.ymlparallelsPerPlatform: 1 (was 2); browserstackLocal: true (was false). Other sample repos default to ppp: 1; flipping Local on by default lets the local-host fixture run without a yml edit.
  • Both fixtures tagged [Category("sample-test")] / [Category("sample-local-test")], matching nunit-browserstack/SampleTest.cs#L7 and SampleLocalTest.cs.
  • README + .github/workflows/sanity-workflow.yml filters swapped from FullyQualifiedName~… to Category=sample-test / Category=sample-local-test; "flip the toggle" steps dropped from both the README and the workflow (now the default).

What customers get

.
├── NunitPlaywrightBrowserstack.sln
└── NunitPlaywrightBrowserstack.Tests/
    ├── NunitPlaywrightBrowserstack.Tests.csproj  # NUnit 4.3.2 + Microsoft.Playwright + Microsoft.Playwright.NUnit + BrowserStack.TestAdapter
    ├── browserstack.yml                          # 4 platforms (Win11/Chrome, OSX/WebKit, Win11/Firefox, Win11/Edge) × parallelsPerPlatform:1 × 2 NUnit-parallel fixtures = 8 concurrent sessions; browserstackLocal:true by default
    ├── AssemblyInfo.cs                           # [assembly: Parallelizable(ParallelScope.Fixtures)] + [LevelOfParallelism(4)]
    ├── PlaywrightFixtureBase.cs                  # NUnit [SetUp]/[TearDown] managing IPage; SDK routes the launch
    ├── BStackDemoCartTest.cs                     # bstackdemo add-to-cart scenario — [Category("sample-test")]
    └── BStackLocalSampleTest.cs                  # BrowserStack Local + bs-local.com:45454 harness scenario — [Category("sample-local-test")]

Plus .github/workflows/sanity-workflow.yml (mirrors the xunit-reqnroll sanity workflow, but with matrix.os = [windows-latest, macos-latest] and NUnit Category= filters).

One customer-facing entry point — dotnet test from the project directory runs both fixtures across all four platforms in parallel on BrowserStack.

Design notes

  • Customer code is browser-agnostic. PlaywrightFixtureBase.cs calls pw.Chromium.LaunchAsync() unconditionally; the SDK rewrites the launch (Harmony patches via Lib.Harmony) at runtime to route to the per-platform browser configured in browserstack.yml (chrome / playwright-webkit / playwright-firefox / edge). No Chromium.ConnectAsync(wss_url) plumbing in customer code.
  • One yml, two fixtures, one entry point. browserstackLocal: true ships on by default so both Category=sample-test (public bstackdemo) and Category=sample-local-test (private host) run without a yml edit — customer only needs to stand up the local harness for the local fixture.
  • Parallelism is multiplicative. parallelsPerPlatform: 1 (yml) × [assembly: Parallelizable(ParallelScope.Fixtures)] (AssemblyInfo.cs) × 4 platforms × 2 fixtures produces 8 concurrent sessions per dotnet test — the headline parallelism story that differentiates this sample from csharp-playwright-browserstack.
  • [Category] filtering matches the nunit-browserstack (Selenium) reference: dotnet test --filter "Category=sample-test" scopes to the cart fixture, Category=sample-local-test to the Local fixture, no filter runs all 8.
  • Versions float for Microsoft.Playwright (*) and BrowserStack.TestAdapter (0.*) to match the xunit-reqnroll/csharp-playwright pinning policy. NUnit pinned at 4.3.2 (current stable).
  • Microsoft.Playwright.NUnit is required even though the fixtures don't use PageTest — the SDK's auto-generated BrowserStackPatch.cs references Microsoft.Playwright.NUnit when framework: nunit is set, so omitting the package fails the build with CS0234.
  • Source tag follows the convention <framework>-<library>-playwright:sample-master:v<N>nunit-playwright:sample-master:v1.0.
  • Credentials documentation calls out the .NET SDK quirk: userName/accessKey in the yml are passed literally to the wss CDP endpoint — env vars override only when the lines are absent. Both the yml comment and the README state this explicitly so customers don't get a confusing "Invalid username or password" error.
  • .gitignore excludes bin/, obj/, log/, .config/, .browserstack/, *.user, *.bak, .idea/, .vs/ — same shape as xunit-reqnroll.

Local verification (round 2 — current code)

Fresh end-to-end runs against BrowserStack on the current code (ed3541a + cadbcfa: Category= filters, ppp=1, browserstackLocal=true by default). Creds in env vars, placeholder lines stripped from yml.

Mode Build Sessions Result
dotnet test --filter "Category=sample-test" 02e164650d… (build #21) 4 (Win11/Chrome 148 + Win11/Firefox 148 + OSX-Ventura/WebKit 26 + Win11/Edge 148) all passed, 16/16/18/29s, total 30s
dotnet test --filter "Category=sample-local-test" (python http.server harness on :45454; SDK auto-started Local tunnel via default-on browserstackLocal: true) 22a853f38f… (build #23) 4 (same matrix; cloud browsers reached harness via bs-local.com:45454) all passed, 15/13/11/9s, total 75s
dotnet test (no filter — full parallelism showcase) 7e77413767… (build #25) 8 (4 platforms × 2 fixtures = 8 concurrent sessions) all passed, durations 8–23s, total 38s

Workflow verification (post-merge dispatch)

Run #26153748026 against merged main (50a4e33) — both matrix jobs (windows-latest, macos-latest) ✓ green; both Run sample tests (public bstackdemo) and Run local tests (BrowserStack Local + python http.server harness) steps exited 0. Windows job 4m13s; macos similar. (BS session URLs not visible from this PR account — the workflow uses org-level CI secrets that point to a separate BrowserStack account; success is inferred from the step exit codes, which would propagate any session failure as a non-zero exit.)

Build verification: dotnet build clean with [Category] attribute resolving in both fixtures. Cleanup: browserstack.yml restored to shipping state (YOUR_USERNAME/YOUR_ACCESS_KEY placeholders; browserstackLocal: true now intentional); no bin/, obj/, .config/, .browserstack/, or .bak files staged.

Test plan

  • dotnet test --filter "Category=sample-test" runs all 4 platforms in parallel — verified on round-2 commit, 02e164650d…
  • dotnet test --filter "Category=sample-local-test" with python http.server harness + default-on Local — verified on round-2 commit, 22a853f38f…
  • dotnet test (no filter) fans out to 8 concurrent sessions — verified on round-2 commit at ppp=1, 7e77413767…
  • NUnit test display names (NunitPlaywrightBrowserstack.Tests.BStackDemoCartTest.AddTheFirstItemToCart + …BStackLocalSampleTest.ReachPrivateHostViaBrowserStackLocal) flow to BrowserStack dashboard as session names
  • env-var path works when yml userName/accessKey lines are absent
  • No generated artifacts (bin/, obj/, log/, .config/, .browserstack/, *.bak) are committed
  • dotnet build clean on round-2 commit — [Category] attribute resolves at compile time
  • Sanity workflow filter strings aligned with README/yml (Category=…) in cadbcfa — both job steps run without an in-workflow sed toggle
  • Sanity workflow runs green on first workflow_dispatch post-merge — verified, run #26153748026 (both windows-latest + macos-latest matrix jobs green)
  • Reviewer-blessed source-tag value nunit-playwright:sample-master:v1.0 (please confirm with the ASI team if a different version/prefix is preferred)

🤖 Generated with Claude Code

Customer-facing starting point for running NUnit + Microsoft.Playwright
.NET tests on BrowserStack via the BrowserStack .NET SDK
(BrowserStack.TestAdapter). Coexists with browserstack/csharp-playwright-browserstack
as the modern SDK-only variant; mirrors the shape of the xUnit + Reqnroll
sample at browserstack/xunit-reqnroll-playwright-browserstack but adapted
for NUnit.

Layout:
  NunitPlaywrightBrowserstack.sln
  NunitPlaywrightBrowserstack.Tests/
    NunitPlaywrightBrowserstack.Tests.csproj  (NUnit 4.3.2 + Playwright + BS.TestAdapter)
    browserstack.yml                          (4 platforms x parallelsPerPlatform:2 = 8 sessions)
    AssemblyInfo.cs                           (NUnit fixture-level parallelism)
    PlaywrightFixtureBase.cs                  ([SetUp]/[TearDown]; SDK rewrites the launch)
    BStackDemoCartTest.cs                     (bstackdemo add-to-cart)
    BStackLocalSampleTest.cs                  (Local + bs-local.com:45454 harness)
  .github/workflows/sanity-workflow.yml       (windows-latest + macos-latest; both filters)

Source tag: nunit-playwright:sample-master:v1.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jimesh-browserstack Jimesh-browserstack requested a review from a team as a code owner May 19, 2026 06:20
Jimesh-browserstack and others added 2 commits May 19, 2026 11:58
Addresses the xunit-reqnroll PR #1 review feedback from @xxshubhamxx
(structural parity with junit-browserstack/junit-5/browserstack.yml).
Keeps the NUnit-specific 4 platforms x 2 fixtures = 8 sessions note
underneath the generic Example 1/Example 2 worked-example text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the spirit of recent review feedback that all sample repos should
look + work similarly, treat xunit-reqnroll-playwright-browserstack as
the closest precedent for this sample (vs csharp-playwright-browserstack,
the older reference). Refreshes inline comments to point at the right
hooks/scenario in xunit-reqnroll. No behavioural changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jimesh-browserstack
Copy link
Copy Markdown
Collaborator Author

Review-comment alignment pass + cross-platform coverage update

Per a re-read of the xunit-reqnroll PR #1 review threads, treating that repo as the canonical reference for similarity.

Review-comment alignment audit

# xunit-reqnroll review feedback Status here
1 @Baaryan: "Add workflows from csharp-playwright-browserstack" sanity-workflow.yml modeled on xunit-reqnroll's (inheriting csharp-pw's shape via that path) — workflow_dispatch + commit_sha + github-script status checks + sed-strip creds + python http.server harness for Local
2 @Baaryan: "Is this SDK approach?" (on Playwright.CreateAsync hook) ✅ Same SDK approach in PlaywrightFixtureBase.cs — calls Playwright.CreateAsync() then Chromium.LaunchAsync(); SDK rewrites the launch via Harmony patches
3 @xxshubhamxx: "Match junit-browserstack/junit-5/browserstack.yml; framework key missing" framework: nunit present; structural parity with junit-browserstack restored in dd1530b (Example 1 / Example 2 generic worked-text in parallelsPerPlatform block)

Necessary deviations from junit-browserstack canonical yml — all warranted for .NET / Playwright correctness or user-chosen in brainstorm: .NET SDK quirk credentials note, sample-specific buildName, .NET framework example list (nunit, xunit, mstest), 4-browser desktop matrix (vs junit-browserstack's 3 incl. mobile), parallelsPerPlatform: 2 for the 8-session showcase, browserstackLocal: false default (matches xunit-reqnroll's shipping state so first-time customers land on a passing test).

Test-code pattern alignment (commit 97bef30)

Repointed inline comments to reference xunit-reqnroll-playwright-browserstack as the canonical precedent rather than csharp-playwright-browserstack:

  • PlaywrightFixtureBase.cs is the NUnit equivalent of xunit-reqnroll's Hooks/PlaywrightHooks.cs (BeforeScenario / AfterScenario) — same "explicit Playwright.CreateAsync() + Chromium.LaunchAsync()" pattern that surfaces the SDK rewrite mechanic. Not PageTest (csharp-pw's older pattern).
  • BStackLocalSampleTest.cs mirrors xunit-reqnroll's Features/LocalSample.feature + StepDefinitions/LocalSampleSteps.cs.
  • Sanity workflow filters stay on FullyQualifiedName~BStackDemoCart / ~BStackLocalSample (xunit-reqnroll pattern), not the older Category= syntax.

Cross-platform session verification

Dimension Verified? Evidence
BrowserStack platforms: Win11/Chrome, Win11/Edge, Win11/Firefox, OSX-Ventura/WebKit ✅ 4/4 platforms × 2/2 fixtures = 8/8 sessions passed on the current branch head Build 92f0a435…dotnet test (no filter) against commit 97bef30
CI runner OS — windows-latest (sanity workflow) ⏸️ blocked until merge GitHub Actions only registers workflows that live on the default branch — sanity-workflow.yml is on browserstack-sdk only, invisible to workflow_dispatch API (HTTP 404). Same constraint xunit-reqnroll PR #1 hit and validated post-merge.
CI runner OS — macos-latest (sanity workflow) ⏸️ blocked until merge same default-branch requirement

Plan post-merge

Once merged, I'll dispatch the sanity workflow with the merge-commit SHA from both windows-latest and macos-latest runners and post the results back in a follow-up comment, matching xunit-reqnroll's verification flow.

@Jimesh-browserstack
Copy link
Copy Markdown
Collaborator Author

CI verification on a fork (per @Baaryan's review feedback)

To pre-validate the sanity workflow before merge, forked this repo to Jimesh-browserstack/nunit-playwright-browserstack, set the fork's default branch to browserstack-sdk so workflow_dispatch registers, configured BROWSERSTACK_USERNAME + BROWSERSTACK_ACCESS_KEY as fork-scoped Actions secrets, and dispatched the sanity workflow twice against commit 97bef30 (the current PR head).

Run results

Run windows-latest job macos-latest job
#1 ✅ success (3m46s) ❌ failure (1m56s) — OSX Ventura / playwright-webkit hit System.TimeoutException : Timeout 30000ms exceeded on Page.GotoAsync("https://bstackdemo.com/")
#2 ✅ success ✅ success

BrowserStack-side coverage (run #2)

Build ee25c10f…10/10 sessions passed, all 4 platforms exercised on both fixtures from both CI runners:

Platform AddTheFirstItemToCart ReachPrivateHostViaBrowserStackLocal
Windows 11 / Chrome 148 ✅ (windows-latest + macos-latest jobs both)
Windows 11 / Edge 148 ✅ (both jobs)
Windows 11 / Firefox 148
OSX Ventura / WebKit 26

Conclusion

  • ✅ End-to-end CI flow works on both windows-latest and macos-latest runners
  • ⚠️ Observed one flake on run Add BrowserStack SDK + Playwright NUnit sample #1: a 30s Playwright navigation timeout hitting playwright-webkit from macos-latest. The same scenario passed cleanly on run Bump System.Text.RegularExpressions from 4.3.0 to 4.3.1 #2 (and across my 4 local builds today), so this looks like an intermittent network/scheduling hiccup rather than a sample defect.
  • 🛠️ Optional hardening for follow-up: bump Page.GotoAsync(..., new() { Timeout = 60000 }) (or use WaitUntilState.DOMContentLoaded) to make the sample resilient on slower customer CI networks. Neither csharp-pw nor xunit-reqnroll tunes this default, so flagging as deferrable rather than blocking.

Fork remains live for re-dispatch on additional commits if reviewers want to re-verify later.

Comment thread NunitPlaywrightBrowserstack.Tests/BStackLocalSampleTest.cs Outdated
Comment thread NunitPlaywrightBrowserstack.Tests/AssemblyInfo.cs Outdated
- BStackLocalSampleTest.cs: drop xunit-reqnroll pointer; keep a
  one-line `browserstackLocal` + port-45454 prerequisite note.
- AssemblyInfo.cs: collapse parallelism explainer to a single line
  about the SDK-spawned-process invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Baaryan
Baaryan previously approved these changes May 19, 2026
Align with nunit-browserstack (Selenium) sample conventions:
- browserstack.yml: parallelsPerPlatform 2 -> 1, browserstackLocal false -> true
- Tag fixtures with [Category("sample-test")] / [Category("sample-local-test")]
  so `dotnet test --filter "Category=..."` scopes the run (matches
  nunit-browserstack/SampleTest.cs and SampleLocalTest.cs)
- README: swap FullyQualifiedName filters for Category= filters; drop the
  now-redundant "flip the toggle" step; update parallelism narrative to ppp=1
  (4 platforms x 1 x 2 NUnit-parallel fixtures = still 8 concurrent sessions)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the README/yml changes from ed3541a:
- Filter strings: FullyQualifiedName~BStack* -> Category=sample-test /
  sample-local-test
- Drop the in-workflow sed that flipped browserstackLocal false -> true;
  the yml ships with browserstackLocal: true now
- Refresh the top-of-file scenario summary to match

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jimesh-browserstack Jimesh-browserstack merged commit 50a4e33 into main May 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants