diff --git a/.changeset/bidi-inspector-dispatch.md b/.changeset/bidi-inspector-dispatch.md new file mode 100644 index 00000000..4f2a35d0 --- /dev/null +++ b/.changeset/bidi-inspector-dispatch.md @@ -0,0 +1,6 @@ +--- +'@wdio/selenium-devtools': patch +'@wdio/nightwatch-devtools': patch +--- + +Selenium and Nightwatch traces on Chrome listed every BiDi request twice, a pending row beside the completed one, because selenium-webdriver's network inspector delivers every network event to every subscriber. Each request is now one row, a redirect continues that row rather than adding one, and a request the browser gives up on is closed as a failure instead of staying pending. diff --git a/.changeset/network-resource-timing.md b/.changeset/network-resource-timing.md new file mode 100644 index 00000000..fff97312 --- /dev/null +++ b/.changeset/network-resource-timing.md @@ -0,0 +1,12 @@ +--- +'@wdio/devtools-script': minor +'@wdio/devtools-service': minor +'@wdio/selenium-devtools': minor +'@wdio/nightwatch-devtools': minor +'@wdio/devtools-backend': patch +'@wdio/devtools-app': patch +--- + +Capture network requests on browsers without WebDriver BiDi: Safari, and every session Appium serves. Network capture rode on BiDi (or, in Nightwatch, Chrome's performance log), so those sessions showed an empty Network tab. The injected page collector now also reads the document's Resource Timing buffer, which every browser keeps: the document, scripts, styles, images and fonts, each with its timing phases and size, and on Chromium its status. It reads the buffer at install, so loads made before the collector existed are included, then observes. The collector's existing `fetch`/`XMLHttpRequest` patch keeps supplying headers, status and bodies for the calls the page makes, and a request both saw is listed once. + +What Resource Timing cannot see is stated rather than left blank: the detail card of such a request names that this browser exposes no headers or body for it (and on Safari no status), a request with no status is coloured as unknown rather than as still in flight, the method column is a dash where the browser names none, and the trace's `trace.network` entry carries `_source: "resource-timing"` and the captured `_resourceType` so a reopened trace renders the same row rather than a failed request. When BiDi is serving a session, the Resource Timing rows are dropped at ingest, so Chrome sessions are unchanged. Serving means delivering, not subscribed: against Appium 3.7's XCUITest driver the BiDi inspectors attach and then send no event, so each adapter now counts BiDi as a source only once a network event has arrived (the Selenium adapter applies the same rule to its console gate). Nightwatch's performance log, which is read after the page is drained, now upgrades the page's row for each load in place with the headers and status it has, so a Chrome session lists each request once and a Safari or Appium session whose capabilities still name a performance log is not left with an empty tab. A native app session has no document and still has no network capture, because the driver exposes none. A mobile Chrome session, which opens on the browser's own new-tab page before the first navigation, no longer lists that page, and a second injection of the collector into a document keeps the first one instead of replacing it and dropping what it had buffered. A `driver.wait()` in a Selenium test now shows as the one command the test made rather than a row per poll. Safari delivers synthesized clicks only to its frontmost window and reports success either way, so once the live dashboard took the front every click reached nothing. The Selenium adapter now brings the session's own window forward before each element click, key input, clear or submit on Safari, and opens its dashboard behind it; the WDIO service, Nightwatch and Python bring Safari forward once after the session starts. The collector injected into a loaded page no longer waits for an animation frame before it starts listening, which on Safari under automation could be never, so a live run on Safari no longer replays an unchanging page with empty fields. On a browser without document-start injection (Safari, Appium), the Selenium adapter's navigation now resolves only once the collector is in the destination, so typing that follows a navigation is captured instead of racing the injection, and the collector's DOM anchor carries each form control's current value and checked state, so a form typed into before the collector arrived no longer replays empty. diff --git a/CLAUDE.md b/CLAUDE.md index ca16c55b..3b18691d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -197,6 +197,8 @@ For UI or runtime changes, `examples//` is the verification harness. When CI can't run an example (no real browser), the PR description says so explicitly. +The venv's editable install of `selenium_devtools` points at the primary checkout, so in a worktree every Python example, and the backend and page collector it resolves through `_find_monorepo_backend`, come from `main` unless `PYTHONPATH=packages/selenium-devtools-py/src` is exported first. A Python run that reports the old behaviour from a worktree is usually this, not the change. + ### Skipping tests that depend on workspace-internal build artifacts A handful of tests need `@wdio/devtools-script` to be built first (the browser-injected bundle). CI test jobs sometimes run before that build step; those tests gate on `it.skipIf` after probing `createRequire(import.meta.url).resolve('@wdio/devtools-script')`. Locally they run normally. @@ -269,6 +271,10 @@ Documented divergences from the conventions above. They exist today as debt to b - `replaceCommand` has two semantics — Selenium mutates in place (preserves `_id`/`id` for chained calls); Nightwatch splices and reissues. Both call the same `core/suite-helpers` factories; the storage strategy stays adapter-specific because runner integrations differ. Could be unified by parameterizing the policy if the divergence ever causes a real problem. - `patchNodeAssert` (via `core/assert-patcher`) is now wired in all three adapters, default-on behind each adapter's `captureAssertions` option (opt out with `captureAssertions: false`). Framework matcher libraries differ: Service taps expect-webdriverio's `beforeAssertion`/`afterAssertion` hooks so passing+failing matchers render as `expect.*` actions (mechanism in the assert-capture entry below); Nightwatch native `assert`/`verify` and Selenium's `node:assert` also surface passing+failing rows via their reconcile/patch paths. The remaining gap is Selenium's jest-style `expect()` (and chai): jest/vitest expose no pass+fail assertion hook, so only failing matchers surface there. - BiDi is auto-attached in Service and Selenium; Nightwatch is opt-in via `bidi: true` and requires `webSocketUrl: true` in capabilities. +- **Network has three sources, and the page's Resource Timing is the floor.** BiDi (all four adapters) and Nightwatch's Chrome performance log are driver-side and carry headers and status. The page collector carries two: its patched `fetch`/`XMLHttpRequest` (the only source with bodies) and the document's Resource Timing buffer (`script/src/collectors/resourceTiming.ts`): every load with timing phases and size, a status on Chromium only, never headers. Resource Timing is the only network source a browser without BiDi has, which is Safari and every Appium session (Appium's BiDi reference lists `session.*` alone). The collector reads the buffer at construction, which is what covers loads made before a `