diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ef0c5766..bcc6fce3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -6,7 +6,7 @@ A descriptive map of how the pieces fit together. For conventions and coding sta ## At a glance -A devtools dashboard for end-to-end browser tests. Three test frameworks (WebdriverIO, Nightwatch, Selenium) push the same normalized event stream through a single backend into a single browser UI. +A devtools dashboard for end-to-end browser tests. Four adapters — WebdriverIO, Nightwatch and Selenium in JavaScript, plus Selenium in Python — push the same normalized event stream through a single backend into a single browser UI. ``` [user's test framework] @@ -56,7 +56,7 @@ Contains: - `SessionCapturerBase` — orchestrates per-session capture (console/stream patching, WS connection, command-id bookkeeping, upstream-send guard with `onUpstreamDrop` hook). - `TestReporterBase` — common reporter behavior, extended by Nightwatch + Selenium reporters (Service uses `@wdio/reporter` from WDIO directly). -- `ScreencastRecorderBase` — frame buffer + polling fallback shared by all three adapters. +- `ScreencastRecorderBase` — frame buffer + polling fallback shared by the three JavaScript adapters. - `resolveAdapterOutputDir` — the dir-resolution helper that picks where screencast/trace files land (test-file dir → config dir → cwd, with a `node_modules/` skip). - Pure helpers: `assert-patcher`, `bidi` (`attachBidiHandlers`, `loadSeleniumSubmodule`, `arrayHeadersToObject`), `console` (`stripAnsi`, `detectLogLevel`, `createConsoleLogEntry`, `mapChromeBrowserLogs`, `chromeLogLevelToLogLevel`), `error` (`serializeError`, `errorMessage`), `finalize-screencast`, `net` (`isPortInUse`, `findFreePort`, `getRequestType`), `performance-capture` (`CAPTURE_PERFORMANCE_SCRIPT`, `applyPerformanceData`), `retry-tracker`, `script-loader` (`loadInjectableScript`, `pollUntilReady`), `stack` (`isUserCodeFrame`, `normalizeFilePath`, `getCallSourceFromStack`), `suite-helpers`, `test-discovery` (`findTestDefinitions`, `extractTestMetadata`), `uid` (`generateStableUid`, `deterministicUid`, `resetSignatureCounters`), `video-encoder` (`encodeToVideo`). @@ -100,6 +100,23 @@ Contains: Imports from: `core`, `shared`, `selenium-webdriver` (peer). Does not import: other adapter packages, `backend`, `app`. +### `packages/selenium-devtools-py` — Python Selenium adapter + +The same contract from another language. Not a port of the JavaScript adapters: it shares no code with them, only the `{scope, data}` wire format, so nothing here imports `core` or `shared` — the parts of those it needs are **generated** into `src/selenium_devtools/_contract.py` by `scripts/gen_contract.py`, which doubles as a drift guard. + +Contains: + +- `instrumentation.py` — wraps `WebDriver.execute`, the one chokepoint every driver and element command flows through. +- `bidi.py` — console, JS errors and network over selenium's regenerated BiDi layer (4.44+), observe-only. +- `snapshot.py` / `bidi_preload.py` — the shared page collector, registered at document start. +- `screencast.py` / `cdp_screencast.py` — pushed CDP frames on Chrome, one screenshot per command elsewhere. +- `pytest_plugin.py` — the suite tree, keyed by pytest nodeid; auto-discovered, opt-in by env var. +- `rerun.py`, `run_id.py`, `performance.py`, `transport.py` — run controls, run identity, navigation timings, and a stdlib-only WebSocket client. + +Live mode only: it does not write a trace archive. That work, and the question of whether the ~3,100 lines of trace transforms in `core` should move behind the wire rather than be reimplemented per language, is tracked in [#330](https://github.com/webdriverio/devtools/issues/330) and [#278](https://github.com/webdriverio/devtools/issues/278). + +Imports from: `selenium` (required, 4.44+). Does not import: `core`, `shared`, any other package. + ### `packages/backend` The server adapters connect to and the app talks to. @@ -236,9 +253,9 @@ The architecture above is the actual state of the repo. Where it diverges from t Notable in-place pieces worth knowing about: - `replaceCommand` has two semantics across adapters — Selenium mutates the existing entry in place (preserves `_id`/`id` continuity for chained calls); Nightwatch splices and reissues with a new `_id`. Both call the same `core/suite-helpers` factories; the storage strategy stays adapter-specific because the runner integrations differ. -- `patchNodeAssert` (via `core/assert-patcher`) is wired in all three adapters, default-on behind each adapter's `captureAssertions` option (opt out with `captureAssertions: false`), so `node:assert` — and, where the framework exposes assertion hooks, `expect` matchers — surface as trace action rows. Framework matcher libraries differ (Service taps expect-webdriverio's hooks; Nightwatch native `assert`/`verify` and Selenium's `node:assert` surface via their reconcile/patch paths), so the remaining gap is Selenium's jest-style `expect()`. +- `patchNodeAssert` (via `core/assert-patcher`) is wired in all three JavaScript adapters, default-on behind each adapter's `captureAssertions` option (opt out with `captureAssertions: false`), so `node:assert` — and, where the framework exposes assertion hooks, `expect` matchers — surface as trace action rows. Framework matcher libraries differ (Service taps expect-webdriverio's hooks; Nightwatch native `assert`/`verify` and Selenium's `node:assert` surface via their reconcile/patch paths), so the remaining gap is Selenium's jest-style `expect()`. - BiDi is auto-attached in Service and Selenium. Nightwatch is opt-in via `bidi: true` and requires `webSocketUrl: true` in capabilities — historically Nightwatch users haven't all enabled BiDi by default. -- Performance API capture (`CAPTURE_PERFORMANCE_SCRIPT`) is identical across all three adapters; each wires it into its own afterCommand-equivalent path. +- Performance API capture (`CAPTURE_PERFORMANCE_SCRIPT`) is identical across the three JavaScript adapters; each wires it into its own afterCommand-equivalent path. The Python adapter carries its **own copy** of that browser-side script and of `applyPerformanceData`, because `core` is TypeScript — one of the concrete duplications [#278](https://github.com/webdriverio/devtools/issues/278) is weighing. - Output directory for screencast videos and trace files is resolved through `core/resolveAdapterOutputDir` — adapters feed `userConfiguredDir` (WDIO honors `wdio.conf.ts`'s `outputDir`/`rootDir`), `testFilePath` (Selenium/Nightwatch), and `configPath` (Nightwatch), and the helper picks the first writable, non-`node_modules/` candidate. For per-package implementation details, see each package's `README.md` diff --git a/README.md b/README.md index a3695816..9b63cc82 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A powerful browser devtools extension for debugging, visualizing, and controlling test executions in real-time. -Works with **WebdriverIO**, **[Nightwatch.js](./packages/nightwatch-devtools/README.md)**, and **[Selenium WebDriver](./packages/selenium-devtools/README.md)** (any test runner) — same backend, same UI, same capture infrastructure. +Works with **WebdriverIO**, **[Nightwatch.js](./packages/nightwatch-devtools/README.md)**, **[Selenium WebDriver](./packages/selenium-devtools/README.md)** (any test runner), and **[Python Selenium](./packages/selenium-devtools-py/README.md)** — same backend, same UI, same capture infrastructure. It runs in two modes: **live** — an interactive dashboard that opens as your tests run — and **trace** — a portable `trace.zip` artifact for offline replay, CI, and AI-agent diffing. @@ -98,9 +98,9 @@ The dashboard runs in one of **two modes**, set per adapter via the shared `mode - **`live`** (default) — the interactive DevTools UI window described above. - **`trace`** — a headless capture path that writes a portable trace archive, opened later in the **trace player**. -All three adapters (`@wdio/devtools-service`, `@wdio/selenium-devtools`, `@wdio/nightwatch-devtools`) emit the **same normalized trace** through the shared `@wdio/devtools-core` capture library, so one archive format and one player serve every framework. +All three JavaScript adapters (`@wdio/devtools-service`, `@wdio/selenium-devtools`, `@wdio/nightwatch-devtools`) emit the **same normalized trace** through the shared `@wdio/devtools-core` capture library, so one archive format and one player serve every framework. -The trace **format and the player are identical** across the three adapters, but **capture completeness varies** — WebdriverIO is the most complete; Selenium and Nightwatch cover the core flow with some gaps (e.g. inline-Allure per-test artifacts, retry-aware retention, Cucumber step nesting, auto BiDi). Each adapter's README lists its specifics. +The trace **format and the player are identical** across those three, but **capture completeness varies** — WebdriverIO is the most complete; Selenium and Nightwatch cover the core flow with some gaps (e.g. inline-Allure per-test artifacts, retry-aware retention, Cucumber step nesting, auto BiDi). Each adapter's README lists its specifics. **Trace-mode support by adapter:** @@ -166,7 +166,7 @@ The player exposes everything captured in the archive: - **Dense filmstrip** — with `filmstrip` enabled, the timeline scrubs a continuous screencast for smooth playback rather than one frame per action. - **Timeline input markers** — keyboard actions and pointer hits (commands with a captured hit point) get distinct glyphs on the timeline. -The `show-trace` bin is exposed by each adapter (`@wdio/devtools-service`, `@wdio/nightwatch-devtools`, `@wdio/selenium-devtools`), so `pnpm show-trace ` / `npx show-trace ` work in any project that installs one — no extra dependency. +The `show-trace` bin is exposed by each JavaScript adapter (`@wdio/devtools-service`, `@wdio/nightwatch-devtools`, `@wdio/selenium-devtools`), so `pnpm show-trace ` / `npx show-trace ` work in any project that installs one — no extra dependency. **Other viewers.** The trace uses a portable NDJSON schema, so the same `.zip` also opens in other compatible standalone trace viewers that read the format, and — because it shares that on-disk format — is what an Allure report's **embedded trace viewer** (Allure ≥ 2.35) reads. See the [backend README](./packages/backend/README.md#trace-serving--show-trace) for the reader details. @@ -222,7 +222,7 @@ These are emulator-specific issues; on a physical phone with USB debugging only ### 🏗️ Architecture - **Frontend**: Lit web components with reactive state management (`@lit/context`) - **Backend**: Fastify server with WebSocket streaming for real-time updates -- **Shared core**: All three adapters share the same capture/reporting library (`@wdio/devtools-core`) — `SessionCapturerBase`, `TestReporterBase`, `ScreencastRecorderBase`, plus pure helpers for console/network/error/sourcemap/BiDi +- **Shared core**: The three JavaScript adapters share the same capture/reporting library (`@wdio/devtools-core`) — `SessionCapturerBase`, `TestReporterBase`, `ScreencastRecorderBase`, plus pure helpers for console/network/error/sourcemap/BiDi - **Process Management**: Tree-kill for proper cleanup of spawned processes See [ARCHITECTURE.md](./ARCHITECTURE.md) for the full package map and data flow, and [CLAUDE.md](./CLAUDE.md) for the conventions in place across the repo. @@ -276,7 +276,12 @@ npm install @wdio/nightwatch-devtools npm install @wdio/selenium-devtools ``` -> See the [Nightwatch Integration](#nightwatch-integration) and [Selenium Integration](#selenium-integration) sections for configuration details. +**Python (Selenium):** +```bash +pip install -e packages/selenium-devtools-py # or: pip install selenium-devtools-py (when published) +``` + +> See the [Nightwatch Integration](#nightwatch-integration), [Selenium Integration](#selenium-integration) and [Python Integration](#python-integration) sections for configuration details. ## Configuration @@ -367,6 +372,14 @@ Using `selenium-webdriver` directly — under Mocha, Jest, Cucumber, or a plain → **[`@wdio/selenium-devtools`](./packages/selenium-devtools/README.md)** — per-runner setup, configuration options, and screencast details. +## Python Integration + +Writing your Selenium tests in Python? A fourth adapter feeds the same backend and UI over the same language-neutral `{scope, data}` contract. Under pytest nothing goes in your test files — the plugin is auto-discovered and turns itself on from `DEVTOOLS_ENABLE=1`. + +Live mode only for now: it streams to the dashboard and does not yet write a `trace.zip` (tracked in the trace-mode epic), so the sections above about trace artifacts describe the three JavaScript adapters. + +→ **[`selenium-devtools-py`](./packages/selenium-devtools-py/README.md)** — pytest and plain-script setup, assertions, run controls, Preserve & Rerun, and parallel runs. + ## Project Structure ``` @@ -379,10 +392,11 @@ packages/ ├── elements/ # Element-detection scripts — getSnapshot, a11y tree, element list (@wdio/elements) ├── service/ # WebdriverIO adapter (@wdio/devtools-service) ├── nightwatch-devtools/ # Nightwatch adapter (@wdio/nightwatch-devtools) -└── selenium-devtools/ # Selenium WebDriver adapter (@wdio/selenium-devtools) +├── selenium-devtools/ # Selenium WebDriver adapter (@wdio/selenium-devtools) +└── selenium-devtools-py/ # Python Selenium adapter (selenium-devtools-py) — live mode ``` -`shared` and `core` are workspace-internal (`"private": true`) — every consumer bundles them into its own `dist/` at build time. The three adapter packages each translate framework-specific hooks into calls on `core`'s shared capture library; `backend` and `app` import only from `shared` and communicate via the WS/HTTP boundary. +`shared` and `core` are workspace-internal (`"private": true`) — every consumer bundles them into its own `dist/` at build time. The three JavaScript adapter packages each translate framework-specific hooks into calls on `core`'s shared capture library; the Python adapter speaks the same wire contract without sharing that code, which is what [#278](https://github.com/webdriverio/devtools/issues/278) exists to address; `backend` and `app` import only from `shared` and communicate via the WS/HTTP boundary. ## Contributing