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 Cucumber-JS + Playwright BrowserStack SDK sample
# on a clean-network GitHub runner, mirroring browserstack/node-js-playwright-browserstack.
# Runs the public bstackdemo `sample` profile, then the BrowserStack Local `local`
# profile against a python http.server harness on port 45454.

name: Cucumber-JS 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: Cucumber-JS 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
85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,85 @@
# cucumber-js-playwright-browserstack
Creating a sample repo for different Playwright languages and runners

This sample shows how to run [Cucumber-JS](https://github.com/cucumber/cucumber-js) + [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 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 plain `@playwright/test` + `@cucumber/cucumber` -- 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)

> **Note on the Cucumber version:** this sample pins `@cucumber/cucumber` to **v11**, which supports Node 18/20/22. Cucumber **v12** (`latest`) requires **Node 22+** (it uses `fs/promises.glob`) and will fail on Node 18/20 with `promises_1.glob is not a function`. Bump to v12 only if you are on Node 22+.

## 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 (the `sample` Cucumber profile) 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 the `local` profile:

```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 scenario 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 Cucumber run per `(platform x parallelsPerPlatform)` cell, no per-platform branching needed.
- **The SDK rewrites Playwright launches** -- `features/support/hooks.js` 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
├── cucumber.js # Cucumber profiles: `sample` (bstackdemo) and `local` (bs-local)
└── features/
├── sample.feature # bstackdemo add-to-cart scenario
├── local.feature # BrowserStack Local scenario
├── step_definitions/
│ ├── sample_steps.js
│ └── local_steps.js
└── support/
└── hooks.js # launches Playwright per scenario; SDK routes the launch
```

## 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

- [Cucumber-JS](https://github.com/cucumber/cucumber-js)
- [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: cucumber-js-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 step definitions call `chromium.launch()` unconditionally (see features/support/hooks.js)
# -- the SDK transparently routes the launch to the per-platform browser configured here.
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: cucumber-js-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
19 changes: 19 additions & 0 deletions cucumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Cucumber-JS profiles. The BrowserStack SDK (invoked via `browserstack-node-sdk
// cucumber-js`) reads browserstack.yml, fans each profile out across the platforms
// declared there, and routes the Playwright `chromium.launch()` in support/hooks.js
// to BrowserStack.
//
// npm run sample-test -> --profile sample (public bstackdemo)
// npm run sample-local-test -> --profile local (BrowserStack Local)
const common = ['features/support/hooks.js'];

module.exports = {
sample: {
paths: ['features/sample.feature'],
require: ['features/step_definitions/sample_steps.js', ...common],
},
local: {
paths: ['features/local.feature'],
require: ['features/step_definitions/local_steps.js', ...common],
},
};
9 changes: 9 additions & 0 deletions features/local.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature: BStackLocalSample

As a developer testing a private host
I want BrowserStack Local to tunnel my localhost to BrowserStack
So that the cloud browser can reach a page only my machine can serve

Scenario: Reach a private host via BrowserStack Local
Given I open the local sample page on bs-local
Then the local sample page title contains "BrowserStack Local"
10 changes: 10 additions & 0 deletions features/sample.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Feature: BStackDemo cart

As a shopper on bstackdemo.com
I want to add an item to my cart
So that I can verify the cart shows what I picked

Scenario: Add the first item to cart
Given I open the bstackdemo home page
When I add the first product to the cart
Then the cart shows 1 item that matches the product I added
13 changes: 13 additions & 0 deletions features/step_definitions/local_steps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { Given, Then } = require('@cucumber/cucumber');
const assert = require('assert');

// With `browserstackLocal: true` in browserstack.yml the SDK opens a tunnel, so the
// cloud browser can reach http://bs-local.com:<port>/ -- a host only your machine serves.
Given('I open the local sample page on bs-local', async function () {
await this.page.goto('http://bs-local.com:45454/');
});

Then('the local sample page title contains {string}', async function (expected) {
const title = await this.page.title();
assert.ok(title.includes(expected), `expected title to contain "${expected}" but was "${title}"`);
});
22 changes: 22 additions & 0 deletions features/step_definitions/sample_steps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');

let productTitle;

Given('I open the bstackdemo home page', async function () {
await this.page.goto('https://bstackdemo.com/');
});

When('I add the first product to the cart', async function () {
const firstProduct = this.page.locator('[id="1"]');
productTitle = await firstProduct.locator('.shelf-item__title').first().innerText();
await firstProduct.getByText('Add to Cart').click();
});

Then('the cart shows 1 item that matches the product I added', async function () {
const quantity = await this.page.locator('.bag__quantity').innerText();
assert.strictEqual(quantity, '1');

const cartTitle = await this.page.locator('.shelf-item__details .title').innerText();
assert.strictEqual(cartTitle, productTitle);
});
28 changes: 28 additions & 0 deletions features/support/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { Before, After, BeforeAll, AfterAll, setDefaultTimeout } = require('@cucumber/cucumber');
const { chromium } = require('@playwright/test');

// Cucumber steps can take longer than the default 5s on a remote browser.
setDefaultTimeout(60 * 1000);

let browser;

// Your code calls `chromium.launch()` as usual -- the BrowserStack SDK transparently
// routes the launch to the per-platform browser configured in browserstack.yml.
BeforeAll(async () => {
browser = await chromium.launch();
});

AfterAll(async () => {
if (browser) await browser.close();
});

// A fresh context + page per scenario; `this` is the Cucumber World shared with steps.
Before(async function () {
this.context = await browser.newContext();
this.page = await this.context.newPage();
});

After(async function () {
if (this.page) await this.page.close();
if (this.context) await this.context.close();
});
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "cucumber-js-playwright-browserstack",
"version": "1.0.0",
"description": "Sample showing how to run Cucumber-JS + Playwright tests on BrowserStack using the BrowserStack Node SDK",
"main": "index.js",
"scripts": {
"sample-test": "browserstack-node-sdk cucumber-js --profile sample",
"sample-local-test": "browserstack-node-sdk cucumber-js --profile local",
"postinstall": "npm update browserstack-node-sdk"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@cucumber/cucumber": "^11.0.0",
"@playwright/test": "latest",
"browserstack-node-sdk": "latest"
}
}
Loading