Skip to content

Fix: SDK-7075 honour BROWSERSTACK_LOCAL / BROWSERSTACK_LOCAL_IDENTIFIER in the WDIO service - #151

Open
shivam5643 wants to merge 2 commits into
v8from
fix/SDK-7075-honour-local-env-vars
Open

Fix: SDK-7075 honour BROWSERSTACK_LOCAL / BROWSERSTACK_LOCAL_IDENTIFIER in the WDIO service#151
shivam5643 wants to merge 2 commits into
v8from
fix/SDK-7075-honour-local-env-vars

Conversation

@shivam5643

@shivam5643 shivam5643 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

What is this about?

@wdio/browserstack-service never read BROWSERSTACK_LOCAL / BROWSERSTACK_LOCAL_IDENTIFIER. With
both exported and no browserstackLocal key in wdio.conf.js, no tunnel was launched and the
session capabilities carried zero local-related keys — isLocalEnabled: false, and
caps['localIdentifier'] raised KeyError. This makes the service honour the two env vars that are
the SDK-wide contract every other BrowserStack SDK already respects.

Root cause

BrowserstackLauncherService.onPrepare resolves Local purely from the service options: it gates
the entire Local path on this._options.browserstackLocal (src/launcher.ts:520) and takes the
tunnel identifier from this._options.opts.localIdentifier (:532). Nothing in the package ever
read either env var — a grep for both names across packages/browserstack-service/src/ returned
zero hits on v8 and on main. So with the env vars exported and no browserstackLocal in the
config, onPrepare took the browserstackLocal is not enabled - skipping... early return: no
tunnel, and neither _updateCaps(capabilities, 'local') nor
_updateCaps(capabilities, 'localIdentifier', …) ever ran.

These env vars are not a WDIO invention — browserstack-binary's EnvCapsMapping maps
browserstackLocal → BROWSERSTACK_LOCAL and localIdentifier → BROWSERSTACK_LOCAL_IDENTIFIER
(core/config/env.js:12-13), which is why the sibling wrapper cases on the other SDKs pass. Every
other SDK routes config resolution through the binary; this service does its own resolution in the
non-CLI path and never implemented the env layer.

Fix

One normalization at the single point where the options are first seen — the launcher constructor,
beside the existing normalizeTestReportingConfig() / normalizeTestReportingEnvVariables() calls
that do exactly this job. The tunnel-start and _updateCaps code is untouched, so the env-var path
and the config path converge immediately and cannot drift.

File Change
packages/browserstack-service/src/util.ts New normalizeLocalEnvVariables() — maps the two env vars onto _options.browserstackLocal / _options.opts.localIdentifier
packages/browserstack-service/src/launcher.ts Calls it from the constructor, before BrowserStackConfig.getInstance() (4 lines)
packages/browserstack-service/src/constants.ts BROWSERSTACK_LOCAL, BROWSERSTACK_LOCAL_IDENTIFIER env-var name constants
packages/browserstack-service/tests/launcher.test.ts 9 new cases (new describe, env cleared in beforeEach/afterEach)
.changeset/sdk-7075-honour-local-env-vars-v8.md patch changeset

Two deliberate behavioural decisions, both regression-guarded by tests:

  • Env wins over wdio.conf.js — the same direction as the binary's updateConfigWithEnvVars
    (core/config/index.js:168) and as getObservabilityUser / getObservabilityKey /
    getObservabilityProject in this very file.
  • An identifier alone does not enable Local — enablement keys off BROWSERSTACK_LOCAL only,
    mirroring the binary's getLocalConfig() (core/config/index.js:693), so a stale
    BROWSERSTACK_LOCAL_IDENTIFIER left in a CI environment stays inert.

Related Jira task/s

Release (mandatory for every PR — required for the ready-for-review label)

Version bump:

  • minor (backwards-compatible feature)
  • patch (bug fix or other small change)

Release notes type:

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing):

  • Fixed BROWSERSTACK_LOCAL and BROWSERSTACK_LOCAL_IDENTIFIER being ignored. Both env vars now configure BrowserStack Local — the tunnel is launched and the local / localIdentifier capabilities reach the session — matching the other BrowserStack SDKs.

Release notes (internal):

  • normalizeLocalEnvVariables() (util.ts) maps BROWSERSTACK_LOCAL_options.browserstackLocal and BROWSERSTACK_LOCAL_IDENTIFIER_options.opts.localIdentifier, called from the BrowserstackLauncherService constructor before BrowserStackConfig.getInstance(). Previously the non-CLI path read Local only from wdio.conf.js, so onPrepare early-returned and no tunnel or local/localIdentifier caps were produced. Env takes precedence over the config; an identifier alone does not enable Local.

Proof

End-to-end against real BrowserStack sessions

Built this branch, npm packed it, installed the tarball into the reproduction project and ran it
under the original failure conditions: node 16.19.0, WDIO v8, service 8.50.0,
services: [["browserstack", {"testReporting": "true"}]] (no browserstackLocal key anywhere),
with BROWSERSTACK_LOCAL=true and BROWSERSTACK_LOCAL_IDENTIFIER=local_identifier_qoknmq exported.

Build: https://automate.browserstack.com/builds/71c8aac5d40fd2f6c82dbae7be0d3abf5056d64b

Assertion Before After
isLocalEnabled False True
inputCapabilities localIdentifier key absentKeyError local_identifier_qoknmq
local capability absent True (as browserstack.local)
localVersion (tunnel actually ran) 8.9

BStackAutomation regression coverage

  • Status: Passed (the one non-passing assertion is a test-side key-name bug, corrected in the companion PR — see below)
  • Command: ./run_tests.sh tests/{automate,app_automate}/wdio_cucumber/wdio_cucumber_wrapper/test_*wdio_cucumber_wrapper_env_vars.py (from run_test.sample.sh, --language wdio --agent_install True --node_version_agent 16.19.0 --wdio_versions v8)
  • Results: 5 passed / 1 failed / 6 skipped (was 4 failed / 2 passed / 6 skipped before the fix)
  • Observability build (fix-verify): https://observability.browserstack.com/builds/y7oronnsnqazrgzau5ojua8nspizxlrhnu4xs7qx
  • Observability build (pre-fix reproduction): https://observability.browserstack.com/builds/k7wlz0rfnintcxmydptibcp1tmodwjihgppbmeg2
  • BStackAutomation branch: SDK-7075_uncomment_testcase
  • The 6 skips are the snake_case auth params, skipped by the fixture for the unrelated open ticket SDK-4942 — pre-existing.
  • The 1 failure is test_app_automate_local_identifier, which indexed caps['localIdentifier'] strictly while App Automate surfaces the identifier only as browserstack.localIdentifier. The fix-verify caps prove the SDK is correct and the tunnel ran (browserstack.local=True, browserstack.localIdentifier=local_identifier_VfJEXk, browserstack.tunnelIdentifier=local_identifier_VfJEXk, localVersion=8.9) — the assertion read a key that never exists. Corrected in the companion PR, still falsifiable (pre-fix those caps had 26 keys and zero local/tunnel keys).

Companion PR — coverage must land with the fix

browserstack/BStackAutomation#80300 — uncomments the three tests (both wrapper files go from
1 live / 2 commented to 3 live / 0 commented) and corrects the strict assertion above. Merging this
fix without it would leave the coverage switched off.

Unit tests

  • Status: Passed
  • Command: npm test (in packages/browserstack-service)
  • Results: 45/45 test files pass; launcher.test.ts = 122 cases
  • Non-vacuous: with the normalizeLocalEnvVariables() call disabled, launcher.test.ts goes to
    7 failed / 115 passed — the 9 new cases genuinely exercise the change. The 2 that pass either
    way are the deliberate regression guards (config still honoured with no env var set; identifier
    alone must not enable Local).
  • Coverage: both capability shapes (bstack:options and flattened browserstack.*), the identifier
    actually reaching the Local binary (mockStart args), both override directions
    (BROWSERSTACK_LOCAL=true over browserstackLocal: false and =false over : true), and
    BROWSERSTACK_LOCAL_IDENTIFIER over opts.localIdentifier.
  • npm run build (buf generate + tsc -p tsconfig.prod.json) and tsc --noEmit both clean.

Blast radius

  • _options.browserstackLocal is read at exactly one site in src/ (launcher.ts:520); opts.localIdentifier is consumed only at :532-533.
  • Neither local nor localIdentifier is in NOT_ALLOWED_KEYS_IN_CAPS (constants.ts:50), so _removeCliOnlyCapabilityOptions cannot strip the new caps.
  • In the CLI/binary flow onPrepare returns before the Local block (launcher.ts:516-518) and the binary resolves the same two env vars itself — consistent either way, so neither code path is left behind.
  • No behaviour change when neither env var is set: the guard is !isUndefined(process.env[…]), and isUndefined() treats '' as unset, so an exported-but-empty var cannot silently disable a config-enabled tunnel.

Deliberately out of scope

  • BROWSERSTACK_BUILD_IDENTIFIER is also missing from the caps, but it is not the same code path:
    _handleBuildIdentifier (launcher.ts:1064) already branches on process.env.BROWSERSTACK_BUILD_NAME
    and deletes buildIdentifier from the caps when it is set, so feeding the identifier in from the
    env would fight that rule. Needs its own ticket. Same for BROWSERSTACK_PROJECT_NAME /
    BROWSERSTACK_BUILD_NAME — the passing test_project_name case does not prove those env vars work,
    because the fixture writes them into test.conf.js and exports them with the same values, so the
    assertion passes off the config.
  • The v9 line (main) has the identical gapgit grep for either env var on origin/main
    returns nothing. This branch targets v8 only, per the repo's rule (v8 → v8, v9 → main) and
    because the ticket and all three reproductions are on v8. The port to main is a one-to-one
    cherry-pick of this commit and should be raised alongside
    — please don't let the release lines
    drift.

Checklist

  • Ready to review
  • Has it been tested locally?

Review Checklist

  • Minimal change — no unrelated refactors (176 insertions, 2 deletions; the 2 are import-list edits)
  • Error handling — no I/O or parsing that can throw; process.env reads + property assignment only, so no try/catch is warranted
  • No hardcoded values — env-var names extracted to constants.ts beside the existing BROWSERSTACK_* constants
  • Backward compatible — no API change; config-only users unaffected and regression-guarded by test
  • Graceful degradation maintained — cannot throw, so it cannot break a user's test run
  • Correct layer — the non-CLI path this service owns; the binary already covers the CLI path
  • No anti-patterns (sdk-anti-patterns.md) — incl. §2 hardcoded config, §23 parallel-path drift, §26 unreachable fallbacks
  • Thread safety — runs once in the launcher constructor (main process, pre-session)
  • BStackAutomation tests pass (companion PR #80300)
  • Unit tests pass, and proven non-vacuous
  • Cross-SDK impact — none; no binary change

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.

Wiki

  • wiki-entry: knowledge/infra-issues/harness-unsatisfiable-assertion-v1.md — the App Automate caps['localIdentifier'] strict-index case, corrected in the companion PR
  • cited verified entries:
    • knowledge/infra-issues/uncomment-scaffolding-v1.md (Variant C checked and absent; Variant D — the reason the coverage ships in the companion PR rather than a follow-up)
    • knowledge/conventions/exit-code-is-not-proof.md (why the unit tests were re-run with the fix disabled instead of trusting a green suite)
    • knowledge/conventions/handoff-bundle-format.md
    • knowledge/conventions/uncomment-branch-resolution.md

Handoff bundle

  • handoff_dir: BStackAutomation/SDK/api/.github/docs/handoff/SDK-7075/
  • handoff.md — the original reproduction (three independent runs, all on service 8.50.0)
  • fix-notes.md — root cause + files changed + local verification
  • fix-verify.log — test output on this fix branch

…OCAL_IDENTIFIER (SDK-7075)

The service resolved BrowserStack Local purely from the `wdio.conf.js` service
options, so the SDK-wide `BROWSERSTACK_LOCAL` / `BROWSERSTACK_LOCAL_IDENTIFIER`
env vars were silently dropped: no tunnel was launched and no `local` /
`localIdentifier` capability reached the session, leaving every session with
`isLocalEnabled: false`.

Add `normalizeLocalEnvVariables()` and call it from the launcher constructor
alongside the existing `normalizeTestReporting*` normalizers. The env var wins
over `wdio.conf.js`, matching `updateConfigWithEnvVars` in browserstack-binary
and `getObservabilityUser` / `getObservabilityKey` here. An identifier on its
own still does not enable Local, mirroring `getLocalConfig()` in the binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shivam5643
shivam5643 requested a review from a team as a code owner August 17, 2026 11:16
@shivam5643
shivam5643 requested review from dandonarahul2002 and kamal-kaur04 and removed request for a team August 17, 2026 11:16
@github-actions

Copy link
Copy Markdown
Contributor

🔴 SDK PR Review gate is red. Pending:

  • The SDK PR Review Agent has not reviewed the current head commit yet — run the SDK PR Review Agent.

It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants