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. + +![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= + 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 '<!doctype html><title>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); + }); +});