Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

- Fixed: BrowserStack sessions honour `--provider-project`, `--provider-build`, and
`--provider-session-name`. The capability builder emitted the legacy JSON Wire keys `device`,
`os_version`, and `app` at the top level next to the W3C `bstack:options` block; the hub treats a
request carrying any legacy key as a legacy session, reads the labels from the legacy top-level
`project`/`build`/`name` (never set), and ignores `bstack:options`, so every session landed in
"Untitled Project" / "Untitled Build" with an empty name — even after #2495 carried the flags to
the provider (#2494). The builder now emits `appium:deviceName`, `appium:platformVersion`, and
`appium:app` and no legacy key, verified against live App Automate sessions.
- Changed (sessions): the implicit session is now keyed by workspace **and platform**, so one checkout
can drive iOS and Android without inventing a `--session` name for every command (#2580). An
implicit session was addressed by `cwd:<workspace>:default`, one slot per checkout, and it stayed
Expand Down
15 changes: 12 additions & 3 deletions packages/provider-webdriver/src/browserstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,23 @@ export function createBrowserStackUploadApp(
};
}

/**
* Builds the W3C `alwaysMatch` capabilities for a BrowserStack App Automate session.
*
* Every key is either W3C-standard (`platformName`), `appium:`-prefixed, or inside
* `bstack:options`. The legacy JSON Wire keys (`device`, `os_version`, `app`, `project`, `build`,
* `name`) must not appear: when the hub sees any of them it treats the whole request as a legacy
* session and reads the labels from the legacy top-level keys instead of `bstack:options`, so the
* project/build/session names are silently dropped and the session lands in "Untitled Project".
*/
export function buildBrowserStackCapabilities(
options: BrowserStackCapabilitiesOptions,
): Record<string, unknown> {
const { 'bstack:options': configuredBstackOptions, ...configured } = options.configured ?? {};
return {
device: options.deviceName,
os_version: options.osVersion,
...(options.app ? { app: options.app } : {}),
'appium:deviceName': options.deviceName,
'appium:platformVersion': options.osVersion,
...(options.app ? { 'appium:app': options.app } : {}),
...configured,
// Merged per key, never assigned: `configured` carrying its own `bstack:options` used to
// replace the whole object and silently drop the session/build labels below.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ test('lease_allocate over HTTP prepares the BrowserStack session end to end', as
assert.deepEqual(alwaysMatch(create), {
platformName: 'Android',
'appium:deviceName': 'Google Pixel 8',
device: 'Google Pixel 8',
os_version: '14.0',
app: 'bs://preuploaded',
'appium:platformVersion': '14.0',
'appium:app': 'bs://preuploaded',
'bstack:options': {
projectName: 'MyProject',
buildName: 'Build-2026-09-11',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,12 @@ test('BrowserStack facade nests device-feature capabilities inside bstack:option
locale: 'Fr',
networkProfile: '4g-lte-advanced-good',
});
// Vendor capabilities stay nested; only BrowserStack's legacy selectors sit at the top level.
// Vendor capabilities stay nested, and no legacy JSON Wire selector reaches the top level —
// the hub would otherwise treat the request as legacy and ignore bstack:options entirely.
assert.equal(alwaysMatch?.deviceOrientation, undefined);
for (const legacyKey of ['device', 'os_version', 'app', 'project', 'build', 'name']) {
assert.equal(alwaysMatch?.[legacyKey], undefined, `legacy key ${legacyKey} leaked`);
}
});
}, 15_000);

Expand Down Expand Up @@ -390,9 +394,8 @@ function assertBrowserStackCalls(
alwaysMatch: {
platformName: 'Android',
'appium:deviceName': 'Google Pixel 8',
device: 'Google Pixel 8',
os_version: '14.0',
app: 'bs://preuploaded',
'appium:platformVersion': '14.0',
'appium:app': 'bs://preuploaded',
'bstack:options': {
projectName: 'agent-device',
buildName: `build-${lease.runId}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,8 @@ function assertWebDriverCalls(calls: readonly CloudWebDriverHttpCall[], leaseId:
alwaysMatch: {
platformName: 'Android',
'appium:deviceName': 'Google Pixel 8',
device: 'Google Pixel 8',
os_version: '14.0',
app: 'bs://app-id',
'appium:platformVersion': '14.0',
'appium:app': 'bs://app-id',
'bstack:options': {
projectName: 'agent-device',
buildName: 'run-a',
Expand Down
Loading