Skip to content

Add BrowserStack SDK + Playwright xUnit/Reqnroll sample#1

Open
Jimesh-browserstack wants to merge 2 commits intomainfrom
browserstack-sdk
Open

Add BrowserStack SDK + Playwright xUnit/Reqnroll sample#1
Jimesh-browserstack wants to merge 2 commits intomainfrom
browserstack-sdk

Conversation

@Jimesh-browserstack
Copy link
Copy Markdown
Collaborator

Summary

Customer-facing starting point for running xUnit + Reqnroll BDD tests on BrowserStack via Playwright .NET and the BrowserStack .NET SDK. Mirrors the shape of browserstack/csharp-playwright-browserstack (NUnit reference) but adapted for xUnit + Reqnroll.

What customers get

.
├── XunitReqnrollPlaywrightBrowserstack.sln
└── XunitReqnrollPlaywrightBrowserstack.Tests/
    ├── XunitReqnrollPlaywrightBrowserstack.Tests.csproj  # xUnit + Reqnroll.xUnit + Microsoft.Playwright + BrowserStack.TestAdapter
    ├── browserstack.yml                                  # 2-platform parallel default (Win11-Chrome, OSX-WebKit)
    ├── Features/
    │   └── Sample.feature                                # bstackdemo add-to-cart scenario
    ├── StepDefinitions/
    │   └── SampleSteps.cs
    └── Hooks/
        └── PlaywrightHooks.cs                            # creates IPage per scenario; SDK routes the launch

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

Design notes

  • Customer code is browser-agnostic. Hooks/PlaywrightHooks.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. No Chromium.ConnectAsync(wss_url) plumbing in customer code.
  • One yml, one feature, no run-mode switching. Aligned with browserstack/behave-playwright-browserstack#1 review feedback: dropped the Makefile, the config/ alt-yml directory, and the local-html harness; documented Local mode as a single-line browserstackLocal: true flip in the README rather than a separate run target.
  • Versions float for Microsoft.Playwright (*) and BrowserStack.TestAdapter (0.*) to match the NUnit reference repo's pinning policy.
  • Source tag follows the convention <framework>-<library>:sample-master:v<N>xunit-reqnroll-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" or BS Local "Auth Token must be alphanumeric" error.
  • No CI workflow added. This branch only carries the sample — the empty-repo Semgrep.yml already on main continues to run on PRs.

Local verification (already complete)

Ran end-to-end against BrowserStack with Jimesh's creds in env vars (and again with creds substituted into the yml):

Mode Build Sessions Result
dotnet test (env vars, lines absent) 0a6a6690… 2 (Win11/Chrome 147 + OSX/playwright-webkit 26) both passed, 13s each, started concurrently
dotnet test (real creds in yml, the customer-facing default) e04c95b5… 2 (Win11/Chrome 147 + OSX/playwright-webkit 26) both passed, 12–15s, started concurrently

Cleanup: no leftover .bak files, no stray BrowserStackLocal processes (Local toggle is false in default yml), Reqnroll-generated *.feature.cs are gitignored.

Test plan

  • dotnet test runs both platforms in parallel on BrowserStack — verified via builds linked above
  • Reqnroll feature scenario name flows to BrowserStack dashboard (XunitReqnrollPlaywrightBrowserstack.Tests.Features.BStackDemoCartFeature.AddTheFirstItemToCart Add the first item to cart)
  • yml-with-real-creds path works (the customer-facing default)
  • env-var path works when yml userName/accessKey lines are absent
  • No generated artifacts (bin/, obj/, log/, .config/, .browserstack/, *.feature.cs) are committed
  • Reviewer-blessed source-tag value xunit-reqnroll-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 xUnit + Reqnroll BDD tests on
BrowserStack via Playwright .NET and the BrowserStack .NET SDK. Mirrors
the shape of browserstack/csharp-playwright-browserstack (NUnit) but
adapted for xUnit + Reqnroll.

What customers get:
- browserstack.yml with 2-platform parallel default (Win11-Chrome,
  macOS-WebKit) and the Local toggle
- Reqnroll feature + step definitions for the bstackdemo add-to-cart
  scenario
- Hooks/PlaywrightHooks.cs that creates an IPage per scenario; the SDK
  redirects the launch to BrowserStack at runtime, so customer code
  uses pure Microsoft.Playwright with no manual ConnectAsync
- README and yml comments documenting credential setup, parallel run,
  and the Local-tunnel flip

Verified end-to-end against BrowserStack (build hashed_ids
0a6a6690b74901b2691a24ccd6875d4e0fb1f4fb,
e04c95b5abc3602aa01874eecfd899b63f57b1c3): both Win11/Chrome and
OSX/WebKit sessions started concurrently and passed the cart scenario.

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 7, 2026 08:30
Copy link
Copy Markdown
Collaborator

@Baaryan Baaryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please add the workflows? https://github.com/browserstack/csharp-playwright-browserstack/tree/main/.github/workflows
This is needed for verification before and after the repo setup is done
Also, please trigger that workflow on a fork to test the results for the same

[BeforeScenario]
public async Task SetUp()
{
_pw = await Playwright.CreateAsync();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this SDK approach?

Mirrors browserstack/csharp-playwright-browserstack/.github/workflows/sanity-workflow.yml so the repo can be verified pre/post setup via workflow_dispatch with a commit_sha input.

- .github/workflows/sanity-workflow.yml: workflow_dispatch + commit_sha input, dotnet 8.0.x on windows-latest, github-script in_progress/completed check-runs, two test phases
  - public bstackdemo run (browserstackLocal: false)
  - Local run: spins up python -m http.server 45454 with a title-matching index.html, flips browserstackLocal to true, SDK starts/stops the tunnel, scenario asserts the tunneled page title contains "BrowserStack Local"
- Features/LocalSample.feature + StepDefinitions/LocalSampleSteps.cs: BDD analogue of csharp-playwright-browserstack/SampleLocalTest.cs (page.GotoAsync("http://bs-local.com:45454/") + title contains assertion)
- Verified locally on BrowserStack build b06134226200d2c2e5550e70de73dec89b835187: 2 sessions (Win11/Chrome 147 + OSX/Ventura/playwright-webkit 26), both browserstack_status=done, status=passed

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

Thanks for the review @Baaryan — addressing both threads here.


1. Workflow ask  (review #4242494122)

Can we please add the workflows? https://github.com/browserstack/csharp-playwright-browserstack/tree/main/.github/workflows … please trigger that workflow on a fork to test the results

Done in 5945ab6.github/workflows/sanity-workflow.yml. Mirrors the NUnit reference's shape (workflow_dispatch + commit_sha input + actions/github-script checks.create for in_progress/completed status reporting), with two adaptations forced by xUnit/Reqnroll instead of NUnit:

Reference (csharp-playwright-browserstack) This repo
Matrix dotnet: ['6.0.x', '5.0.x', '7.0.x'] dotnet: ['8.0.x']  — Reqnroll.xUnit 3.3.4 + xUnit 2.9.2 + Microsoft.Playwright 1.59 require .NET 8+
Filter Category=sample-test / sample-local-test (NUnit [Category]) Filter by FullyQualifiedName~BStackDemoCart / ~BStackLocalSample  — xUnit doesn't have NUnit-style category filters; Reqnroll's generated feature class names give us a clean per-feature filter without dragging in tag→trait plumbing
Local mode: browserstackLocal: true baked into the yml at all times Two-phase run: phase 1 keeps browserstackLocal: false for the public bstackdemo scenario; phase 2 starts a python -m http.server 45454 harness with a title-matching index.html, sed-flips browserstackLocal: falsetrue, runs only the Local scenario, then trap-kills the harness on exit

The yml also strips the literal userName: / accessKey: placeholder lines before any dotnet test invocation — the .NET SDK only falls back to BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY env vars when those yml fields are absent (not when they hold placeholders), so this avoids a confusing "Invalid username or password" failure on first dispatch.

Required org-level secrets for workflow_dispatch

  • BROWSERSTACK_USERNAME
  • BROWSERSTACK_ACCESS_KEY

How to dispatch

First dispatch needs the workflow file on the default branch, so once this PR merges the Run workflow button shows up under the Actions tab → xUnit Reqnroll Playwright SDK sanity workflow on workflow_dispatch → enter the full commit id of any branch/PR head and click Run.

Fork dispatch

I don't have permissions to push a workflow file to a personal fork in the BrowserStack org's enterprise SSO setup, so I haven't run it on a fork myself — happy to run it on this branch's commit (5945ab6) once the workflow lives on main, or to coordinate with whoever has fork-write access if you'd prefer pre-merge verification.


2. SDK approach  (review #4242593244 inline)

Is this SDK approach?

Yes — this is the .NET SDK path. The BrowserStack.TestAdapter package is the SDK on .NET (the package is published by BrowserStack on NuGet alongside the underlying browserstack-sdk dotnet tool it installs at build time). Customer code intentionally looks vanilla because the SDK rewrites the launch at runtime via Harmony rather than asking the customer to import a BrowserStack-specific Browser / PageTest base class.

Evidence in this branch:

  • XunitReqnrollPlaywrightBrowserstack.Tests.csproj<PackageReference Include="BrowserStack.TestAdapter" Version="0.*" />
  • browserstack.yml at the project root drives platforms, parallelsPerPlatform, browserstackLocal, reporting, source tag — i.e. the SDK config surface.
  • Hooks/PlaywrightHooks.cs calls pw.Chromium.LaunchAsync() with no args — no ConnectAsync(wssEndpoint), no caps in code. The SDK Harmony-patches that call at runtime to route to the per-platform browser declared in the yml.
  • The local SDK adapter logs from this morning confirm the patch path: browserstack_sdk.Program | Adapter Flow Triggered with testDllPath: … followed by Running for f/w xunit 2.9.2, Detected dotnet version 8.0.420, Detected Playwright version 1.59.0.

End-to-end proof on real BrowserStack builds:

Mode Build Sessions Result
Public bstackdemo (env-var creds) 0a6a6690… 2 (Win11/Chrome + OSX/playwright-webkit) both passed, parallel
Public bstackdemo (yml-creds) e04c95b5… 2 both passed, parallel
BrowserStack Local + harness on :45454 (new, gating this commit) b06134226… 2 (Win11/Chrome 147 7s + OSX/Ventura/playwright-webkit 26 9s) both passed, browserstack_status: done, tunneled bs-local.com page title matched

Two readable tells if you'd ever want to confirm "SDK vs not" on a customer's repo at a glance:

  1. Test-DLL path appears in browserstack_sdk.Program log lines under XunitReqnrollPlaywrightBrowserstack.Tests/log/adapter.log.
  2. The dashboard build's source value → xunit-reqnroll-playwright:sample-master:v1.0 (set in the yml; SDK-path-only — the legacy explicit-ConnectAsync flow doesn't emit a source tag).

Happy to add a one-liner // SDK rewrites this at runtime via BrowserStack.TestAdapter above the LaunchAsync if you'd like a permanent in-code signpost so future readers don't ask the same question — let me know.


cc @Baaryan — please re-review when you have a moment.

@Jimesh-browserstack
Copy link
Copy Markdown
Collaborator Author

@Baaryan — fork dispatch done.

Workflow run: Jimesh-browserstack/xunit-reqnroll-playwright-browserstack → run #25491535183 ✅ green

Phase Step Result Duration
Setup checkout @ 5945ab6 → setup-dotnet 8.0.x → strip yml cred placeholders → dotnet build success ~70s
Phase 1 Run sample tests (public bstackdemo) --filter "FullyQualifiedName~BStackDemoCart" Passed: 1 / Failed: 0 13s
Phase 2 Run local tests (browserstackLocal: true + python http.server :45454 harness) --filter "FullyQualifiedName~BStackLocalSample" Passed: 1 / Failed: 0 7s
Status actions/github-script checks.create in_progress + completed success

Total: 3m 25s on windows-latest.

Smoking-gun proof the Local tunnel actually carried traffic on the fork run

From the runner's python http.server stdout (workflow log, step 8):

::ffff:127.0.0.1 - - [07/May/2026 10:57:47] "GET / HTTP/1.1" 200 -
::ffff:127.0.0.1 - - [07/May/2026 10:57:58] "GET / HTTP/1.1" 200 -
::ffff:127.0.0.1 - - [07/May/2026 10:58:02] "GET / HTTP/1.1" 200 -
::ffff:127.0.0.1 - - [07/May/2026 10:58:02] "GET /favicon.ico HTTP/1.1" 404 -

Those 127.0.0.1 GETs are the cloud browsers (Win11/Chrome 147 + OSX/Ventura/playwright-webkit 26) reaching back through the BrowserStack Local tunnel to http://bs-local.com:45454/ and pulling the harness index.html (<title>BrowserStack Local Test</title>) the workflow stood up moments earlier. The LocalSampleSteps Assert.Contains("BrowserStack Local", title) then passed.

Setup notes for future fork dispatches

  • BrowserStack creds are set as fork repo secrets (BROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEY).
  • Forks ship with Actions enabled but workflows un-indexed — pushing any commit to the fork's default branch triggers indexing; after that workflow_dispatch works via API or UI.
  • I changed the fork's default branch to browserstack-sdk so the workflow file lives on default. Once this PR merges into upstream main, the same dispatch on the org repo doesn't need that workaround.

Re-pinging — please re-review at your convenience.

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.

2 participants