Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/sanity-workflow.yml
Original file line number Diff line number Diff line change
@@ -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@v4
with:
ref: ${{ github.event.inputs.ref || github.ref }}

- name: Setup node
uses: actions/setup-node@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'
<!doctype html>
<html><head><title>BrowserStack Local Test</title></head>
<body>OK</body></html>
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
test-results
log/
package-lock.json
local.log
.DS_Store
browserstack.err
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,79 @@
# 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.

![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780)

## 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=<browserstack-username>
export BROWSERSTACK_ACCESS_KEY=<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)

For apps on `localhost`, a staging host, or behind a firewall, set `browserstackLocal: true` in `browserstack.yml`, then 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 test points at `http://bs-local.com:45454/`, a hostname BrowserStack Local resolves back to your machine.

## 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!
82 changes: 82 additions & 0 deletions browserstack.yml
Original file line number Diff line number Diff line change
@@ -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 # <boolean> (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: # <string> (Default: null) Needed if you need to run multiple instances of local.
# forceLocal: true # <boolean> (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-sdk:v1.0

# ===================
# Debugging features
# ===================
debug: false # <boolean> # Set to true if you need screenshots for every selenium command ran
networkLogs: false # <boolean> Set to true to enable HAR logs capturing
consoleLogs: errors # <string> 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
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -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,
};
7 changes: 7 additions & 0 deletions jest.local.config.js
Original file line number Diff line number Diff line change
@@ -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,
};
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
33 changes: 33 additions & 0 deletions tests/bstack_local_test.test.js
Original file line number Diff line number Diff line change
@@ -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:<port>/ -- 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');
});
});
41 changes: 41 additions & 0 deletions tests/bstack_sample.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading