Skip to content

Commit 412dc39

Browse files
Add BrowserStack SDK + Playwright xUnit/Reqnroll sample
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>
1 parent 8534953 commit 412dc39

8 files changed

Lines changed: 296 additions & 1 deletion

File tree

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
bin/
2+
obj/
3+
log/
4+
TestResults/
5+
.vs/
6+
.vscode/
7+
.idea/
8+
.config/
9+
.browserstack/
10+
*.user
11+
*.suo
12+
.DS_Store
13+
browserstack.err
14+
15+
# Reqnroll-generated code-behind for .feature files
16+
**/Features/*.feature.cs

README.md

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,97 @@
11
# xunit-reqnroll-playwright-browserstack
2-
Sample repo for customers
2+
3+
This sample shows how to run [xUnit](https://xunit.net/) + [Reqnroll](https://reqnroll.net/) + [Playwright](https://playwright.dev/dotnet) tests on BrowserStack using the [BrowserStack .NET SDK](https://www.nuget.org/packages/BrowserStack.TestAdapter). The SDK reads `browserstack.yml`, fans your scenarios out across the platforms listed there, starts and stops BrowserStack Local automatically, and reports test status to the BrowserStack dashboard. Your test code stays pure `Microsoft.Playwright` + Reqnroll -- no manual `ConnectAsync`, no caps in code.
4+
5+
![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780)
6+
7+
## Run Sample Build
8+
9+
* Clone the repo
10+
* Open the solution `XunitReqnrollPlaywrightBrowserstack.sln` in Visual Studio (or your IDE of choice)
11+
* Build the solution (`dotnet build`)
12+
* Replace the `userName` and `accessKey` placeholders in `browserstack.yml` with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings). Alternatively, remove those two lines from the yml and set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` as environment variables -- the SDK falls back to env vars only when the yml fields are absent
13+
14+
### Running your tests from CLI
15+
16+
```sh
17+
cd XunitReqnrollPlaywrightBrowserstack.Tests
18+
dotnet test
19+
```
20+
21+
The sample runs across both platforms declared in `browserstack.yml` (Windows 11 / Chrome and macOS / WebKit) in parallel.
22+
23+
Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github).
24+
25+
### Testing a private host (BrowserStack Local)
26+
27+
If your app lives on `localhost`, a staging host, or behind a firewall, set `browserstackLocal: true` in `browserstack.yml` and rerun `dotnet test`. The SDK starts and stops the BrowserStack Local tunnel for you -- no manual binary download or lifecycle management. Then point your scenarios at `http://bs-local.com:<port>/` (a hostname BrowserStack Local resolves to your machine) instead of a public URL.
28+
29+
## Integrate your test suite
30+
31+
This repository uses the BrowserStack SDK to run tests on BrowserStack. To wire the SDK into your own test suite:
32+
33+
* Create a `browserstack.yml` at the project root with your BrowserStack credentials and platform list (see this repo for a working template)
34+
* Add the `BrowserStack.TestAdapter` NuGet package:
35+
36+
```sh
37+
dotnet add package BrowserStack.TestAdapter
38+
```
39+
40+
* Build the project (`dotnet build`); the SDK installs the `browserstack-sdk` dotnet tool and patches the test assembly so Playwright launches are routed to BrowserStack at runtime
41+
42+
## How the SDK changes things
43+
44+
- **One `browserstack.yml`** declares platforms, parallelism, the Local toggle, and reporting; the SDK picks them up automatically
45+
- **The SDK runs platforms in parallel for you** -- one xUnit run per `(platform x parallelsPerPlatform)` cell, no per-platform branching needed
46+
- **The SDK rewrites Playwright launches** -- `Hooks/PlaywrightHooks.cs` calls `pw.Chromium.LaunchAsync()` and the SDK transparently redirects to the per-platform browser configured in the yml (`chrome` / `playwright-webkit` / `playwright-firefox` / etc.). No `Chromium.ConnectAsync(wss_url)` plumbing
47+
- **The SDK starts and stops BrowserStack Local** when `browserstackLocal: true` -- no manual tunnel lifecycle management
48+
- **Reqnroll generates xUnit test classes** from `.feature` files at build time, so behaviour-driven scenarios run as standard xUnit tests under `dotnet test`
49+
50+
## Repo layout
51+
52+
```
53+
.
54+
├── XunitReqnrollPlaywrightBrowserstack.sln
55+
└── XunitReqnrollPlaywrightBrowserstack.Tests/
56+
├── XunitReqnrollPlaywrightBrowserstack.Tests.csproj
57+
├── browserstack.yml # SDK config: credentials, platforms, Local toggle, reporting
58+
├── Features/
59+
│ └── Sample.feature # bstackdemo add-to-cart scenario
60+
├── StepDefinitions/
61+
│ └── SampleSteps.cs
62+
└── Hooks/
63+
└── PlaywrightHooks.cs # creates IPage per scenario; SDK routes the launch
64+
```
65+
66+
## Notes
67+
68+
* You can view your test results on the [BrowserStack Automate dashboard](https://www.browserstack.com/automate)
69+
* To test on a different set of browsers, see our [list of supported browsers and platforms](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate)
70+
* You can export the environment variables for the Username and Access Key of your BrowserStack account:
71+
72+
* For Unix-like or Mac machines:
73+
```sh
74+
export BROWSERSTACK_USERNAME=<browserstack-username> &&
75+
export BROWSERSTACK_ACCESS_KEY=<browserstack-access-key>
76+
```
77+
* For Windows Cmd:
78+
```cmd
79+
set BROWSERSTACK_USERNAME=<browserstack-username>
80+
set BROWSERSTACK_ACCESS_KEY=<browserstack-access-key>
81+
```
82+
* For Windows Powershell:
83+
```powershell
84+
$env:BROWSERSTACK_USERNAME=<browserstack-username>
85+
$env:BROWSERSTACK_ACCESS_KEY=<browserstack-access-key>
86+
```
87+
88+
## Further Reading
89+
90+
- [xUnit](https://xunit.net/)
91+
- [Reqnroll](https://reqnroll.net/)
92+
- [Playwright .NET](https://playwright.dev/dotnet/)
93+
- [BrowserStack documentation for Playwright in C#](https://www.browserstack.com/docs/automate/playwright/getting-started/c-sharp)
94+
- [BrowserStack.TestAdapter on NuGet](https://www.nuget.org/packages/BrowserStack.TestAdapter)
95+
- [NUnit reference sample](https://github.com/browserstack/csharp-playwright-browserstack)
96+
97+
Happy Testing!
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Feature: BStackDemo cart
2+
3+
As a shopper on bstackdemo.com
4+
I want to add an item to my cart
5+
So that I can verify the cart shows what I picked
6+
7+
Scenario: Add the first item to cart
8+
Given I open the bstackdemo home page
9+
When I add the first product to the cart
10+
Then the cart shows 1 item that matches the product I added
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.Playwright;
2+
using Reqnroll;
3+
4+
namespace XunitReqnrollPlaywrightBrowserstack.Tests.Hooks;
5+
6+
[Binding]
7+
public class PlaywrightHooks
8+
{
9+
private readonly ScenarioContext _scenario;
10+
private IPlaywright? _pw;
11+
private IBrowser? _browser;
12+
13+
public PlaywrightHooks(ScenarioContext scenario) => _scenario = scenario;
14+
15+
[BeforeScenario]
16+
public async Task SetUp()
17+
{
18+
_pw = await Playwright.CreateAsync();
19+
_browser = await _pw.Chromium.LaunchAsync();
20+
var context = await _browser.NewContextAsync();
21+
var page = await context.NewPageAsync();
22+
_scenario.Set(page, "page");
23+
}
24+
25+
[AfterScenario]
26+
public async Task TearDown()
27+
{
28+
if (_browser is not null) await _browser.CloseAsync();
29+
_pw?.Dispose();
30+
}
31+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.Playwright;
2+
using Reqnroll;
3+
4+
namespace XunitReqnrollPlaywrightBrowserstack.Tests.StepDefinitions;
5+
6+
[Binding]
7+
public class SampleSteps
8+
{
9+
private readonly ScenarioContext _scenario;
10+
private IPage Page => _scenario.Get<IPage>("page");
11+
private string _productTitle = string.Empty;
12+
13+
public SampleSteps(ScenarioContext scenario) => _scenario = scenario;
14+
15+
[Given(@"I open the bstackdemo home page")]
16+
public async Task OpenHome()
17+
{
18+
await Page.GotoAsync("https://bstackdemo.com/");
19+
}
20+
21+
[When(@"I add the first product to the cart")]
22+
public async Task AddFirstProduct()
23+
{
24+
var firstProduct = Page.Locator("[id=\"\\31 \"]");
25+
var titles = await firstProduct.Locator(".shelf-item__title").AllInnerTextsAsync();
26+
_productTitle = titles[0];
27+
await firstProduct.GetByText("Add to Cart").ClickAsync();
28+
}
29+
30+
[Then(@"the cart shows 1 item that matches the product I added")]
31+
public async Task CartHasOneMatchingItem()
32+
{
33+
var quantity = await Page.Locator(".bag__quantity").InnerTextAsync();
34+
Assert.Equal("1", quantity);
35+
36+
var cartTitle = await Page.Locator(".shelf-item__details").Locator(".title").InnerTextAsync();
37+
Assert.Equal(_productTitle, cartTitle);
38+
}
39+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="BrowserStack.TestAdapter" Version="0.*" />
14+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16+
<PackageReference Include="Microsoft.Playwright" Version="*" />
17+
<PackageReference Include="Reqnroll.xUnit" Version="3.3.4" />
18+
<PackageReference Include="xunit" Version="2.9.2" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
<PrivateAssets>all</PrivateAssets>
22+
</PackageReference>
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<Using Include="Xunit" />
27+
</ItemGroup>
28+
29+
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# =============================
2+
# Set BrowserStack Credentials
3+
# =============================
4+
# Replace the placeholders below with your real BrowserStack credentials,
5+
# or remove these two lines entirely and set BROWSERSTACK_USERNAME and
6+
# BROWSERSTACK_ACCESS_KEY as environment variables. NOTE: when these
7+
# fields are present in the yml, the SDK uses their literal values --
8+
# env vars override them only if the lines are absent.
9+
userName: YOUR_USERNAME
10+
accessKey: YOUR_ACCESS_KEY
11+
12+
# ======================
13+
# BrowserStack Reporting
14+
# ======================
15+
projectName: BrowserStack Samples
16+
buildName: xunit-reqnroll-playwright-browserstack
17+
buildIdentifier: '#${BUILD_NUMBER}'
18+
19+
# =======================================
20+
# Platforms (Browsers / Devices to test)
21+
# =======================================
22+
# Each entry is one cross-browser cell. The SDK runs `parallelsPerPlatform`
23+
# parallel sessions per entry. Customer code in Hooks/PlaywrightHooks.cs calls
24+
# `pw.Chromium.LaunchAsync()` -- the SDK transparently routes the launch to
25+
# the per-platform browser configured here at runtime.
26+
platforms:
27+
- os: Windows
28+
osVersion: 11
29+
browserName: chrome
30+
browserVersion: latest
31+
- os: OS X
32+
osVersion: Ventura
33+
browserName: playwright-webkit
34+
browserVersion: latest
35+
36+
parallelsPerPlatform: 1
37+
38+
# ===================================
39+
# BrowserStack Local (private hosts)
40+
# ===================================
41+
# Set to true to test localhost / staging hosts. The SDK starts and stops
42+
# the BrowserStack Local tunnel for you -- no manual binary management.
43+
browserstackLocal: false
44+
45+
# ===========
46+
# Diagnostics
47+
# ===========
48+
debug: false
49+
networkLogs: false
50+
consoleLogs: errors
51+
52+
# Identifier so BrowserStack can tag the sample source -- please leave as-is.
53+
source: xunit-reqnroll-playwright:sample-master:v1.0
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XunitReqnrollPlaywrightBrowserstack.Tests", "XunitReqnrollPlaywrightBrowserstack.Tests\XunitReqnrollPlaywrightBrowserstack.Tests.csproj", "{A6D6CB01-9FC5-4A8E-94E8-1C450639104D}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(SolutionProperties) = preSolution
14+
HideSolutionNode = FALSE
15+
EndGlobalSection
16+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
17+
{A6D6CB01-9FC5-4A8E-94E8-1C450639104D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{A6D6CB01-9FC5-4A8E-94E8-1C450639104D}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
{A6D6CB01-9FC5-4A8E-94E8-1C450639104D}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+
{A6D6CB01-9FC5-4A8E-94E8-1C450639104D}.Release|Any CPU.Build.0 = Release|Any CPU
21+
EndGlobalSection
22+
EndGlobal

0 commit comments

Comments
 (0)