diff --git a/.github/workflows/sanity-workflow.yml b/.github/workflows/sanity-workflow.yml
new file mode 100644
index 0000000..61bead9
--- /dev/null
+++ b/.github/workflows/sanity-workflow.yml
@@ -0,0 +1,65 @@
+# Sanity workflow that verifies the Jest + Playwright BrowserStack SDK sample
+# on a clean-network GitHub runner, mirroring browserstack/node-js-playwright-browserstack.
+# Runs the public bstackdemo sample test, then the BrowserStack Local test against a
+# python http.server harness on port 45454.
+
+name: Jest Playwright SDK sanity workflow on workflow_dispatch
+
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: 'Branch or commit to build (defaults to the triggering ref)'
+ required: false
+
+permissions:
+ contents: read
+
+jobs:
+ sanity:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ node: ['20']
+ name: Jest Playwright Sample (Node ${{ matrix.node }})
+ env:
+ BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
+ BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
+
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ ref: ${{ github.event.inputs.ref || github.ref }}
+
+ - name: Setup node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: ${{ matrix.node }}
+
+ - name: Strip credential placeholders so env vars take effect
+ # The yml ships with literal YOUR_USERNAME / YOUR_ACCESS_KEY placeholders;
+ # the SDK uses those literal values unless the lines are absent.
+ run: sed -i '/^userName:/d; /^accessKey:/d' browserstack.yml
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run sample test (public bstackdemo)
+ run: npm run sample-test
+
+ - name: Run local test (BrowserStack Local + python http.server harness)
+ run: |
+ set -u
+ mkdir -p "$RUNNER_TEMP/bs-local-harness"
+ cat > "$RUNNER_TEMP/bs-local-harness/index.html" <<'HTML'
+
+
BrowserStack Local Test
+ OK
+ HTML
+ ( cd "$RUNNER_TEMP/bs-local-harness" && python3 -m http.server 45454 ) &
+ HTTP_PID=$!
+ trap 'kill "$HTTP_PID" 2>/dev/null || true' EXIT
+ sleep 2
+ sed -i 's/^browserstackLocal: false.*/browserstackLocal: true/' browserstack.yml
+ npm run sample-local-test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..748011b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules
+test-results
+log/
+package-lock.json
+local.log
+.DS_Store
+browserstack.err
diff --git a/README.md b/README.md
index ac60a6e..9c3af14 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,94 @@
# jest-playwright-browserstack
-Sample repo for customers
+
+This sample shows how to run [Jest](https://jestjs.io/) + [Playwright](https://playwright.dev/) tests on BrowserStack using the [BrowserStack Node SDK](https://www.npmjs.com/package/browserstack-node-sdk). The SDK reads `browserstack.yml`, fans your tests out across the platforms listed there, starts and stops BrowserStack Local automatically, and reports test status to the BrowserStack dashboard. Your test code stays plain `@playwright/test` + Jest -- no manual `connect()`, no capabilities in code.
+
+
+
+## Prerequisites
+
+* [Node.js](https://nodejs.org/) 18, 20, or 22 LTS, and npm (verified on Node 20)
+* A BrowserStack account -- grab your [Username and Access Key](https://www.browserstack.com/accounts/settings)
+
+## Setup
+
+* Clone the repo
+* Install dependencies:
+
+ ```sh
+ npm install
+ ```
+
+* Add your credentials to `browserstack.yml` (replace `YOUR_USERNAME` / `YOUR_ACCESS_KEY`), or remove those two lines and export them as environment variables instead:
+
+ ```sh
+ export BROWSERSTACK_USERNAME=
+ export BROWSERSTACK_ACCESS_KEY=
+ ```
+
+## Run Sample Test
+
+Runs the public [bstackdemo.com](https://bstackdemo.com/) add-to-cart scenario across every platform in `browserstack.yml`:
+
+```sh
+npm run sample-test
+```
+
+## Run Local Test (BrowserStack Local)
+
+Use this when the site under test is on `localhost`, a staging host, or behind a firewall. BrowserStack Local opens a secure tunnel and resolves `bs-local.com` back to **your** machine, so the cloud browser can reach a page only you can serve.
+
+The bundled local test navigates to `http://bs-local.com:45454/` and asserts the page title contains `BrowserStack Local` — so you must have something serving that page on port `45454` first:
+
+1. Serve a matching page locally (any page whose `` contains "BrowserStack Local" satisfies the test):
+
+ ```sh
+ mkdir -p bs-local-site && printf 'BrowserStack LocalOK' > bs-local-site/index.html
+ (cd bs-local-site && python3 -m http.server 45454) &
+ ```
+
+ To test **your own** app instead, serve it on port `45454` (or change the port and the title assertion in `tests/bstack_local_test.test.js`).
+
+2. Set `browserstackLocal: true` in `browserstack.yml`.
+
+3. Run:
+
+ ```sh
+ npm run sample-local-test
+ ```
+
+The SDK starts and stops the BrowserStack Local tunnel for you -- no manual binary download or lifecycle management. The tunnel routes `bs-local.com:45454` to your machine's `localhost:45454`.
+
+## How the SDK changes things
+
+- **One `browserstack.yml`** declares platforms, parallelism, the Local toggle, and reporting; the SDK picks them up automatically.
+- **The SDK runs platforms in parallel for you** -- one Jest run per `(platform x parallelsPerPlatform)` cell, no per-platform branching needed.
+- **The SDK rewrites Playwright launches** -- your test calls `chromium.launch()` and the SDK transparently redirects it to the per-platform browser configured in the yml (`chrome` / `playwright-webkit` / `playwright-firefox`). No `chromium.connect(wss_url)` plumbing.
+- **The SDK starts and stops BrowserStack Local** when `browserstackLocal: true`.
+
+## Repo layout
+
+```
+.
+├── browserstack.yml # SDK config: credentials, platforms, Local toggle, reporting
+├── package.json # SDK run scripts + deps
+├── jest.config.js # sample test config (bstackdemo)
+├── jest.local.config.js # local test config (bs-local)
+└── tests/
+ ├── bstack_sample.test.js # bstackdemo add-to-cart scenario
+ └── bstack_local_test.test.js # BrowserStack Local scenario
+```
+
+## Notes
+
+* View your test results on the [BrowserStack Automate dashboard](https://www.browserstack.com/automate).
+* 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).
+* Understand how many parallel sessions you need with the [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github).
+
+## Further Reading
+
+- [Jest](https://jestjs.io/)
+- [Playwright](https://playwright.dev/)
+- [BrowserStack documentation for Playwright](https://www.browserstack.com/docs/automate/playwright)
+- [BrowserStack Node SDK](https://www.npmjs.com/package/browserstack-node-sdk)
+
+Happy Testing!
diff --git a/browserstack.yml b/browserstack.yml
new file mode 100644
index 0000000..a76fdf5
--- /dev/null
+++ b/browserstack.yml
@@ -0,0 +1,82 @@
+# =============================
+# Set BrowserStack Credentials
+# =============================
+# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and
+# BROWSERSTACK_ACCESS_KEY as env variables
+userName: YOUR_USERNAME
+accessKey: YOUR_ACCESS_KEY
+
+# ======================
+# BrowserStack Reporting
+# ======================
+# The following capabilities are used to set up reporting on BrowserStack:
+# Set 'projectName' to the name of your project. Example, Marketing Website
+projectName: BrowserStack Samples
+# Set `buildName` as the name of the job / testsuite being run
+buildName: jest-playwright-browserstack
+# `buildIdentifier` is a unique id to differentiate every execution that gets appended to
+# buildName. Choose your buildIdentifier format from the available expressions:
+# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution
+# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30
+# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests
+buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression}
+
+# =======================================
+# Platforms (Browsers / Devices to test)
+# =======================================
+# Platforms object contains all the browser / device combinations you want to test on.
+# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate)
+# Your Jest tests call `chromium.launch()` unconditionally -- the SDK transparently
+# routes the launch to the per-platform browser configured here at runtime.
+platforms:
+ - os: Windows
+ osVersion: 11
+ browserName: chrome
+ browserVersion: latest
+ - os: OS X
+ osVersion: Ventura
+ browserName: playwright-webkit
+ browserVersion: latest
+ - os: Windows
+ osVersion: 11
+ browserName: playwright-firefox
+ browserVersion: latest
+
+# =======================
+# Parallels per Platform
+# =======================
+# The number of parallel threads to be used for each platform set.
+# BrowserStack's SDK runner will select the best strategy based on the configured value
+#
+# Example 1 - If you have configured 3 platforms and set `parallelsPerPlatform` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
+#
+# Example 2 - If you have configured 1 platform and set `parallelsPerPlatform` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
+parallelsPerPlatform: 1
+
+# ==========================================
+# BrowserStack Local
+# (For localhost, staging/private websites)
+# ==========================================
+# Set browserstackLocal to true if your website under test is not accessible publicly over the internet
+# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction
+browserstackLocal: false # (Default false). Set to true to run the local sample test.
+# browserStackLocalOptions:
+# Options to be passed to BrowserStack local in-case of advanced configurations
+ # localIdentifier: # (Default: null) Needed if you need to run multiple instances of local.
+ # forceLocal: true # (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel.
+ # Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections
+
+framework: playwright
+source: jest-playwright-browserstack:sample-main:v1.0
+
+# ===================
+# Debugging features
+# ===================
+debug: false # # Set to true if you need screenshots for every selenium command ran
+networkLogs: false # Set to true to enable HAR logs capturing
+consoleLogs: errors # Remote browser's console debug levels to be printed (Default: errors)
+# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors)
+
+# Test Reporting and Analytics is an intelligent test reporting & debugging product. It collects data using the SDK.
+# Visit automation.browserstack.com to see your test reports and insights. To disable, set `testReporting: false`.
+testReporting: true
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..987e977
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,8 @@
+// Sample config -- runs the public bstackdemo test.
+// The BrowserStack SDK (invoked via `browserstack-node-sdk jest`) reads
+// browserstack.yml, fans this run out across the platforms declared there,
+// and routes each Playwright `chromium.launch()` to BrowserStack.
+module.exports = {
+ testMatch: ['**/tests/bstack_sample*.test.js'],
+ testTimeout: 90 * 1000,
+};
diff --git a/jest.local.config.js b/jest.local.config.js
new file mode 100644
index 0000000..3da89a9
--- /dev/null
+++ b/jest.local.config.js
@@ -0,0 +1,7 @@
+// Local config -- runs the BrowserStack Local test.
+// Set `browserstackLocal: true` in browserstack.yml and the SDK starts/stops
+// the tunnel for you so the cloud browser can reach a host only your machine serves.
+module.exports = {
+ testMatch: ['**/tests/bstack_local*.test.js'],
+ testTimeout: 90 * 1000,
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6f8a926
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "jest-playwright-browserstack",
+ "version": "1.0.0",
+ "description": "Sample showing how to run Jest + Playwright tests on BrowserStack using the BrowserStack Node SDK",
+ "main": "index.js",
+ "scripts": {
+ "sample-test": "browserstack-node-sdk jest --config=jest.config.js",
+ "sample-local-test": "browserstack-node-sdk jest --config=jest.local.config.js",
+ "postinstall": "npm update browserstack-node-sdk"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@playwright/test": "latest",
+ "browserstack-node-sdk": "latest",
+ "jest": "latest"
+ }
+}
diff --git a/tests/bstack_local_test.test.js b/tests/bstack_local_test.test.js
new file mode 100644
index 0000000..eaa1444
--- /dev/null
+++ b/tests/bstack_local_test.test.js
@@ -0,0 +1,33 @@
+const { chromium } = require('@playwright/test');
+
+// Local test: with `browserstackLocal: true` in browserstack.yml the SDK opens a
+// tunnel, so the cloud browser can reach http://bs-local.com:/ -- a host that
+// only your machine serves. bs-local.com is resolved to your machine by the tunnel.
+describe('BStackLocalSample', () => {
+ let browser;
+ let context;
+ let page;
+
+ beforeAll(async () => {
+ browser = await chromium.launch();
+ });
+
+ afterAll(async () => {
+ if (browser) await browser.close();
+ });
+
+ beforeEach(async () => {
+ context = await browser.newContext();
+ page = await context.newPage();
+ });
+
+ afterEach(async () => {
+ if (context) await context.close();
+ });
+
+ test('reach a private host via BrowserStack Local', async () => {
+ await page.goto('http://bs-local.com:45454/');
+ const title = await page.title();
+ expect(title).toContain('BrowserStack Local');
+ });
+});
diff --git a/tests/bstack_sample.test.js b/tests/bstack_sample.test.js
new file mode 100644
index 0000000..113a68e
--- /dev/null
+++ b/tests/bstack_sample.test.js
@@ -0,0 +1,41 @@
+const { chromium } = require('@playwright/test');
+
+// Sample test: add the first product to the cart on bstackdemo.com and verify.
+// Your code calls `chromium.launch()` as usual -- the BrowserStack SDK transparently
+// routes the launch to the per-platform browser configured in browserstack.yml.
+describe('BStackDemo cart', () => {
+ let browser;
+ let context;
+ let page;
+
+ beforeAll(async () => {
+ browser = await chromium.launch();
+ });
+
+ afterAll(async () => {
+ if (browser) await browser.close();
+ });
+
+ beforeEach(async () => {
+ context = await browser.newContext();
+ page = await context.newPage();
+ });
+
+ afterEach(async () => {
+ if (context) await context.close();
+ });
+
+ test('add the first product to the cart', async () => {
+ await page.goto('https://bstackdemo.com/');
+
+ const firstProduct = page.locator('[id="1"]');
+ const productTitle = await firstProduct.locator('.shelf-item__title').first().innerText();
+ await firstProduct.getByText('Add to Cart').click();
+
+ const quantity = await page.locator('.bag__quantity').innerText();
+ expect(quantity).toBe('1');
+
+ const cartTitle = await page.locator('.shelf-item__details .title').innerText();
+ expect(cartTitle).toBe(productTitle);
+ });
+});