From e6dc8d2dba1902ea3f25ad15da6f2e327dcb16ff Mon Sep 17 00:00:00 2001 From: Simon Markowski Date: Fri, 31 Jul 2026 20:41:03 -0400 Subject: [PATCH 1/7] fix(browserstack-service): route browserstack_executor commands via HTTP/S in BiDi sessions In BiDi sessions, browser.execute() routes over WebSocket directly to the browser, bypassing BrowserStack's HTTP hub, so browserstack_executor: commands fail silently. Overwrite the execute command in BiDi sessions to route executor-prefixed scripts through executeScript (which always uses HTTP/S), leaving all other scripts untouched. Handles single-browser and multiremote setups. Ported from webdriverio/webdriverio#15216. Co-Authored-By: RohanImmanuel Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/browserstack-service/src/service.ts | 26 ++++++++ .../tests/service.test.ts | 59 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index b9e2bbe..350cbc1 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -242,6 +242,19 @@ export default class BrowserstackService implements Services.ServiceInstance { PerformanceTester.scenarioThatRan = this._scenariosThatRan if (this._browser) { + try { + if (this._browser.isMultiremote) { + const multiRemoteBrowser = this._browser as unknown as WebdriverIO.MultiRemoteBrowser + Object.keys(this._caps).forEach((browserName) => { + this._routeBidiExecutorToHttp(multiRemoteBrowser.getInstance(browserName)) + }) + } else { + this._routeBidiExecutorToHttp(this._browser as WebdriverIO.Browser) + } + } catch (err) { + BStackLogger.warn(`Failed to patch execute for BiDi browserstack_executor routing; executor commands may not work in BiDi sessions: ${err}`) + } + try { const sessionId = this._browser.sessionId @@ -888,6 +901,19 @@ export default class BrowserstackService implements Services.ServiceInstance { }) } + _routeBidiExecutorToHttp (browser: WebdriverIO.Browser) { + if (!browser.isBidi) { + return + } + + browser.overwriteCommand('execute', async (originalExecute, script, ...args) => { + if (typeof script === 'string' && script.startsWith('browserstack_executor:')) { + return browser.executeScript(script, args) + } + return originalExecute(script, ...args) + }) + } + _multiRemoteAction (action: MultiRemoteAction) { if (!this._browser) { return Promise.resolve() diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index 95a2e32..e45703e 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -113,6 +113,7 @@ beforeEach(() => { browser = { execute: vi.fn(), executeScript: vi.fn(), + overwriteCommand: vi.fn(), on: vi.fn(), sessionId: sessionId, config: {}, @@ -626,6 +627,64 @@ describe('before', () => { expect(service['_failReasons']).toEqual([]) expect(service['_sessionBaseUrl']).toEqual('https://api.browserstack.com/automate-turboscale/v1/sessions') }) + + it('should overwrite execute command to route browserstack_executor via executeScript', async () => { + (browser as any).isBidi = true + const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) + await service.before(service['_config'] as any, [], browser) + + expect(browser.overwriteCommand).toHaveBeenCalledWith('execute', expect.any(Function)) + + const overwrite = vi.mocked(browser.overwriteCommand).mock.calls[0][1] as Function + const originalExecute = vi.fn() + + await overwrite(originalExecute, 'browserstack_executor: {"action":"annotate"}') + expect(browser.executeScript).toHaveBeenCalledWith('browserstack_executor: {"action":"annotate"}', []) + expect(originalExecute).not.toHaveBeenCalled() + + await overwrite(originalExecute, 'return document.title') + expect(originalExecute).toHaveBeenCalledWith('return document.title') + + const extraArg = { key: 'value' } + await overwrite(originalExecute, 'return arguments[0]', extraArg) + expect(originalExecute).toHaveBeenCalledWith('return arguments[0]', extraArg) + }) + + it('should not overwrite execute command for non-BiDi sessions', async () => { + (browser as any).isBidi = false + const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) + await service.before(service['_config'] as any, [], browser) + + expect(browser.overwriteCommand).not.toHaveBeenCalled() + }) + + it('should overwrite execute on each instance for multiremote', async () => { + const browserA = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } + const browserB = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } + const multiRemoteBrowser = { + ...browser, + isMultiremote: true, + getInstance: vi.fn().mockImplementation((name: string) => name === 'browserA' ? browserA : browserB) + } as unknown as WebdriverIO.MultiRemoteBrowser + + const service = new BrowserstackService({} as any, { browserA: {}, browserB: {} } as any, { + user: 'foo', key: 'bar' + }) + await service.before(service['_config'] as any, [], multiRemoteBrowser as any) + + expect(browserA.overwriteCommand).toHaveBeenCalledWith('execute', expect.any(Function)) + expect(browserB.overwriteCommand).toHaveBeenCalledWith('execute', expect.any(Function)) + + const overwriteA = vi.mocked(browserA.overwriteCommand).mock.calls[0][1] as Function + await overwriteA(vi.fn(), 'browserstack_executor: {"action":"annotate"}') + expect(browserA.executeScript).toHaveBeenCalledWith('browserstack_executor: {"action":"annotate"}', []) + expect(browserB.executeScript).not.toHaveBeenCalled() + + const originalExecuteA = vi.fn() + const extraArg = { key: 'value' } + await overwriteA(originalExecuteA, 'return arguments[0]', extraArg) + expect(originalExecuteA).toHaveBeenCalledWith('return arguments[0]', extraArg) + }) }) describe('beforeHook', () => { From b9bb4eb3bbc7626d6967fd348fb89e0d2d64c1b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:45:35 +0000 Subject: [PATCH 2/7] chore(changeset): auto-generate from PR template (patch) --- .changeset/pr-119.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pr-119.md diff --git a/.changeset/pr-119.md b/.changeset/pr-119.md new file mode 100644 index 0000000..12b2e1e --- /dev/null +++ b/.changeset/pr-119.md @@ -0,0 +1,5 @@ +--- +"@wdio/browserstack-service": patch +--- + +- Fixed BrowserStack executor commands (session name, status, annotations) being ignored in WebDriver BiDi sessions. From 1deec858560e769c86fa76a37d9e2c78887abfe5 Mon Sep 17 00:00:00 2001 From: xxshubhamxx Date: Wed, 5 Aug 2026 00:15:00 +0530 Subject: [PATCH 3/7] fix(browserstack-service): isolate BiDi executor patch failures per instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The try/catch wrapped the whole multiremote forEach, so a getInstance failure on one instance aborted the loop and left every later instance unpatched — a half-patched session indistinguishable in the logs from a fully-failed one. Wrap each instance's resolve-and-patch individually and name the failing instance in the warning. Co-Authored-By: Claude Opus 5 --- packages/browserstack-service/src/service.ts | 24 +++++++++++-------- .../tests/service.test.ts | 22 +++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index 350cbc1..9daea81 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -242,17 +242,21 @@ export default class BrowserstackService implements Services.ServiceInstance { PerformanceTester.scenarioThatRan = this._scenariosThatRan if (this._browser) { - try { - if (this._browser.isMultiremote) { - const multiRemoteBrowser = this._browser as unknown as WebdriverIO.MultiRemoteBrowser - Object.keys(this._caps).forEach((browserName) => { - this._routeBidiExecutorToHttp(multiRemoteBrowser.getInstance(browserName)) - }) - } else { - this._routeBidiExecutorToHttp(this._browser as WebdriverIO.Browser) + const patchBidiExecutorRouting = (resolveBrowser: () => WebdriverIO.Browser, label?: string) => { + try { + this._routeBidiExecutorToHttp(resolveBrowser()) + } catch (err) { + BStackLogger.warn(`Failed to patch execute for BiDi browserstack_executor routing${label ? ` on ${label}` : ''}; executor commands may not work in BiDi sessions: ${err}`) } - } catch (err) { - BStackLogger.warn(`Failed to patch execute for BiDi browserstack_executor routing; executor commands may not work in BiDi sessions: ${err}`) + } + + if (this._browser.isMultiremote) { + const multiRemoteBrowser = this._browser as unknown as WebdriverIO.MultiRemoteBrowser + Object.keys(this._caps).forEach((browserName) => { + patchBidiExecutorRouting(() => multiRemoteBrowser.getInstance(browserName), browserName) + }) + } else { + patchBidiExecutorRouting(() => this._browser as WebdriverIO.Browser) } try { diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index e45703e..7a28f01 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -685,6 +685,28 @@ describe('before', () => { await overwriteA(originalExecuteA, 'return arguments[0]', extraArg) expect(originalExecuteA).toHaveBeenCalledWith('return arguments[0]', extraArg) }) + + it('should keep patching remaining multiremote instances when one instance fails to resolve', async () => { + const browserA = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } + const browserB = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } + const multiRemoteBrowser = { + ...browser, + isMultiremote: true, + getInstance: vi.fn() + .mockImplementationOnce(() => { + throw new Error('no such instance') + }) + .mockImplementation((name: string) => name === 'browserA' ? browserA : browserB) + } as unknown as WebdriverIO.MultiRemoteBrowser + + const service = new BrowserstackService({} as any, { browserA: {}, browserB: {} } as any, { + user: 'foo', key: 'bar' + }) + await service.before(service['_config'] as any, [], multiRemoteBrowser as any) + + expect(browserA.overwriteCommand).not.toHaveBeenCalled() + expect(browserB.overwriteCommand).toHaveBeenCalledWith('execute', expect.any(Function)) + }) }) describe('beforeHook', () => { From c1ebb00ac7805ed90dabd51057473495c75ca7cf Mon Sep 17 00:00:00 2001 From: Anish Sinha Date: Mon, 17 Aug 2026 19:59:28 +0530 Subject: [PATCH 4/7] fix(browserstack-service): tolerate leading whitespace in BiDi executor routing Extract the executor-script check into isBrowserstackExecutorScript in util.ts and trim leading whitespace before the prefix match, so a padded browserstack_executor: script is routed over HTTP on BiDi instead of being swallowed by script.callFunction. Kept start-anchored and case-sensitive: this is a rewrite decision, unlike the a11y shouldPatchExecuteScript substring checks which only skip a scan. Co-Authored-By: Claude Opus 5 (1M context) --- packages/browserstack-service/src/service.ts | 5 +++-- packages/browserstack-service/src/util.ts | 9 +++++++++ .../browserstack-service/tests/service.test.ts | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index 9daea81..8f52c80 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -9,7 +9,8 @@ import { patchConsoleLogs, isTrue, getUniqueIdentifier, - getHookType + getHookType, + isBrowserstackExecutorScript } from './util.js' import type { BrowserstackConfig, BrowserstackOptions, MultiRemoteAction } from './types.js' import type { Pickle, Feature, ITestCaseHookParameter, CucumberHook } from './cucumber-types.js' @@ -911,7 +912,7 @@ export default class BrowserstackService implements Services.ServiceInstance { } browser.overwriteCommand('execute', async (originalExecute, script, ...args) => { - if (typeof script === 'string' && script.startsWith('browserstack_executor:')) { + if (isBrowserstackExecutorScript(script)) { return browser.executeScript(script, args) } return originalExecute(script, ...args) diff --git a/packages/browserstack-service/src/util.ts b/packages/browserstack-service/src/util.ts index 972ce45..8b308da 100644 --- a/packages/browserstack-service/src/util.ts +++ b/packages/browserstack-service/src/util.ts @@ -2150,6 +2150,15 @@ export function getMochaTestHierarchy(test: Frameworks.Test) { return value.reverse() } +/** + * True only for the hub-interpreted `browserstack_executor: {…}` magic string. + * Anchored to the start (leading whitespace tolerated) and case-sensitive, matching + * how the hub reads the payload — a plain script merely mentioning the token must not + * be rerouted off its normal transport. + */ +export const isBrowserstackExecutorScript = (script: unknown): script is string => + typeof script === 'string' && script.trimStart().startsWith('browserstack_executor:') + export const performO11ySync = async (browser: WebdriverIO.Browser) => { if (isBrowserstackSession(browser)) { await browser.execute(`browserstack_executor: ${JSON.stringify({ diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index 7a28f01..80dacfb 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -650,6 +650,24 @@ describe('before', () => { expect(originalExecute).toHaveBeenCalledWith('return arguments[0]', extraArg) }) + it('should route executor scripts with leading whitespace and leave look-alike scripts on BiDi', async () => { + (browser as any).isBidi = true + const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) + service['_routeBidiExecutorToHttp'](browser) + + const overwrite = vi.mocked(browser.overwriteCommand).mock.calls[0][1] as Function + const originalExecute = vi.fn() + + const padded = '\n browserstack_executor: {"action":"annotate"}' + await overwrite(originalExecute, padded) + expect(browser.executeScript).toHaveBeenCalledWith(padded, []) + expect(originalExecute).not.toHaveBeenCalled() + + const lookAlike = 'return document.title.includes("browserstack_executor:")' + await overwrite(originalExecute, lookAlike) + expect(originalExecute).toHaveBeenCalledWith(lookAlike) + }) + it('should not overwrite execute command for non-BiDi sessions', async () => { (browser as any).isBidi = false const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) From d9c585c1d0cad724a63a3bcc56f095eeabea3c19 Mon Sep 17 00:00:00 2001 From: Anish Sinha Date: Mon, 17 Aug 2026 20:18:04 +0530 Subject: [PATCH 5/7] fix(browserstack-service): gate BiDi executor routing on BrowserStack sessions The patch only checked isBidi, so a non-BrowserStack BiDi session had execute overwritten and prefix-matched scripts rerouted to /execute/sync. Add the isBrowserstackSession guard every other executor path in the package uses. Applied per multiremote instance, so a mixed multiremote now patches only the BrowserStack leg. Co-Authored-By: Claude Opus 5 (1M context) --- packages/browserstack-service/src/service.ts | 2 +- packages/browserstack-service/tests/service.test.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index 8f52c80..b6595ca 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -907,7 +907,7 @@ export default class BrowserstackService implements Services.ServiceInstance { } _routeBidiExecutorToHttp (browser: WebdriverIO.Browser) { - if (!browser.isBidi) { + if (!browser.isBidi || !isBrowserstackSession(browser)) { return } diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index 80dacfb..e0231ef 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -676,6 +676,17 @@ describe('before', () => { expect(browser.overwriteCommand).not.toHaveBeenCalled() }) + it('should not overwrite execute command for non-BrowserStack BiDi sessions', async () => { + (browser as any).isBidi = true + const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) + // describe('_update') leaves a file-wide getCloudProvider spy returning 'browserstack', + // so stub the session check itself for this one call rather than restoring it. + vi.spyOn(utils, 'isBrowserstackSession').mockReturnValueOnce(false) + service['_routeBidiExecutorToHttp'](browser) + + expect(browser.overwriteCommand).not.toHaveBeenCalled() + }) + it('should overwrite execute on each instance for multiremote', async () => { const browserA = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } const browserB = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } From b6f1f18ed86c5fd69a94fb53bfeebdb75e49cd23 Mon Sep 17 00:00:00 2001 From: Anish Sinha Date: Mon, 17 Aug 2026 20:23:50 +0530 Subject: [PATCH 6/7] fix(browserstack-service): route executeAsync executor payloads over HTTP on BiDi executeAsync hits the same isBidi && !isMultiremote gate as execute in webdriverio, so a user calling browser.executeAsync with a browserstack_executor payload had it swallowed by script.callFunction. Mirror the overwrite to executeAsyncScript, which is where the classic path already lands. Co-Authored-By: Claude Opus 5 (1M context) --- packages/browserstack-service/src/service.ts | 9 +++++- .../tests/service.test.ts | 29 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index b6595ca..80288ad 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -247,7 +247,7 @@ export default class BrowserstackService implements Services.ServiceInstance { try { this._routeBidiExecutorToHttp(resolveBrowser()) } catch (err) { - BStackLogger.warn(`Failed to patch execute for BiDi browserstack_executor routing${label ? ` on ${label}` : ''}; executor commands may not work in BiDi sessions: ${err}`) + BStackLogger.warn(`Failed to patch execute/executeAsync for BiDi browserstack_executor routing${label ? ` on ${label}` : ''}; executor commands may not work in BiDi sessions: ${err}`) } } @@ -917,6 +917,13 @@ export default class BrowserstackService implements Services.ServiceInstance { } return originalExecute(script, ...args) }) + + browser.overwriteCommand('executeAsync', async (originalExecuteAsync, script, ...args) => { + if (isBrowserstackExecutorScript(script)) { + return browser.executeAsyncScript(script, args) + } + return originalExecuteAsync(script, ...args) + }) } _multiRemoteAction (action: MultiRemoteAction) { diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index e0231ef..3644930 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -113,6 +113,7 @@ beforeEach(() => { browser = { execute: vi.fn(), executeScript: vi.fn(), + executeAsyncScript: vi.fn(), overwriteCommand: vi.fn(), on: vi.fn(), sessionId: sessionId, @@ -676,6 +677,26 @@ describe('before', () => { expect(browser.overwriteCommand).not.toHaveBeenCalled() }) + it('should overwrite executeAsync command to route browserstack_executor via executeAsyncScript', async () => { + (browser as any).isBidi = true + const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) + service['_routeBidiExecutorToHttp'](browser) + + expect(browser.overwriteCommand).toHaveBeenCalledWith('executeAsync', expect.any(Function)) + + const overwrite = vi.mocked(browser.overwriteCommand).mock.calls + .find(([command]) => command === 'executeAsync')?.[1] as Function + const originalExecuteAsync = vi.fn() + + await overwrite(originalExecuteAsync, 'browserstack_executor: {"action":"annotate"}') + expect(browser.executeAsyncScript).toHaveBeenCalledWith('browserstack_executor: {"action":"annotate"}', []) + expect(originalExecuteAsync).not.toHaveBeenCalled() + + const extraArg = { key: 'value' } + await overwrite(originalExecuteAsync, 'arguments[0](1)', extraArg) + expect(originalExecuteAsync).toHaveBeenCalledWith('arguments[0](1)', extraArg) + }) + it('should not overwrite execute command for non-BrowserStack BiDi sessions', async () => { (browser as any).isBidi = true const service = new BrowserstackService({} as any, [{}] as any, { user: 'foo', key: 'bar', capabilities: {} }) @@ -688,8 +709,8 @@ describe('before', () => { }) it('should overwrite execute on each instance for multiremote', async () => { - const browserA = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } - const browserB = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } + const browserA = { executeScript: vi.fn(), executeAsyncScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } + const browserB = { executeScript: vi.fn(), executeAsyncScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } const multiRemoteBrowser = { ...browser, isMultiremote: true, @@ -716,8 +737,8 @@ describe('before', () => { }) it('should keep patching remaining multiremote instances when one instance fails to resolve', async () => { - const browserA = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } - const browserB = { executeScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } + const browserA = { executeScript: vi.fn(), executeAsyncScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionA', isBidi: true } + const browserB = { executeScript: vi.fn(), executeAsyncScript: vi.fn(), overwriteCommand: vi.fn(), sessionId: 'sessionB', isBidi: true } const multiRemoteBrowser = { ...browser, isMultiremote: true, From 283dd9a3c228ccf43e3a19d79b330d6779282018 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:02:32 +0000 Subject: [PATCH 7/7] chore(changeset): auto-generate from PR template (patch) --- .changeset/pr-119.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pr-119.md b/.changeset/pr-119.md index 12b2e1e..256a866 100644 --- a/.changeset/pr-119.md +++ b/.changeset/pr-119.md @@ -2,4 +2,4 @@ "@wdio/browserstack-service": patch --- -- Fixed BrowserStack executor commands (session name, status, annotations) being ignored in WebDriver BiDi sessions. +- Fixed `browserstack_executor` commands issued through `browser.execute()` or `browser.executeAsync()` being ignored in WebDriver BiDi sessions.