From 2ff9314ce85ef6952320b53ba13272956a21bec5 Mon Sep 17 00:00:00 2001 From: Jimesh Chokshi Date: Mon, 4 May 2026 17:18:47 +0530 Subject: [PATCH 1/7] Add BrowserStack SDK + Playwright behave sample Provides customers with a working starting point for running behave tests on BrowserStack via Playwright Python and the BrowserStack Python SDK. Mirrors the shape of browserstack/cucumber-ruby-browserstack (browserstack-sdk-selenium-4 branch) but adapted for behave + Playwright: - One browserstack.yml at root declares 3 platforms covering all 3 Playwright browser engines (chromium / firefox / webkit). The SDK monkeypatches Playwright launches and routes to the per-platform browser, so customer code uses chromium.launch() unchanged across all three. - Alternate configs under config/ for single-platform and BrowserStack Local modes; Makefile swaps them in around the SDK call and restores the parallel default afterwards (even on failure). - Local mode self-tests: serves a small static page on :45454 from features/local-html/ and routes a localhost scenario through the BS Local tunnel that the SDK starts/stops automatically. - GitHub Actions workflow uses workflow_dispatch + commit_sha + check-status reporting, mirroring the cucumber-java-playwright-browserstack pattern. - Playwright pinned to ==1.49.0 because PW >=1.50 trips an SDK monkeypatch arg that no longer matches (`unexpected keyword argument 'artifactsDir'`). Verified end-to-end against BrowserStack: - make parallel: 3 sessions (chrome/pw-firefox/pw-webkit), all passed - make single: 1 chrome session, passed - make local: 1 session via Local tunnel against localhost:45454, passed Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build.yml | 90 ++++++++++++++++++++++++++++++++++ .gitignore | 6 +++ Makefile | 40 +++++++++++++++ README.md | 59 +++++++++++++++++++++- browserstack.yml | 61 +++++++++++++++++++++++ config/browserstack.local.yml | 28 +++++++++++ config/browserstack.single.yml | 25 ++++++++++ features/environment.py | 18 +++++++ features/local-html/index.html | 11 +++++ features/local.feature | 5 ++ features/sample.feature | 6 +++ features/steps/local_steps.py | 18 +++++++ features/steps/sample_steps.py | 27 ++++++++++ requirements.txt | 7 +++ 14 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 browserstack.yml create mode 100644 config/browserstack.local.yml create mode 100644 config/browserstack.single.yml create mode 100644 features/environment.py create mode 100644 features/local-html/index.html create mode 100644 features/local.feature create mode 100644 features/sample.feature create mode 100644 features/steps/local_steps.py create mode 100644 features/steps/sample_steps.py create mode 100644 requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..76ee014 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,90 @@ +# Runs the behave + Playwright sample against BrowserStack SDK on workflow_dispatch. +# Mirrors the pattern from browserstack/cucumber-java-playwright-browserstack: +# triggered manually with a commit SHA, posts check statuses back to that SHA so +# results show up on PRs without binding to push/pull_request triggers. + +name: Behave Playwright SDK Test workflow on workflow_dispatch + +on: + workflow_dispatch: + inputs: + commit_sha: + description: 'The full commit id to build' + required: true + +permissions: + contents: read + +jobs: + sample-run: + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + name: Behave Playwright Sample + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.commit_sha }} + + - uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 + id: status-check-in-progress + env: + job_name: Behave Playwright Sample + commit_sha: ${{ github.event.inputs.commit_sha }} + with: + github-token: ${{ github.token }} + script: | + const result = await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: process.env.job_name, + head_sha: process.env.commit_sha, + status: 'in_progress' + }).catch((err) => ({status: err.status, response: err.response})); + console.log(`The status-check response : ${result.status} Response : ${JSON.stringify(result.response)}`) + if (result.status !== 201) { + console.log('Failed to create check run') + } + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: pip + + - name: Install dependencies + run: make install + + - name: Run parallel sample on BrowserStack + run: make parallel + + - name: Run local sample on BrowserStack + run: make local + + - if: always() + uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 + id: status-check-completed + env: + conclusion: ${{ job.status }} + job_name: Behave Playwright Sample + commit_sha: ${{ github.event.inputs.commit_sha }} + with: + github-token: ${{ github.token }} + script: | + const result = await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: process.env.job_name, + head_sha: process.env.commit_sha, + status: 'completed', + conclusion: process.env.conclusion + }).catch((err) => ({status: err.status, response: err.response})); + console.log(`The status-check response : ${result.status} Response : ${JSON.stringify(result.response)}`) + if (result.status !== 201) { + console.log('Failed to create check run') + } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cb5ab2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.venv/ +__pycache__/ +*.pyc +browserstack.yml.bak +log/ +local.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d926860 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# Makefile for behave + Playwright + BrowserStack SDK sample. +# +# The SDK only reads ./browserstack.yml, so the `single` and `local` targets +# swap in an alternate config from config/ for the duration of the run and +# restore the parallel default afterwards (even on failure). + +PARALLEL_CMD := browserstack-sdk behave features/sample.feature +LOCAL_CMD := browserstack-sdk behave features/local.feature + +.PHONY: parallel single local install + +# Default: parallel run uses the committed browserstack.yml (3 platforms). +parallel: + $(PARALLEL_CMD) + +# Single-platform run: swap in config/browserstack.single.yml. +single: + @cp browserstack.yml browserstack.yml.bak; \ + cp config/browserstack.single.yml browserstack.yml; \ + $(PARALLEL_CMD); status=$$?; \ + mv browserstack.yml.bak browserstack.yml; \ + exit $$status + +# Local-tunnel run: serve a small HTML page on :45454 and point a localhost +# scenario at it. The SDK starts/stops the BrowserStack Local tunnel because +# the alternate config sets `browserstackLocal: true`. +local: + @cp browserstack.yml browserstack.yml.bak; \ + cp config/browserstack.local.yml browserstack.yml; \ + python3 -m http.server 45454 --directory features/local-html >/tmp/bspl-local-server.log 2>&1 & \ + server_pid=$$!; \ + sleep 1; \ + $(LOCAL_CMD); status=$$?; \ + kill $$server_pid 2>/dev/null; \ + mv browserstack.yml.bak browserstack.yml; \ + exit $$status + +install: + pip install -r requirements.txt + playwright install chromium diff --git a/README.md b/README.md index 36bd3fb..2f0495e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ -# behave-playwright-browserstack -Sample repo for customers +behave-playwright-browserstack (BrowserStack SDK + Playwright) +=============================================================== + +This repo shows how to run [behave](https://behave.readthedocs.io/) tests on BrowserStack using the [BrowserStack Python SDK](https://pypi.org/project/browserstack-sdk/) and [Playwright Python](https://playwright.dev/python/). The SDK handles capability injection, BrowserStack routing for Playwright launches, parallelization, and BrowserStack Local for you — you describe platforms once in `browserstack.yml` and run the test command unchanged. + +## Setup +* Clone this repo +* Install dependencies (creates the BrowserStack SDK CLI on `PATH` and downloads Playwright Chromium) + ```sh + pip install -r requirements.txt + playwright install chromium + ``` + Or simply: `make install` +* Set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` as environment variables, or replace `userName` and `accessKey` directly in `browserstack.yml` (and the variants under `config/`) with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings). Env vars take precedence. + +### Running your tests +* Run tests in parallel across the 3 Playwright browser engines (chromium / firefox / webkit): `make parallel` +* Run a single-platform test: `make single` +* Run with the BrowserStack Local tunnel against a local HTTP server: `make local` + +Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github). + +Alternatively the variables can be set in the environment using env or your CI framework (like GitHub Actions or Jenkins). See `.github/workflows/build.yml` for a GitHub Actions example — it runs on `workflow_dispatch` (manual trigger) with a commit SHA input and posts a status check back to that commit. + +### How the SDK changes things +- **One `browserstack.yml`** declares platforms; the SDK picks them up automatically — no per-task config switching inside `environment.py`. +- **The SDK runs platforms in parallel for you** — no hand-rolled parallel runner; the SDK forks one behave run per `(platform × parallelsPerPlatform)` cell. +- **The SDK monkeypatches Playwright's browser launches** — the test code calls `chromium.launch()` and the SDK transparently routes the launch to the per-platform browser configured in `browserstack.yml` (chromium, firefox, or webkit). No `chromium.connect(wss_url)` plumbing is needed in customer code. +- **The SDK starts and stops BrowserStack Local** when `browserstackLocal: true` — no manual tunnel lifecycle management. +- **The CLI is `browserstack-sdk behave …`** — the `make` targets shell out to that. + +### Repo layout +``` +. +├── browserstack.yml # 3-platform parallel default +├── config/ +│ ├── browserstack.single.yml # 1 platform +│ └── browserstack.local.yml # 1 platform + browserstackLocal: true +├── requirements.txt +├── Makefile +└── features/ + ├── sample.feature # bstackdemo add-to-cart scenario + ├── local.feature # localhost scenario for the Local tunnel + ├── local-html/index.html # static page served on :45454 by `make local` + ├── environment.py # behave hooks: launch browser, hand to context.page + └── steps/ + ├── sample_steps.py + └── local_steps.py +``` + +### Further Reading +- [behave](https://behave.readthedocs.io/) +- [Playwright Python](https://playwright.dev/python/) +- [BrowserStack documentation for Playwright](https://www.browserstack.com/docs/automate/playwright) +- [BrowserStack Python SDK on PyPI](https://pypi.org/project/browserstack-sdk/) + +Happy Testing! diff --git a/browserstack.yml b/browserstack.yml new file mode 100644 index 0000000..55fd0f1 --- /dev/null +++ b/browserstack.yml @@ -0,0 +1,61 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and accessKey here, or set the +# BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY environment +# variables. Env vars take precedence over the values in this file. +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +projectName: BrowserStack Samples +buildName: behave-playwright-sdk-build-1 +# `buildIdentifier` is a unique id appended to buildName for every run. +# ${BUILD_NUMBER} (default) is an incremental counter; ${DATE_TIME} is a timestamp. +buildIdentifier: '#${BUILD_NUMBER}' + +# `framework` lets the SDK send test context (test name, status) to BrowserStack. +framework: behave + +# ======================================= +# Platforms (Browsers / Devices to test) +# ======================================= +# Each entry below is one cross-browser cell. The SDK runs `parallelsPerPlatform` +# parallel sessions per entry, so the three entries below x 1 = 3 parallel sessions. +# These three browsers map 1:1 to the three Playwright engine families (chromium / +# firefox / webkit). The customer code in features/environment.py calls +# `chromium.launch()` — the SDK transparently routes the launch to the correct +# per-platform browser at runtime, so no per-platform branching is needed. +platforms: + - os: Windows + osVersion: 11 + browserName: chrome + browserVersion: latest + - os: Windows + osVersion: 11 + browserName: playwright-firefox + browserVersion: latest + - os: OS X + osVersion: Sonoma + browserName: playwright-webkit + browserVersion: latest + +parallelsPerPlatform: 1 + +# =================================== +# BrowserStack Local (private hosts) +# =================================== +# Default false in this config. The local-mode example sets this true. +browserstackLocal: false + +# =========== +# Diagnostics +# =========== +debug: true +networkLogs: false +consoleLogs: errors + +# Identifier so BrowserStack can tag the sample source — please leave as-is. +source: behave-playwright-sdk:sample-master:v1.0 diff --git a/config/browserstack.local.yml b/config/browserstack.local.yml new file mode 100644 index 0000000..98e54ee --- /dev/null +++ b/config/browserstack.local.yml @@ -0,0 +1,28 @@ +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +projectName: BrowserStack Samples +buildName: behave-playwright-sdk-build-1 +buildIdentifier: '#${BUILD_NUMBER}' + +framework: behave + +# Single-platform run for the local-tunnel example. +platforms: + - os: Windows + osVersion: 11 + browserName: chrome + browserVersion: latest + +parallelsPerPlatform: 1 + +# BrowserStack Local on: the SDK starts/stops the tunnel for you, and the +# `make local` target serves a small HTML page on http://localhost:45454/ +# which the test scenario hits via the tunnel. +browserstackLocal: true + +debug: true +networkLogs: false +consoleLogs: errors + +source: behave-playwright-sdk:sample-master:v1.0 diff --git a/config/browserstack.single.yml b/config/browserstack.single.yml new file mode 100644 index 0000000..e4c6161 --- /dev/null +++ b/config/browserstack.single.yml @@ -0,0 +1,25 @@ +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +projectName: BrowserStack Samples +buildName: behave-playwright-sdk-build-1 +buildIdentifier: '#${BUILD_NUMBER}' + +framework: behave + +# Single-platform run: one chromium session, no parallelism. +platforms: + - os: Windows + osVersion: 11 + browserName: chrome + browserVersion: latest + +parallelsPerPlatform: 1 + +browserstackLocal: false + +debug: true +networkLogs: false +consoleLogs: errors + +source: behave-playwright-sdk:sample-master:v1.0 diff --git a/features/environment.py b/features/environment.py new file mode 100644 index 0000000..c3effc3 --- /dev/null +++ b/features/environment.py @@ -0,0 +1,18 @@ +from playwright.sync_api import sync_playwright + + +def before_scenario(context, scenario): + # Customer code calls `chromium.launch()` directly. The BrowserStack SDK + # monkeypatches Playwright at runtime so this launch is routed to the + # browser configured in the platform entry the SDK is currently driving + # — works unchanged for chromium, firefox, and webkit platforms. + context.pw = sync_playwright().start() + context.browser = context.pw.chromium.launch() + context.page = context.browser.new_page() + + +def after_scenario(context, scenario): + try: + context.browser.close() + finally: + context.pw.stop() diff --git a/features/local-html/index.html b/features/local-html/index.html new file mode 100644 index 0000000..ad526a6 --- /dev/null +++ b/features/local-html/index.html @@ -0,0 +1,11 @@ + + + + + BrowserStack Local Sample + + +

BrowserStack Local Sample

+

If you can see this page from a BrowserStack session, the Local tunnel is working.

+ + diff --git a/features/local.feature b/features/local.feature new file mode 100644 index 0000000..e89be4b --- /dev/null +++ b/features/local.feature @@ -0,0 +1,5 @@ +Feature: Local sample + + Scenario: Can reach a localhost page through the BrowserStack Local tunnel + Given I visit the local sample page + Then I should see the local sample heading diff --git a/features/sample.feature b/features/sample.feature new file mode 100644 index 0000000..49a2f95 --- /dev/null +++ b/features/sample.feature @@ -0,0 +1,6 @@ +Feature: Browserstack test + + Scenario: Can add the product in cart + Given I visit bstackdemo website + When I add a product to the cart + Then I should see same product in cart section diff --git a/features/steps/local_steps.py b/features/steps/local_steps.py new file mode 100644 index 0000000..405fc61 --- /dev/null +++ b/features/steps/local_steps.py @@ -0,0 +1,18 @@ +from behave import given, then + + +LOCAL_URL = "http://localhost:45454/" +EXPECTED_HEADING = "BrowserStack Local Sample" + + +@given("I visit the local sample page") +def visit_local(context): + context.page.goto(LOCAL_URL) + + +@then("I should see the local sample heading") +def verify_local_heading(context): + heading = context.page.locator("h1").text_content() + assert heading == EXPECTED_HEADING, ( + f"expected {EXPECTED_HEADING!r}, got {heading!r}" + ) diff --git a/features/steps/sample_steps.py b/features/steps/sample_steps.py new file mode 100644 index 0000000..aaad462 --- /dev/null +++ b/features/steps/sample_steps.py @@ -0,0 +1,27 @@ +from behave import given, when, then + + +@given("I visit bstackdemo website") +def visit_bstackdemo(context): + context.page.goto("https://www.bstackdemo.com/") + assert context.page.title() == "StackDemo" + + +@when("I add a product to the cart") +def add_product(context): + product_locator = context.page.locator('xpath=//*[@id="1"]/p') + context.product_on_page_text = product_locator.text_content() + context.page.locator('xpath=//*[@id="1"]/div[4]').click() + + +@then("I should see same product in cart section") +def verify_cart(context): + cart = context.page.locator('xpath=//*[@class="float-cart__content"]') + cart.wait_for(state="visible") + cart_product_locator = context.page.locator( + 'xpath=//*[@id="__next"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]' + ) + product_on_cart_text = cart_product_locator.text_content() + assert product_on_cart_text == context.product_on_page_text, ( + f"expected {context.product_on_page_text!r} in cart, got {product_on_cart_text!r}" + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e2d9c8f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +browserstack-sdk>=1.46.0 +behave>=1.2.7 +# Pinned: the SDK monkeypatches Playwright internals, and PW >=1.50 changes +# the launch keyword args in a way the current SDK doesn't yet handle +# (`unexpected keyword argument 'artifactsDir'`). Keep at 1.49.0 until the +# SDK release notes call out a higher upper bound. +playwright==1.49.0 From 0f337d225c056bddf352d283d6d5e4f2566fe9c4 Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack Date: Thu, 7 May 2026 12:45:12 +0530 Subject: [PATCH 2/7] Simplify to one yml, drop Makefile / config / local-mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Karan's review (#1, #2, #4) pushed back on the per-mode config files and the Makefile-as-profile-switcher. Aligning with the canonical cucumber-ruby-browserstack shape (one feature run unchanged): - Drop Makefile and config/{single,local}.yml — the Python SDK reads only ./browserstack.yml so the swap was the workaround; aligning instead means one yml, one command. - Drop features/local.feature, local-html/, and local_steps.py. Customers needing the Local tunnel flip browserstackLocal: true and rerun the same command (documented in README). - Drop buildIdentifier from browserstack.yml — pure dashboard UX, not required, and Karan flagged it as noise. - CI workflow runs browserstack-sdk behave directly (no make targets). - README pruned to single-mode setup + Running + Local-toggle note. --- .github/workflows/build.yml | 11 +++++----- .gitignore | 1 - Makefile | 40 ---------------------------------- README.md | 34 +++++++++++++---------------- browserstack.yml | 6 ++--- config/browserstack.local.yml | 28 ------------------------ config/browserstack.single.yml | 25 --------------------- features/local-html/index.html | 11 ---------- features/local.feature | 5 ----- features/steps/local_steps.py | 18 --------------- 10 files changed, 22 insertions(+), 157 deletions(-) delete mode 100644 Makefile delete mode 100644 config/browserstack.local.yml delete mode 100644 config/browserstack.single.yml delete mode 100644 features/local-html/index.html delete mode 100644 features/local.feature delete mode 100644 features/steps/local_steps.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76ee014..34bb7be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,13 +58,12 @@ jobs: cache: pip - name: Install dependencies - run: make install + run: | + pip install -r requirements.txt + playwright install chromium - - name: Run parallel sample on BrowserStack - run: make parallel - - - name: Run local sample on BrowserStack - run: make local + - name: Run sample on BrowserStack + run: browserstack-sdk behave features/sample.feature - if: always() uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 diff --git a/.gitignore b/.gitignore index 6cb5ab2..39ea696 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .venv/ __pycache__/ *.pyc -browserstack.yml.bak log/ local.log diff --git a/Makefile b/Makefile deleted file mode 100644 index d926860..0000000 --- a/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -# Makefile for behave + Playwright + BrowserStack SDK sample. -# -# The SDK only reads ./browserstack.yml, so the `single` and `local` targets -# swap in an alternate config from config/ for the duration of the run and -# restore the parallel default afterwards (even on failure). - -PARALLEL_CMD := browserstack-sdk behave features/sample.feature -LOCAL_CMD := browserstack-sdk behave features/local.feature - -.PHONY: parallel single local install - -# Default: parallel run uses the committed browserstack.yml (3 platforms). -parallel: - $(PARALLEL_CMD) - -# Single-platform run: swap in config/browserstack.single.yml. -single: - @cp browserstack.yml browserstack.yml.bak; \ - cp config/browserstack.single.yml browserstack.yml; \ - $(PARALLEL_CMD); status=$$?; \ - mv browserstack.yml.bak browserstack.yml; \ - exit $$status - -# Local-tunnel run: serve a small HTML page on :45454 and point a localhost -# scenario at it. The SDK starts/stops the BrowserStack Local tunnel because -# the alternate config sets `browserstackLocal: true`. -local: - @cp browserstack.yml browserstack.yml.bak; \ - cp config/browserstack.local.yml browserstack.yml; \ - python3 -m http.server 45454 --directory features/local-html >/tmp/bspl-local-server.log 2>&1 & \ - server_pid=$$!; \ - sleep 1; \ - $(LOCAL_CMD); status=$$?; \ - kill $$server_pid 2>/dev/null; \ - mv browserstack.yml.bak browserstack.yml; \ - exit $$status - -install: - pip install -r requirements.txt - playwright install chromium diff --git a/README.md b/README.md index 2f0495e..94cfe71 100644 --- a/README.md +++ b/README.md @@ -10,42 +10,38 @@ This repo shows how to run [behave](https://behave.readthedocs.io/) tests on Bro pip install -r requirements.txt playwright install chromium ``` - Or simply: `make install` -* Set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` as environment variables, or replace `userName` and `accessKey` directly in `browserstack.yml` (and the variants under `config/`) with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings). Env vars take precedence. +* Set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` as environment variables, or replace `userName` and `accessKey` directly in `browserstack.yml` with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings). Env vars take precedence. ### Running your tests -* Run tests in parallel across the 3 Playwright browser engines (chromium / firefox / webkit): `make parallel` -* Run a single-platform test: `make single` -* Run with the BrowserStack Local tunnel against a local HTTP server: `make local` +Run the sample in parallel across the 3 Playwright browser engines (chromium / firefox / webkit) declared in `browserstack.yml`: + +```sh +browserstack-sdk behave features/sample.feature +``` Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github). Alternatively the variables can be set in the environment using env or your CI framework (like GitHub Actions or Jenkins). See `.github/workflows/build.yml` for a GitHub Actions example — it runs on `workflow_dispatch` (manual trigger) with a commit SHA input and posts a status check back to that commit. +### Testing a private host (BrowserStack Local) +If your app lives on `localhost`, a staging host, or behind a firewall, set `browserstackLocal: true` in `browserstack.yml` and rerun the same command. 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:/` (a hostname BrowserStack Local resolves to your machine) instead of a public URL. + ### How the SDK changes things -- **One `browserstack.yml`** declares platforms; the SDK picks them up automatically — no per-task config switching inside `environment.py`. +- **One `browserstack.yml`** declares platforms; the SDK picks them up automatically. - **The SDK runs platforms in parallel for you** — no hand-rolled parallel runner; the SDK forks one behave run per `(platform × parallelsPerPlatform)` cell. - **The SDK monkeypatches Playwright's browser launches** — the test code calls `chromium.launch()` and the SDK transparently routes the launch to the per-platform browser configured in `browserstack.yml` (chromium, firefox, or webkit). No `chromium.connect(wss_url)` plumbing is needed in customer code. -- **The SDK starts and stops BrowserStack Local** when `browserstackLocal: true` — no manual tunnel lifecycle management. -- **The CLI is `browserstack-sdk behave …`** — the `make` targets shell out to that. +- **The CLI is `browserstack-sdk behave …`** — wraps `behave` and injects the SDK at runtime. ### Repo layout ``` . -├── browserstack.yml # 3-platform parallel default -├── config/ -│ ├── browserstack.single.yml # 1 platform -│ └── browserstack.local.yml # 1 platform + browserstackLocal: true +├── browserstack.yml # SDK config: credentials, 3 parallel platforms, Local toggle, reporting ├── requirements.txt -├── Makefile └── features/ - ├── sample.feature # bstackdemo add-to-cart scenario - ├── local.feature # localhost scenario for the Local tunnel - ├── local-html/index.html # static page served on :45454 by `make local` - ├── environment.py # behave hooks: launch browser, hand to context.page + ├── sample.feature # bstackdemo add-to-cart scenario + ├── environment.py # behave hooks: launch browser, hand to context.page └── steps/ - ├── sample_steps.py - └── local_steps.py + └── sample_steps.py ``` ### Further Reading diff --git a/browserstack.yml b/browserstack.yml index 55fd0f1..3608fd1 100644 --- a/browserstack.yml +++ b/browserstack.yml @@ -12,9 +12,6 @@ accessKey: YOUR_ACCESS_KEY # ====================== projectName: BrowserStack Samples buildName: behave-playwright-sdk-build-1 -# `buildIdentifier` is a unique id appended to buildName for every run. -# ${BUILD_NUMBER} (default) is an incremental counter; ${DATE_TIME} is a timestamp. -buildIdentifier: '#${BUILD_NUMBER}' # `framework` lets the SDK send test context (test name, status) to BrowserStack. framework: behave @@ -47,7 +44,8 @@ parallelsPerPlatform: 1 # =================================== # BrowserStack Local (private hosts) # =================================== -# Default false in this config. The local-mode example sets this true. +# Set to true to test localhost / staging hosts. The SDK starts and stops +# the BrowserStack Local tunnel for you — no manual binary management. browserstackLocal: false # =========== diff --git a/config/browserstack.local.yml b/config/browserstack.local.yml deleted file mode 100644 index 98e54ee..0000000 --- a/config/browserstack.local.yml +++ /dev/null @@ -1,28 +0,0 @@ -userName: YOUR_USERNAME -accessKey: YOUR_ACCESS_KEY - -projectName: BrowserStack Samples -buildName: behave-playwright-sdk-build-1 -buildIdentifier: '#${BUILD_NUMBER}' - -framework: behave - -# Single-platform run for the local-tunnel example. -platforms: - - os: Windows - osVersion: 11 - browserName: chrome - browserVersion: latest - -parallelsPerPlatform: 1 - -# BrowserStack Local on: the SDK starts/stops the tunnel for you, and the -# `make local` target serves a small HTML page on http://localhost:45454/ -# which the test scenario hits via the tunnel. -browserstackLocal: true - -debug: true -networkLogs: false -consoleLogs: errors - -source: behave-playwright-sdk:sample-master:v1.0 diff --git a/config/browserstack.single.yml b/config/browserstack.single.yml deleted file mode 100644 index e4c6161..0000000 --- a/config/browserstack.single.yml +++ /dev/null @@ -1,25 +0,0 @@ -userName: YOUR_USERNAME -accessKey: YOUR_ACCESS_KEY - -projectName: BrowserStack Samples -buildName: behave-playwright-sdk-build-1 -buildIdentifier: '#${BUILD_NUMBER}' - -framework: behave - -# Single-platform run: one chromium session, no parallelism. -platforms: - - os: Windows - osVersion: 11 - browserName: chrome - browserVersion: latest - -parallelsPerPlatform: 1 - -browserstackLocal: false - -debug: true -networkLogs: false -consoleLogs: errors - -source: behave-playwright-sdk:sample-master:v1.0 diff --git a/features/local-html/index.html b/features/local-html/index.html deleted file mode 100644 index ad526a6..0000000 --- a/features/local-html/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - BrowserStack Local Sample - - -

BrowserStack Local Sample

-

If you can see this page from a BrowserStack session, the Local tunnel is working.

- - diff --git a/features/local.feature b/features/local.feature deleted file mode 100644 index e89be4b..0000000 --- a/features/local.feature +++ /dev/null @@ -1,5 +0,0 @@ -Feature: Local sample - - Scenario: Can reach a localhost page through the BrowserStack Local tunnel - Given I visit the local sample page - Then I should see the local sample heading diff --git a/features/steps/local_steps.py b/features/steps/local_steps.py deleted file mode 100644 index 405fc61..0000000 --- a/features/steps/local_steps.py +++ /dev/null @@ -1,18 +0,0 @@ -from behave import given, then - - -LOCAL_URL = "http://localhost:45454/" -EXPECTED_HEADING = "BrowserStack Local Sample" - - -@given("I visit the local sample page") -def visit_local(context): - context.page.goto(LOCAL_URL) - - -@then("I should see the local sample heading") -def verify_local_heading(context): - heading = context.page.locator("h1").text_content() - assert heading == EXPECTED_HEADING, ( - f"expected {EXPECTED_HEADING!r}, got {heading!r}" - ) From fa699044d6cd0bf41f5eeb6977b007cf714a90ba Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack Date: Thu, 7 May 2026 12:55:50 +0530 Subject: [PATCH 3/7] Fix source identifier to match repo + canonical pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was `behave-playwright-sdk:...` — my made-up `-sdk` prefix that doesn't match any real convention. Other samples use `-:sample-master:v` (cucumber-ruby, cucumber-java-playwright), so the analog here is `behave-playwright:sample-master:v1.0`. Addresses https://github.com/browserstack/behave-playwright-browserstack/pull/1#discussion_r3199504420 --- browserstack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browserstack.yml b/browserstack.yml index 3608fd1..36e3c97 100644 --- a/browserstack.yml +++ b/browserstack.yml @@ -56,4 +56,4 @@ networkLogs: false consoleLogs: errors # Identifier so BrowserStack can tag the sample source — please leave as-is. -source: behave-playwright-sdk:sample-master:v1.0 +source: behave-playwright:sample-master:v1.0 From 5030da4f924e2b6627dd79abbcf5e3f1e04ef83f Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack Date: Thu, 7 May 2026 13:35:07 +0530 Subject: [PATCH 4/7] Unpin playwright per review Was `playwright==1.49.0` pinned to dodge the SDK monkeypatch's `artifactsDir` kwarg mismatch on PW >=1.50. Customers get the latest Playwright now; if the SDK still rejects the new kwarg they'll see `TypeError: ... unexpected keyword argument 'artifactsDir'` until BS ships an updated SDK. Resolves https://github.com/browserstack/behave-playwright-browserstack/pull/1#discussion_r3199306786 --- requirements.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index e2d9c8f..3e36efa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,3 @@ browserstack-sdk>=1.46.0 behave>=1.2.7 -# Pinned: the SDK monkeypatches Playwright internals, and PW >=1.50 changes -# the launch keyword args in a way the current SDK doesn't yet handle -# (`unexpected keyword argument 'artifactsDir'`). Keep at 1.49.0 until the -# SDK release notes call out a higher upper bound. -playwright==1.49.0 +playwright From 4392c3999103ed998fb7d66e675d7c080c248685 Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack Date: Thu, 7 May 2026 16:59:06 +0530 Subject: [PATCH 5/7] Pin playwright==1.49.0; align source tag with repo+branch - requirements.txt: pin to 1.49.0 (avoid drift; matches what we test). - browserstack.yml: source -> behave-playwright-browserstack:sample-main:v1.0 to reflect the actual repo and default branch. Verified end-to-end via SDK: 3/3 sessions passed (chromium / firefox / webkit). Build: 9997a825ccaa7a0eef53e2aaf297763c0d3e7b26 --- browserstack.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browserstack.yml b/browserstack.yml index 36e3c97..8d878bf 100644 --- a/browserstack.yml +++ b/browserstack.yml @@ -56,4 +56,4 @@ networkLogs: false consoleLogs: errors # Identifier so BrowserStack can tag the sample source — please leave as-is. -source: behave-playwright:sample-master:v1.0 +source: behave-playwright-browserstack:sample-main:v1.0 diff --git a/requirements.txt b/requirements.txt index 3e36efa..5b2db15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ browserstack-sdk>=1.46.0 behave>=1.2.7 -playwright +playwright==1.49.0 From 943aefd3a9be7cd816661d7073c903760d466143 Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack <110160157+Jimesh-browserstack@users.noreply.github.com> Date: Tue, 12 May 2026 11:28:02 +0530 Subject: [PATCH 6/7] Restructure browserstack.yml to match junit-5 reference layout Addresses xxshubhamxx review: match structure of junit-browserstack/junit-5/browserstack.yml, add missing comments, add buildIdentifier key. --- browserstack.yml | 73 +++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/browserstack.yml b/browserstack.yml index 8d878bf..bccef1e 100644 --- a/browserstack.yml +++ b/browserstack.yml @@ -1,30 +1,39 @@ # ============================= # Set BrowserStack Credentials # ============================= -# Add your BrowserStack userName and accessKey here, or set the -# BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY environment -# variables. Env vars take precedence over the values in this file. +# 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: behave-playwright-sdk-build-1 - -# `framework` lets the SDK send test context (test name, status) to 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} +# Set `framework` of your test suite. Example, `testng`, `cucumber`, `cucumber-testng` +# This property is needed to send test context to BrowserStack (test name, status) framework: behave +source: behave-playwright-browserstack:sample-main:v1.0 + # ======================================= # Platforms (Browsers / Devices to test) # ======================================= -# Each entry below is one cross-browser cell. The SDK runs `parallelsPerPlatform` -# parallel sessions per entry, so the three entries below x 1 = 3 parallel sessions. -# These three browsers map 1:1 to the three Playwright engine families (chromium / -# firefox / webkit). The customer code in features/environment.py calls -# `chromium.launch()` — the SDK transparently routes the launch to the correct -# per-platform browser at runtime, so no per-platform branching is needed. +# 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) +# The three entries below map 1:1 to the Playwright engine families (chromium / firefox / +# webkit). Customer code calls `chromium.launch()` and the SDK transparently routes the +# launch to the per-platform browser at runtime, so no per-platform branching is needed. platforms: - os: Windows osVersion: 11 @@ -39,21 +48,35 @@ platforms: browserName: playwright-webkit 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 (private hosts) -# =================================== -# Set to true to test localhost / staging hosts. The SDK starts and stops -# the BrowserStack Local tunnel for you — no manual binary management. -browserstackLocal: false +# ========================================== +# 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) -# =========== -# Diagnostics -# =========== -debug: true -networkLogs: false -consoleLogs: errors +# Options to be passed to BrowserStack local in-case of advanced configurations +# browserStackLocalOptions: + # 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 -# Identifier so BrowserStack can tag the sample source — please leave as-is. -source: behave-playwright-browserstack:sample-main:v1.0 +# =================== +# Debugging features +# =================== +debug: true # # 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) From 9f41152cedc799098bb4345c10fc72cfc91cb650 Mon Sep 17 00:00:00 2001 From: Jimesh-browserstack Date: Wed, 13 May 2026 12:25:40 +0530 Subject: [PATCH 7/7] Add second sample: BrowserStack Local test (features/local.feature) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the 2-sample pattern from cucumber-java-browserstack and junit-browserstack so customers get one public-site sample plus one BrowserStack Local sample. - features/local.feature — navigates http://bs-local.com:45454/ and asserts the page title contains "BrowserStack Local". - features/steps/local_steps.py — two steps. - features/local-html/index.html — title-matching page; start with `python3 -m http.server 45454 --directory features/local-html` before running the local feature. - browserstack.yml — browserstackLocal: true (matches cucumber-java-browserstack canonical so the SDK starts and stops the tunnel for every run). - README.md — repo layout updated; Running section split into "Sample test" and "Local test" subsections with explicit local-server start command. Live-verified end-to-end: build 03f2c770c3b15871829075c20f45ad4a826b56cf returns status=passed for all 6 sessions (2 scenarios x 3 Playwright engines: chromium 148, playwright-firefox 148, playwright-webkit 18.2). --- README.md | 34 +++++++++++++++++++++++++++++----- browserstack.yml | 2 +- features/local-html/index.html | 16 ++++++++++++++++ features/local.feature | 5 +++++ features/steps/local_steps.py | 14 ++++++++++++++ 5 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 features/local-html/index.html create mode 100644 features/local.feature create mode 100644 features/steps/local_steps.py diff --git a/README.md b/README.md index 94cfe71..dbb9c8b 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,39 @@ This repo shows how to run [behave](https://behave.readthedocs.io/) tests on Bro * Set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` as environment variables, or replace `userName` and `accessKey` directly in `browserstack.yml` with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings). Env vars take precedence. ### Running your tests -Run the sample in parallel across the 3 Playwright browser engines (chromium / firefox / webkit) declared in `browserstack.yml`: +There are two sample scenarios in `features/`: + +* **`features/sample.feature`** — drives `https://www.bstackdemo.com` (a public site) and adds a product to the cart. +* **`features/local.feature`** — drives `http://bs-local.com:45454/` through the BrowserStack Local tunnel; verifies the page title contains "BrowserStack Local". + +`browserstack.yml` enables `browserstackLocal: true`, so the SDK starts and stops the BrowserStack Local tunnel for you on every run — no manual binary lifecycle. + +#### Sample test (public site) +Runs in parallel across the 3 Playwright browser engines (chromium / firefox / webkit) declared in `browserstack.yml`: ```sh browserstack-sdk behave features/sample.feature ``` +#### Local test (private / localhost host) +Start a local HTTP server first — `features/local-html/` contains a tiny page titled "BrowserStack Local Test Page": + +```sh +python3 -m http.server 45454 --directory features/local-html +``` + +Then in a separate terminal: + +```sh +browserstack-sdk behave features/local.feature +``` + +`bs-local.com` is a hostname BrowserStack Local resolves to your machine inside the remote browser — for your own app, point your scenarios at `http://bs-local.com:/` instead of a public URL. + Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github). Alternatively the variables can be set in the environment using env or your CI framework (like GitHub Actions or Jenkins). See `.github/workflows/build.yml` for a GitHub Actions example — it runs on `workflow_dispatch` (manual trigger) with a commit SHA input and posts a status check back to that commit. -### Testing a private host (BrowserStack Local) -If your app lives on `localhost`, a staging host, or behind a firewall, set `browserstackLocal: true` in `browserstack.yml` and rerun the same command. 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:/` (a hostname BrowserStack Local resolves to your machine) instead of a public URL. - ### How the SDK changes things - **One `browserstack.yml`** declares platforms; the SDK picks them up automatically. - **The SDK runs platforms in parallel for you** — no hand-rolled parallel runner; the SDK forks one behave run per `(platform × parallelsPerPlatform)` cell. @@ -39,9 +59,13 @@ If your app lives on `localhost`, a staging host, or behind a firewall, set `bro ├── requirements.txt └── features/ ├── sample.feature # bstackdemo add-to-cart scenario + ├── local.feature # BrowserStack Local tunnel scenario + ├── local-html/ + │ └── index.html # static page served on :45454 for local.feature ├── environment.py # behave hooks: launch browser, hand to context.page └── steps/ - └── sample_steps.py + ├── sample_steps.py + └── local_steps.py ``` ### Further Reading diff --git a/browserstack.yml b/browserstack.yml index bccef1e..cf9deaa 100644 --- a/browserstack.yml +++ b/browserstack.yml @@ -65,7 +65,7 @@ parallelsPerPlatform: 1 # ========================================== # 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) +browserstackLocal: true # (Default false) # Options to be passed to BrowserStack local in-case of advanced configurations # browserStackLocalOptions: diff --git a/features/local-html/index.html b/features/local-html/index.html new file mode 100644 index 0000000..61c07cb --- /dev/null +++ b/features/local-html/index.html @@ -0,0 +1,16 @@ + + + + + BrowserStack Local Test Page + + +

BrowserStack Local Test Page

+

+ Served on http://localhost:45454/ for the + features/local.feature scenario. The BrowserStack Local + tunnel resolves bs-local.com:45454 on the remote browser + back to this page. +

+ + diff --git a/features/local.feature b/features/local.feature new file mode 100644 index 0000000..40565a9 --- /dev/null +++ b/features/local.feature @@ -0,0 +1,5 @@ +Feature: Verify BrowserStack Local + + Scenario: Navigate to a page served on localhost through the BrowserStack Local tunnel + Given I visit local app website + Then the page title should contain "BrowserStack Local" diff --git a/features/steps/local_steps.py b/features/steps/local_steps.py new file mode 100644 index 0000000..61d8d0c --- /dev/null +++ b/features/steps/local_steps.py @@ -0,0 +1,14 @@ +from behave import given, then + + +@given("I visit local app website") +def visit_local_app(context): + context.page.goto("http://bs-local.com:45454/") + + +@then('the page title should contain "{expected}"') +def verify_title_contains(context, expected): + title = context.page.title() + assert expected in title, ( + f"expected title to contain {expected!r}, got {title!r}" + )