The WDIO service's beforeCommand awaits page-side commands from inside the hook that wraps the command being issued. On desktop chromedriver that re-entrancy is tolerated. On an Appium mobile-web session it deadlocks: the wrapped command never reaches the browser.
packages/service/src/index.ts beforeCommand:
if (PAGE_TRANSITION_COMMANDS.includes(command)) { // includes 'url'
await this.#sessionCapturer.captureTrace(this.#browser) // browser.execute(...)
}
if (topLevelUserCommand && mode === 'trace' && …) {
const snap = await captureActionSnapshot(this.#browser, …) // 2 injected scripts + url + title + screenshot
await this.#markDocument() // browser.execute(...)
}
Measured
Emulator medium_phone, API 36 arm64, Appium 3.7.0 + uiautomator2 7.6.1, browserName: Chrome.
|
result |
| the example spec without the devtools service |
2 passing in 1.6 s |
| the same spec with the service |
2 failing, 6 m 13 s, every command at the 180 s timeout |
A screenshot of the emulator during the failing run shows Chrome on its new-tab page: browser.url() never executed.
Everything the service wants to do is individually fast — measured over raw HTTP against the same Appium, on a session created the same way:
| call |
time |
POST /session |
ok |
POST /url → http://10.0.2.2:8099/ |
0 s |
GET /title → Mobile web capture |
ok |
POST /execute/sync (return 1+1) |
0 s |
POST /execute/sync with a 213 KB script (the collector's size) |
0 s |
POST /execute/sync (return window.visualViewport) |
1 s |
So it is neither the script size, nor execute itself, nor the network. It is the nesting.
The failure then cascades: the timed-out execute/sync is followed by ECONNREFUSED 127.0.0.1:<chromedriver port>, then Cannot read properties of undefined (reading 'jwproxy'), then Method has not yet been implemented for url (the session has fallen back to the native context), then the UiAutomator2 instrumentation crashes. All of that is downstream of the first deadlock.
Why native mobile is unaffected
isNativeAppSession (added for #372) makes all three of those calls no-ops, so a native app session never re-enters. The native example passes on the same emulator and writes a correct trace (device: {platform: 'android', name: 'sdk_gphone64_arm64', version: '16'}, viewport 1080 × 2400). The bug is specific to a session that HAS a document and is driven through Appium.
This is the same class the Nightwatch adapter already solved
CLAUDE.md records it for Nightwatch: "browser.* are QUEUED commands, so a probe issued from inside the plugin's own command hook enqueues behind the command still running and never resolves" — which is why that adapter routes its probes over raw WebDriver HTTP (helpers/webdriverHttp.ts). The WDIO service has no equivalent escape hatch, and until now never needed one because desktop chromedriver does not serialise this way.
Options
- Gate the in-hook page calls on
isAppiumSession rather than isNativeAppSession. Smallest change, keeps every run working, and costs a mobile-web Appium session its pre-action DOM capture and its __wdioSnapMark navigation detection. The per-command screenshots and the whole command timeline survive.
- Stop awaiting in the hook — fire-and-forget with the result reconciled later. Preserves capture, but changes the ordering guarantee the pre-action snapshot depends on ("state BEFORE this action executes"), for every adapter and platform.
- A raw-HTTP transport for the service's own probes, mirroring
nightwatch-devtools/src/helpers/webdriverHttp.ts. Most faithful, most work.
(1) is the safe default and (3) is the real fix; (2) trades a documented invariant for it.
Until this is fixed, DEVTOOLS_MOBILE=web against Appium is unusable with the service attached, and examples/MOBILE.md says so.
The WDIO service's
beforeCommandawaits page-side commands from inside the hook that wraps the command being issued. On desktop chromedriver that re-entrancy is tolerated. On an Appium mobile-web session it deadlocks: the wrapped command never reaches the browser.packages/service/src/index.tsbeforeCommand:Measured
Emulator
medium_phone, API 36 arm64, Appium 3.7.0 + uiautomator2 7.6.1,browserName: Chrome.A screenshot of the emulator during the failing run shows Chrome on its new-tab page:
browser.url()never executed.Everything the service wants to do is individually fast — measured over raw HTTP against the same Appium, on a session created the same way:
POST /sessionPOST /url→http://10.0.2.2:8099/GET /title→Mobile web capturePOST /execute/sync(return 1+1)POST /execute/syncwith a 213 KB script (the collector's size)POST /execute/sync(return window.visualViewport)So it is neither the script size, nor
executeitself, nor the network. It is the nesting.The failure then cascades: the timed-out
execute/syncis followed byECONNREFUSED 127.0.0.1:<chromedriver port>, thenCannot read properties of undefined (reading 'jwproxy'), thenMethod has not yet been implementedforurl(the session has fallen back to the native context), then the UiAutomator2 instrumentation crashes. All of that is downstream of the first deadlock.Why native mobile is unaffected
isNativeAppSession(added for #372) makes all three of those calls no-ops, so a native app session never re-enters. The native example passes on the same emulator and writes a correct trace (device: {platform: 'android', name: 'sdk_gphone64_arm64', version: '16'}, viewport1080 × 2400). The bug is specific to a session that HAS a document and is driven through Appium.This is the same class the Nightwatch adapter already solved
CLAUDE.md records it for Nightwatch: "
browser.*are QUEUED commands, so a probe issued from inside the plugin's own command hook enqueues behind the command still running and never resolves" — which is why that adapter routes its probes over raw WebDriver HTTP (helpers/webdriverHttp.ts). The WDIO service has no equivalent escape hatch, and until now never needed one because desktop chromedriver does not serialise this way.Options
isAppiumSessionrather thanisNativeAppSession. Smallest change, keeps every run working, and costs a mobile-web Appium session its pre-action DOM capture and its__wdioSnapMarknavigation detection. The per-command screenshots and the whole command timeline survive.nightwatch-devtools/src/helpers/webdriverHttp.ts. Most faithful, most work.(1) is the safe default and (3) is the real fix; (2) trades a documented invariant for it.
Until this is fixed,
DEVTOOLS_MOBILE=webagainst Appium is unusable with the service attached, andexamples/MOBILE.mdsays so.