Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
429c544
test: add cross-adapter capture-parity harness (Layer A)
vishnuv688 Jul 27, 2026
009928b
test: add cross-adapter verification harness (Layer A + Layer B)
vishnuv688 Jul 27, 2026
f6797e7
test(visual): enforce WebDriver Classic to avoid flaky BiDi shadow-DO…
vishnuv688 Jul 27, 2026
4e2dff5
fix(nightwatch): generate cucumber traces (build the hooks + capture …
vishnuv688 Jul 27, 2026
f193aac
feat(nightwatch): capture native asserts in cucumber traces
vishnuv688 Jul 27, 2026
2766e27
test(visual): pin device-scale-factor so HEADED matches headless base…
vishnuv688 Jul 27, 2026
252c889
test(harness): add live-mode parity layer + record-live recorder
vishnuv688 Jul 28, 2026
6aa9b7c
test(harness): finalize Layer B — re-mint visual baselines + live-das…
vishnuv688 Jul 28, 2026
fcf2b0b
fix(capture): recover DOM capture on documents that miss script injec…
vishnuv688 Jul 28, 2026
e4a6507
test(app): add WDIO component tests for the Lit dashboard
vishnuv688 Jul 29, 2026
52ac526
test(harness): drop the fixture-driven verification layers
vishnuv688 Jul 29, 2026
e1a1275
refactor(shared): make the trace.zip layout one definition
vishnuv688 Jul 29, 2026
c603d31
test(core): table the traceGranularity × tracePolicy grid
vishnuv688 Jul 29, 2026
8a5687b
test(core): assert the artifacts manifest per trace config
vishnuv688 Jul 29, 2026
4ab67ca
test(app): use path aliases and the real demo URLs in component specs
vishnuv688 Jul 29, 2026
59d3125
test(app): cover Tier 1 components with rendering specs
vishnuv688 Jul 29, 2026
ace6f85
test(app): cover Tier 2 components with rendering specs
vishnuv688 Jul 29, 2026
56422d3
fix(app): stop the Errors tab mangling assertion failures
vishnuv688 Jul 30, 2026
914d94b
test(app): cover the compare panel
vishnuv688 Jul 30, 2026
88035a2
test(app): derive sidebar row state instead of asserting it
vishnuv688 Jul 30, 2026
d5ec4ca
test(app): harden panel and player specs, unit-test their helpers
vishnuv688 Jul 30, 2026
1ed0d96
test(app): add an attachable mode to the component-test runner
vishnuv688 Jul 30, 2026
aeaa536
fix(backend,core,service): keep run state across a run's worker sockets
vishnuv688 Jul 30, 2026
d72eca0
fix(backend): match the runner's own full-title form in rerun filters
vishnuv688 Jul 30, 2026
c5450b6
fix(app): window the compare panel to a preserved test's own uid
vishnuv688 Jul 31, 2026
51bd224
fix(ci): commit the vitest config the root gitignore was swallowing
vishnuv688 Jul 31, 2026
9dcbcbb
chore(elements): Added @wdio/elements package context
vishnuv688 Jul 31, 2026
0488dcc
fix(app): derive test outcome one way instead of four
vishnuv688 Jul 31, 2026
29fbed6
fix(app): resolve one failure site per step, strip the ESC byte
vishnuv688 Jul 31, 2026
6793d3d
fix(app): show the Compare tab only for the selected test
vishnuv688 Jul 31, 2026
656050a
fix(app): head each Errors row with what failed
vishnuv688 Jul 31, 2026
9f7f882
fix(app): render the empty-state copy the panels pass
vishnuv688 Jul 31, 2026
8b7b5c0
fix(app): tear down the player's listeners, show the viewport
vishnuv688 Jul 31, 2026
0314345
fix(app): honour the theme per instance, send elapsedTime, nest legally
vishnuv688 Jul 31, 2026
5d0f475
fix(app): stop two event details resolving to any
vishnuv688 Jul 31, 2026
09f5a04
ci: run the component suite, cap workers at two
vishnuv688 Jul 31, 2026
c5136e4
test(app): cover the sidebar shell, shortcuts and DataManager
vishnuv688 Jul 31, 2026
1da6655
docs: correct claims the code no longer supports
vishnuv688 Jul 31, 2026
6b272c1
build: make tsc a gate that actually runs
vishnuv688 Jul 31, 2026
a6bd883
ci: run typecheck and the coverage floor
vishnuv688 Jul 31, 2026
f22ec1d
test: pin the coverage denominator to the source tree
vishnuv688 Jul 31, 2026
908e871
fix(tests): clear the 28 errors the dead type gate had accumulated
vishnuv688 Jul 31, 2026
61acd2e
fix(app): give a suite no verdict until a child settles
vishnuv688 Jul 31, 2026
038f934
refactor(app): drop CollectedError.command and the comments that over…
vishnuv688 Jul 31, 2026
81c40cc
refactor(app): one elapsed-time rule, one theme source
vishnuv688 Jul 31, 2026
fd7a23c
fix(app): explain the empty panels instead of drawing a skeleton forever
vishnuv688 Jul 31, 2026
20e1136
fix: point two ambient globals at the canonical shared types
vishnuv688 Jul 31, 2026
2941dcc
fix(app): keep the window a step's remaining bound gives you
vishnuv688 Jul 31, 2026
b4aed39
fix(app): let a running test be stopped, and offer the control the tr…
vishnuv688 Jul 31, 2026
3fbd334
fix(app): wire the sidebar selection into the selected-test context
vishnuv688 Jul 31, 2026
ddfa2d0
fix: capture and replay text-only DOM changes
vishnuv688 Jul 31, 2026
aa4dcb7
fix: recognise the native a11y header instead of parsing it as a node
vishnuv688 Jul 31, 2026
881fc27
fix: one request-type vocabulary across capture, wire and UI
vishnuv688 Jul 31, 2026
89760bb
docs: correct the component-testing inventory
vishnuv688 Jul 31, 2026
53a85b4
test(app): pin that an Errors row is not a control
vishnuv688 Jul 31, 2026
18f9c6e
fix: update pnpm-lock.yaml
vishnuv688 Jul 31, 2026
f86aa0d
revert(test): pin the coverage denominator to the source tree
vishnuv688 Aug 2, 2026
c8ca562
test(app): make the player specs able to fail, and cover what was cla…
vishnuv688 Aug 2, 2026
5dc7751
test(app): make the panel specs able to fail
vishnuv688 Aug 2, 2026
bec50af
test(app): make the sidebar, shell and workbench specs able to fail
vishnuv688 Aug 2, 2026
8d7ef50
docs: correct the component-testing inventory against the code
vishnuv688 Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ body:
- WebdriverIO (@wdio/devtools-service)
- Nightwatch (@wdio/nightwatch-devtools)
- Selenium WebDriver (@wdio/selenium-devtools)
- None — @wdio/elements used standalone
validations:
required: true

Expand All @@ -40,7 +41,7 @@ body:
id: devtools-version
attributes:
label: DevTools package version
description: The version of the adapter package above (check your package.json or lockfile).
description: The version of the package you selected above (check your package.json or lockfile).
placeholder: e.g. 0.4.2
validations:
required: true
Expand Down Expand Up @@ -72,6 +73,7 @@ body:
options:
- Dashboard UI (rendering, controls, rerun/stop)
- Captured data (network, console, screencast, trace)
- Element snapshot / locators (getSnapshot, getElements, accessibility tree)
- Backend / server (connection, websocket, crashes)
- Test launch / lifecycle (hooks, session, capabilities)
- Not sure
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ body:
options:
- Dashboard UI
- Capture / reporting core
- Element snapshot / locator API (@wdio/elements)
- WebdriverIO adapter
- Nightwatch adapter
- Selenium adapter
Expand Down
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

- [ ] `shared` (types and contracts)
- [ ] `core` (framework-agnostic capture/reporting)
- [ ] `elements` (published element/snapshot API — `@wdio/elements`)
- [ ] `service` (WebdriverIO adapter)
- [ ] `nightwatch-devtools` (Nightwatch adapter)
- [ ] `selenium-devtools` (Selenium adapter)
Expand Down
44 changes: 42 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
- v[0-9]+.[0-9]+.[0-9]+*
pull_request:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -23,9 +26,9 @@ jobs:
cache-name: cache-pnpm-modules
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-
${{ runner.os }}-build-${{ env.cache-name }}-
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
version: 10.26.1
Expand All @@ -36,5 +39,42 @@ jobs:
run: pnpm lint
- name: 📦 Bundle
run: pnpm -r --workspace-concurrency=1 build
# Four tsc programs — see the `typecheck` script. Runs before the tests
# because it is fast and its failures are cheaper to read.
- name: 🔎 Typecheck
run: pnpm typecheck
- name: 🧪 Run Tests
run: pnpm test

# Separate job because these are the only tests that need a real browser, and
# they parallelise with `build` instead of extending it. No bundle step: the
# specs and packages/app/src reach @wdio/devtools-shared through its source
# exports, so Vite compiles everything from source. Chrome comes preinstalled
# on the runner image; wdio fetches the matching driver itself.
component-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
- name: Cache pnpm modules
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
env:
cache-name: cache-pnpm-modules
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
version: 10.26.1
run_install: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Two workers, not the runner default: each carries a Vite server plus a
# Chrome, and past 2 the heaviest specs time out under that contention.
- name: 🧩 Component Tests
run: pnpm test:ui --maxInstances 2
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Scratch scripts and dumps dropped at the repo root. Real root-level config
# has to be un-ignored by name: a new one is otherwise silently never added,
# and CI then runs with different settings than everyone's checkout.
/*.ts
/*.json
!/vitest.config.ts
!/tsconfig.dts.json
*.tgz
examples/wdio/wdio-*.json
examples/wdio/wdio-*.webm
Expand Down Expand Up @@ -55,6 +60,5 @@ examples/**/allure-results*/
examples/**/allure-report*/
packages/**/allure-results*/
packages/**/allure-report*/
examples/playwright-allure/
examples/wdio/cucumber/wdio.allure.conf.ts

6 changes: 4 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ The execution environment is the browser, not Node, so this package cannot impor

### `packages/elements`

Element-detection scripts (`@wdio/elements`). Produces the per-action data that feeds the snapshot pipeline: `getSnapshot`, the depth-indented accessibility-tree serializer, and the interactable-element list, plus locator generation.
The WDIO-typed element-detection API (`@wdio/elements`): `getSnapshot`, `getElements`, the accessibility-tree and interactable-element readers. Unlike `shared` and `core` this package **is** published, so its exported names are public API.

Consumed by `core` (the element-snapshot builder) and by the adapters' per-action capture (`captureActionSnapshot`). The DOM-walking scripts run in the page via `browser.execute`, so — like `script` — they avoid Node-only APIs.
Imports from: `core`. The script bodies, serializers, types, and locator generation all live in `core` (`element-scripts.ts`, `element-snapshot.ts`, `element-types.ts`, `locators/`) because they're framework-agnostic; this package holds only the wrappers that take a `WebdriverIO.Browser` — which `core` can't type against. The arrow never reverses: `core` does not import `elements`, and the adapters' per-action capture (`captureActionSnapshot`) calls core's script bodies directly rather than going through this package. `elements/snapshot.ts` and `elements/locators/index.ts` are re-export shims kept for API stability.

The DOM-walking scripts run in the page via `browser.execute`, so — like `script` — they avoid Node-only APIs.

### `examples/`

Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ Documented divergences from the conventions above. They exist today as debt to b
- **WDIO + Selenium: verified end-to-end** (manual fail-then-pass runs: retained under `retain-on-first-failure`, dropped under `retain-on-failure`).
- **Nightwatch: `retain-on-failure` works; the other retry-aware policies degrade.** Its `--retries` re-runs the testcase *internally* without re-firing the plugin's per-test hooks, and the per-testcase results carry no attempt/retry field (retries live only in undocumented, version-varying Nightwatch internals — `suiteRetries.testRetriesCount` / `reporter.testResults.retryTest`), so the ledger sees only the final attempt for the `describe/it` and exports-object interfaces. Cucumber scenarios expose per-scenario hooks, so the feed captures their attempts. Not cleanly fixable without depending on those internals.
- WDIO `specFileRetries` spawns a fresh worker per retry, so cross-process attempts aren't in the (process-scoped) ledger.
- Run identity across worker sockets is env-propagated. `core/run-id.ts` `resolveRunId()` publishes `DEVTOOLS_RUN_ID` (`RUNNER_ENV.RUN_ID`) and every worker socket carries it as `?runId=` (`WORKER_WS_QUERY`), so the backend keeps accumulated run state when the *next spec's* worker connects and wipes it only for a genuinely new run. Without it every connect read as a new run: Preserve & Rerun 409'd for every spec except the last one that ran, and a dashboard opened mid-run replayed only the current spec. The WDIO service stamps it in the launcher's `onPrepare`, before workers fork, so all workers of one run agree; single-process adapters self-stamp on first use. **Gap: multi-process parallel runs in Selenium/Nightwatch** (jest/vitest workers, nightwatch `test_workers`) load the plugin per worker with no launcher-side hook to stamp first, so each worker generates its own id and still reads as a new run — the pre-fix behaviour, not a regression. Deriving the fallback from `process.ppid` would group those siblings, but would also make two sequential single-process runs share an id and inherit each other's state against a standalone dashboard, so the per-process fallback stands.
- **Chrome 150 headless drops trusted input once capture traffic is running** — a click dispatches but never navigates, so a live-mode example fails on its second test with a missing-element error that looks like a devtools bug. It is a browser regression (fixed in 151), not fixable client-side; `browser.reloadSession()` between tests works around it. Pin the example's `browserVersion` to 149 when reproducing live-mode behaviour on a 150 machine.
- Eager per-test trace slice (Nightwatch + Selenium) can drop an action snapshot whose fire-and-forget capture hasn't resolved by `afterEach` / scenario end — the slice is written from whatever snapshots exist at flush time. The WDIO service is immune because it awaits each snapshot inline before flushing.
- The per-test `screenshot` and `video` options live on the **WDIO `ServiceOptions` only** — not `BaseDevToolsOptions` — because only the service implements them (an option belongs on an adapter until a second adapter consumes it, mirroring the core-helper rule; putting them on the shared base made them appear available in Selenium/Nightwatch and broke those adapters' `Required<>` option types). The policy *types* (`TraceScreenshotPolicy`/`TraceVideoPolicy`) and the capture/slice/encode logic (`core/screenshot-artifact.ts`, `core/video-slice.ts`) are framework-agnostic, so Selenium/Nightwatch adoption was wiring-only — now done (Selenium adds the options on its own `DevToolsOptions` with full inline attach; Nightwatch adds them produce-only — see the Allure-attach entry below). All are gated to `traceGranularity:'test'` (per-test inline Allure); coarser granularities keep artifacts in the manifest. Video records the screencast continuously and slices per-test by wall-time — the session frame buffer is bounded by `maxBufferFrames` (default 2000; decimates keeping first/last), and on non-Chrome the polling recorder issues many `takeScreenshot`s that flood `@wdio/allure-reporter` (pair with `disableWebdriverStepsReporting`).
- The `filmstrip` option (dense screencast into the trace) is on **`BaseDevToolsOptions`** — the counterexample to the screenshot/video entry above — because all three adapters implement it (the "second consumer → base" rule realized). Core owns the work (`core/screencast-trace.ts` `thinScreencastFrames`/`buildDenseScreencast`; slice windowing in `spec-trace-helpers.ts`); adapters only default the option, un-gate the recorder in trace mode when it's set, and feed `recorder.frames` into the finalize context. Each adapter captures frames while the recorder is still alive (service `onReload` → `#filmstripFrames`; Selenium `onDriverEnd` drain before nulling; Nightwatch `#finalizeCurrentScreencast` snapshot before delegating), and each finalize context spreads `[...accumulated, ...(live recorder frames)]` so a **mid-run** per-spec/per-test slice flush (which fires before the recorder is drained) isn't blank. When dense frames are present they **supersede** the sparse per-action filmstrip (the per-action DOM `elements`/`snapshot` are carried independently by the `frame-snapshot` events, so no DOM data is lost); a run without dense frames keeps the sparse filmstrip, byte-stable with before. Thinning is applied at export; the live session frame buffer is bounded by `maxBufferFrames` (default 2000; see the screenshot/video entry above). Per-test filmstrip slicing follows the same per-test-hook availability as `traceGranularity:'test'` (works for WDIO mocha/cucumber, Selenium mocha, Nightwatch exports-object/cucumber; Nightwatch BDD `describe/it` degrades to session scope per the entry below), and non-Chrome polling carries the same reporter-noise caveat.
Expand All @@ -248,6 +250,7 @@ Documented divergences from the conventions above. They exist today as debt to b
- Nightwatch native asserts are buffered at call time and emitted in one batch at test-end (Nightwatch exposes no per-assertion execution/outcome hook reachable from the plugin — `client.queue.tree` / `client.reporter` aren't on `browser`). Streaming mid-run would flash every assert green before its outcome is known, so rows are held and reconciled against `results.testcases[*].assertions` at `afterEach` (the flat `results.assertions` only reflects the last testcase). The exporter re-sorts commands by timestamp (`buildActionEvents`) so the batched rows land at their real timeline positions rather than clustering at flush time. `actual` is parsed from the failure message (`but got: …`, failures only) into a collapsed `{passed, expected, actual}` result; `expected` is the assertion arg — the label still mirrors the call args, not the derived values.
- Nightwatch's BDD `describe/it` interface fires the plugin's global `beforeEach`/`afterEach` **once per module** (with an empty `currentTest.name`), not per `it` — Nightwatch runs the individual `it`s internally with no per-testcase hook or event reachable from the plugin (the lib emits only transport-level events). So `traceGranularity:'test'` records a single slice boundary (the first test) and collapses to one session-scoped slice; retry-aware/per-test retention (`tracePolicy`) degrades to session scope for this interface. Per-test slicing works for adapters/styles that expose per-test hooks (WDIO mocha/cucumber, Selenium mocha; likely Nightwatch's exports-object style, unverified). Session-granularity trace is unaffected. **No per-test support has been built for this interface** — real per-`it` slicing needs a hook Nightwatch doesn't surface; deferred to a full-picture pass, not attempted yet.
- **Empirically confirmed** (BDD example, `mode:'trace'` + `traceGranularity:'test'` + `tracePolicy:'retain-on-failure'` + `screenshot:'on'` + `video:'retain-on-failure'`): the one collapsed slice is keyed to the **first** test's uid (the artifacts manifest's `tests[]` still carries every testcase with correct `state`, so metadata capture is fine — it's the *slice/artifact* keying that collapses). Consequence: with a passing first test and a failing later test, the screenshot **produces** (policy `'on'` captures regardless of outcome, keyed to the first test) but the **trace zip and video are dropped** — `retain-on-failure` evaluates the first (passing) test's outcome, and the actually-failing test never gets its own slice to retain. The per-test produce-only path itself (screenshot/video write + manifest) is correct; only the BDD slice-keying limits it.
- **Nightwatch cucumber: traces now generate + capture asserts; DOM mutations still missing** (via the `test/` harness, 2026-07). Three fixes made the cucumber runner emit a useful trace: **(1) build** — `packages/nightwatch-devtools/tsup.config.ts` now compiles `src/helpers/cucumberHooks.cts` → `dist/helpers/cucumberHooks.cjs` as a self-contained CJS bundle (`@cucumber/cucumber` external). Previously the build ran only `tsup src/index.ts --clean`, so that file never existed and Cucumber's `require:[cucumberHooksPath]` registered *no* hooks (glob matched nothing) → zero capture. `PLUGIN_GLOBAL_KEY` moved to a leaf module `plugin-global-key.ts` so the hooks bundle stays tiny and CJS-safe (importing it via `constants.ts` dragged in core → `createRequire(import.meta.url)`, which throws when bundled to CJS). **(2) capture ordering** — a pre-quit cucumber `After` hook (`order:1000`, `captureCucumberScenarioBeforeQuit`) runs the trace capture + slice flush while the per-scenario browser session is live (the `order:-1` finalize is post browser-quit, so the flush bailed on the absent `sessionId`). Requires `traceGranularity:'test'` (per-scenario slices). **(3) native asserts** — the same pre-quit hook drains `browserProxy.drainNativeAssertCalls()` and calls `captureNativeAssertions` (the `afterEach` path early-returns for cucumber), so `assert.*` rows now appear. **Remaining gaps:** (a) assert **pass/fail isn't correlated** for cucumber — `captureNativeAssertions` is passed `currentTest: undefined` (cucumber has no `browser.currentTest.results`), so rows render neutral; cucumber needs a reconcile against its own scenario result. (b) DOM **`mutations` aren't captured** (`hasMutations:false`) — the page collector fails to (re)inject on the per-scenario re-navigated session (`ECONNRESET` / "collector not found" during teardown), and that noisy teardown-race is itself worth cleaning up. Console/network (BiDi) + commands + asserts + frames + sources + transcript are captured. BDD and live mode are unaffected.
- Service renders expect-webdriverio matchers as single `expect.<matcher>` rows by **folding**, not stack/depth suppression (the old `#assertionDepth`/`#matcherStarted`/self-heal machinery is gone). The matcher's value-read (`toHaveText`→`getText`, `toExist`→`isExisting`, …) is captured as a normal command; `afterAssertion` then coalesces the synthesized `expect.*` row into that read in place — inheriting its callSource, screenshot, and timeline position — and the fold replaces **by timestamp, never a public `id`**: `id` is the per-worker `commandCounter`, which resets per spec, so stamping one lets the app's id-first `replaceCommand` swap a same-id row from another spec (duplicate rows + a fold from another spec vanishing, in multi-spec live mode). `beforeAssertion` arms the pending matcher (depth-counted so aliases like `toBeChecked`→`toBeSelected` fold once); a matcher that **hard-throws** — element never resolves, so expect-webdriverio's `waitUntil` rethrows and `afterAssertion` never fires — is synthesized at `afterTest`/`afterStep` from the throwing read, so a failing assertion renders as `expect.<matcher>` whether or not the element existed. Two limits: its error is then the read's (`Can't call getText on … element wasn't found`), not an assertion-phrased message; and `MATCHER_READ_COMMANDS` is a hand-maintained allowlist, so a matcher whose read isn't listed leaves its raw read visible alongside the `expect.*` row. Plain-value jest matchers (`expect(x).toBe(y)`) don't fire the ewdio hooks, so they aren't captured as rows.

### File-size (raw line counts; soft cap is 500 logic lines)
Expand Down
15 changes: 15 additions & 0 deletions examples/nightwatch/cucumber/features/login.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: Example site smoke test

Mirrors the Nightwatch BDD smoke-test: open a site, wait for the body to be
visible, and assert the page title. Runs as a Cucumber scenario so the
cross-adapter harness has a nightwatch-cucumber trace fixture.

Scenario Outline: I can open a site and read its title
Given I navigate to "<url>"
When the page body becomes visible
Then the page title contains "<title>"

Examples:
| url | title |
| https://example.com | Example |
| https://example.org | Example |
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Step definitions for the example-site smoke scenario.
//
// Nightwatch's Cucumber integration injects the Nightwatch `browser` onto the
// World, so steps reach it via `this.browser` — hence regular (non-arrow)
// functions so `this` binds to the World.
const { Given, When, Then } = require('@cucumber/cucumber')

Given(/^I navigate to "([^"]*)"$/, async function (url) {
await this.browser.url(url)
})

When(/^the page body becomes visible$/, async function () {
await this.browser.waitForElementVisible('body', 5000)
})

Then(/^the page title contains "([^"]*)"$/, async function (title) {
await this.browser.assert.titleContains(title)
})
Loading
Loading