Skip to content

Fix W3C capability rejection breaking every desktop session - #4

Merged
jochen-testingbot merged 1 commit into
mainfrom
fix/w3c-capabilities-and-integration-coverage
Sep 21, 2026
Merged

jochen-testingbot merged 1 commit into
mainfrom
fix/w3c-capabilities-and-integration-coverage

Conversation

@jochen-testingbot

@jochen-testingbot jochen-testingbot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

The bug

Reported from the field:

The desktop MCP error is: "Invalid or unsupported WebDriver capabilities found (platform)". [...] mobile sessions work.

Accurate. tb_openBrowser sent the legacy JSON-Wire platform cap alongside the W3C platformName. The webdriver client's validateCapabilities rejects any top-level key that is neither standard W3C nor colon-namespaced as soon as an extension cap (tb:options) is present — so every desktop session failed client-side, before a request ever reached the hub.

Mobile was unaffected because that branch emits only valid and appium:-namespaced keys, which is exactly the asymmetry the reporter saw. Per TestingBot's docs platform is just the legacy alias for platformName, and the OS codes (VENTURA, WIN11, …) work on platformName directly, so the duplicate bought nothing.

Second bug, found while cross-checking

screenResolution inside tb:options was never honoured. The VM reads the kebab-case screen-resolution key only (lib/drivers/screenresolution.js, lib/protocols/webdriver.js, lib/protocols/rc.js), so the camelCase spelling was silently dropped and desktop VMs stayed at the 1280x1024 default. Now sent as screen-resolution.

Remaining keys were cross-checked against the VM source and are correct: tb:options.realDevice, name, build, and the appium:* / W3C top-level set.

Why the tests didn't catch either

Two compounding gaps:

  1. Every unit test mocks WebDriver.newSession. Capabilities were asserted against our own expectations, so they could never disagree with the client that has to accept them. git log -S shows platform has been there since the initial commit.
  2. The e2e suite existed but had never run. It opens a real Chrome session and would have caught this, but CI excludes it (paid minutes) and it was broken anyway — it never awaited addAutomationTools after that became async, so handle.tools was undefined and it would have failed on its first test.

Test coverage added

  • tests/tools/browse-capabilities.contract.test.ts — drives the real webdriver client against a local stub hub on 127.0.0.1: real validation, real HTTP round trip, asserting the actual JSON body sent. No TestingBot session, ~130ms, runs in the normal npm test pass. Verified it fails with the exact reported error when platform is reintroduced.
  • web.integration.test.ts — 7 → 20 tests. Now covers every browser/shared tool (click, type incl. pressEnter, pressKey via the W3C actions path, getText, getAttribute, executeScript with positional args, all six locator strategies, not-found error handling) in a single session, plus an end-to-end assertion that the requested resolution reaches the VM.
  • mobile.integration.test.ts (new) — the appium:* branch on an Android emulator, resolved at runtime from the /v1/browsers catalog (not /v1/devices, which is the physical-device inventory). Separately gated behind RUN_MOBILE_INTEGRATION_TESTS.
  • appium-child.integration.test.ts (new) — spawns the real appium-mcp child and checks HIDDEN_TOOLS, the steering rewrite, and that no published schema exposes remoteServerUrl. Costs nothing; every other appium test stubs the child, so an upstream rename would currently pass CI and break mobile silently.

CI

.github/workflows/integration.yml — nightly (03:17 UTC) + workflow_dispatch, using the TB_KEY / TB_SECRET repo secrets. It fails fast when the secrets are absent rather than letting the suites skip into a green run that verified nothing, serialises runs so two paid sessions never overlap, skips forks, and always sweeps leaked sessions afterwards via scripts/close-stale-sessions.mjs (best-effort; never turns a passing run red).

Verification

npm run build passes: lint, format, 120 unit tests, tsc.

Not yet verified against the live hub — I have no credentials. Add the secrets, then trigger a manual run from the Actions tab rather than waiting for the first nightly; that's the real proof.

tb_openBrowser sent the legacy JSON-Wire `platform` cap alongside the W3C
`platformName`. The `webdriver` client rejects any top-level capability that
is neither standard W3C nor colon-namespaced as soon as an extension cap
(`tb:options`) is present, so every desktop session failed client-side with
"Invalid or unsupported WebDriver capabilities found (platform)" before a
request ever reached the hub. Mobile was unaffected — that branch emits only
valid and `appium:`-namespaced keys — which is why mobile sessions worked.

Also fix `screenResolution` in tb:options: TestingBot reads the kebab-case
`screen-resolution` key, so the camelCase spelling was silently dropped and
desktop VMs stayed at the 1280x1024 default.

Neither bug was catchable by the existing tests, which mock
WebDriver.newSession and assert capabilities against our own expectations —
they could never disagree with the client that has to accept them. The
integration suite that would have caught it had gone stale: it never awaited
addAutomationTools after that function became async, so it would have failed
on its first test, and CI never runs it.

Test coverage added:

- tests/tools/browse-capabilities.contract.test.ts drives the real webdriver
  client against a local stub hub, asserting the actual JSON body sent. Free
  and fast, so it runs in the normal unit pass. Verified it fails with the
  exact reported error when `platform` is reintroduced.
- web.integration.test.ts now covers every browser and shared tool (click,
  type, pressKey, getText, getAttribute, executeScript, all six locator
  strategies) in a single session, and asserts the requested resolution
  actually reaches the VM.
- mobile.integration.test.ts covers the appium:* capability branch on an
  Android emulator, resolved from the /v1/browsers catalog.
- appium-child.integration.test.ts spawns the real appium-mcp child and
  checks the published tool surface. Opens no session, so it costs nothing.

CI: .github/workflows/integration.yml runs the suites nightly and on demand
using the TB_KEY/TB_SECRET secrets. It fails fast when the secrets are absent
rather than letting the suites skip into a green run that verified nothing,
serialises runs so two paid sessions never overlap, and always sweeps leaked
sessions afterwards via scripts/close-stale-sessions.mjs.
@jochen-testingbot
jochen-testingbot force-pushed the fix/w3c-capabilities-and-integration-coverage branch from c992d37 to 2cf947c Compare September 21, 2026 07:25
@jochen-testingbot
jochen-testingbot merged commit 516c281 into main Sep 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant