Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d27aa95
feat(core,backend): emit console/stdout/stderr events into trace.zip …
vishnuv688 Jul 2, 2026
9eb326c
feat(app): trace-player layout parity — full workbench with top-docke…
vishnuv688 Jul 2, 2026
d4df0ac
feat(app): reference-style timeline strip with inline playback controls
vishnuv688 Jul 3, 2026
56af656
refactor(shared,backend,core): derive LogLevel from runtime LOG_LEVEL…
vishnuv688 Jul 3, 2026
fe19038
fix(backend): skip tracing-group markers when reconstructing player c…
vishnuv688 Jul 3, 2026
c7f94b4
feat(shared,backend,app): nested action tree with failure rollup in t…
vishnuv688 Jul 3, 2026
e5ba562
feat(shared,core): trace retention policy foundations
vishnuv688 Jul 7, 2026
adc167b
feat(core,backend): carry network response bodies through the trace
vishnuv688 Jul 7, 2026
53aebcf
feat(shared,core,backend,adapters): assertions as first-class trace a…
vishnuv688 Jul 7, 2026
a93fe17
refactor(core,adapters): consolidate trace-export orchestration into …
vishnuv688 Jul 7, 2026
c634da2
feat(core,backend): carry command result through the trace
vishnuv688 Jul 8, 2026
f81f8c4
feat(core,adapters): wire tracePolicy so retain-on-failure gates trac…
vishnuv688 Jul 8, 2026
e76b8c1
feat(core,service): surface expect/step failures on the triggering ac…
vishnuv688 Jul 8, 2026
f35b59c
feat(app): Errors tab in the workbench with jump-to-source
vishnuv688 Jul 8, 2026
dd4bc5b
fix(service,nightwatch): distinct rerun-stable uids for Cucumber step…
vishnuv688 Jul 8, 2026
3da30fa
fix(core,service): collapse same-timestamp snapshots so blank frames …
vishnuv688 Jul 8, 2026
2a26451
chore: Added more actions to the action mapping to appear in the time…
vishnuv688 Jul 8, 2026
9fe36d0
chore(examples): split wdio example into cucumber/ and mocha/ with sh…
vishnuv688 Jul 8, 2026
62d3a32
chore: Update path in readme and update pnpm-lock.yaml
vishnuv688 Jul 8, 2026
fabd782
fix(core): drop dependency-internal assertions from the trace
vishnuv688 Jul 8, 2026
c7f3ca4
fix(service): give Cucumber scenario-outline rows distinct trace groups
vishnuv688 Jul 8, 2026
8d1f98a
fix(service): show waits as actions and suppress their internal polling
vishnuv688 Jul 8, 2026
40d9208
feat(core,adapters): capture per-test retry attempts for retry-aware …
vishnuv688 Jul 9, 2026
ce5c673
feat(service,core): render expect-webdriverio matchers as trace actions
vishnuv688 Jul 9, 2026
7c87033
test(backend): remove skip-forever foreign-zip tests
vishnuv688 Jul 9, 2026
d6bcd38
chore(examples): add WDIO retry harness for retry-aware trace policies
vishnuv688 Jul 9, 2026
015fb75
docs: correct node:assert note; record retain-on-first-failure limita…
vishnuv688 Jul 9, 2026
56e92ff
refactor(shared,adapters): consolidate adapter types into shared
vishnuv688 Jul 9, 2026
3e5203b
test(service): cover expect-webdriverio matcher capture
vishnuv688 Jul 9, 2026
4d1c569
feat(core): per-test trace granularity foundation
vishnuv688 Jul 9, 2026
2626631
feat(trace): per-test granularity, output folders, and test-results/ …
vishnuv688 Jul 10, 2026
41db0aa
feat(service): capture expect-webdriverio matchers as assertion actions
vishnuv688 Jul 10, 2026
6b068e2
feat(nightwatch): capture native browser.assert/verify as assertion a…
vishnuv688 Jul 10, 2026
084bdb9
fix(core): rate-limit repeated terminal lines and fix failLastAction
vishnuv688 Jul 10, 2026
7009296
fix(app): real per-action duration and span-based active highlight
vishnuv688 Jul 10, 2026
f2a4534
fix(app): sidebar icon alignment and resizable-pane drag
vishnuv688 Jul 10, 2026
3a8092b
chore(examples): assertion + retry harnesses, chromedriver bump
vishnuv688 Jul 10, 2026
19d046a
docs: test-results/ layout, traceGranularity:'test', tracePolicy, and…
vishnuv688 Jul 10, 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ packages/nightwatch-devtools/nightwatch-video-*.webm
trace-*.zip
examples/**/trace-*/

# test results
examples/**/test-results/

# vitest --coverage output
coverage/

# pnpm state, cache, logs, and debug files
/packages/**/*.mjs

.claude
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ The execution environment is the browser, not Node, so this package cannot impor

Per-framework demo projects used for manual verification.

- `examples/wdio/` — WebdriverIO with Mocha (default). Run via `pnpm demo:wdio`.
- `examples/wdio/` — WebdriverIO, split into `cucumber/` and `mocha/` (shared page objects in `pageobjects/`). Run via `pnpm demo:wdio` (Cucumber) or `pnpm demo:wdio:mocha`.
- `examples/nightwatch/` — Nightwatch (both vanilla and Cucumber). Run via `pnpm demo:nightwatch`.
- `examples/selenium/` — Selenium with subdirs for `mocha-test/`, `jest-test/`, `cucumber-test/`, `jasmine-test/`, `vitest-test/`. `pnpm demo:selenium` runs mocha; `pnpm --filter @wdio/selenium-devtools example:<runner>` runs the others.

Expand Down
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,19 @@ Documented divergences from the conventions above. They exist today as debt to b
### Architecture

- `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` is wired only in `selenium-devtools`. The shared helper lives in `core/assert-patcher`; Service and Nightwatch can opt in via a one-line call when ready. Not auto-enabled — both communities lean on chai/expect.
- `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 additionally taps expect-webdriverio's `afterAssertion` hook so passing+failing matchers render as `expect.*` actions; Selenium (chai/jest) and Nightwatch (`browser.assert`/`verify`) don't yet surface passing framework-matcher assertions as trace actions.
- BiDi is auto-attached in Service and Selenium; Nightwatch is opt-in via `bidi: true` and requires `webSocketUrl: true` in capabilities.
- Retry-aware trace policies aren't uniform across adapters. Selenium emits one metadata entry per attempt (counter-based `generateStableUid` uid + retry-stable tracker key), so all six policies — including `retain-on-first-failure` (needs the first attempt's outcome) — evaluate correctly. Service and Nightwatch key metadata on the retry-stable uid and overwrite/mutate per attempt, collapsing to the *final* attempt's outcome; the retry-count policies (`retain-on-failure`, `on-first-retry`, `on-all-retries`, `retain-on-failure-and-retries`) are still correct, but `retain-on-first-failure` can't see a failed-then-passed first attempt and under-retains. Proper fix rides with B5 (per-test granularity preserves per-attempt slices); until then all three set `attemptInfoAvailable: true`.
- Nightwatch negated native asserts (`browser.assert.not.*` / `verify.not.*`) produce no trace row: the `not` namespace is a nested Proxy that `wrapAssertionNamespaces` passes through untouched, and the internal command it issues is dropped by the `hasUserSource` gate in `browserProxy`. Positive `assert.*`/`verify.*` still record.
- 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.
- Nightwatch native asserts populate all at once in live mode and finalize pass/fail in one test-end batch: Nightwatch exposes no per-assertion execution/outcome hook reachable from the plugin (`client.queue.tree` / `client.reporter` aren't on `browser`), so call-time capture streams neutral pending rows and `afterEach` reconciles outcomes. Trace-timeline positions are corrected from `results.commands`.
- Service assertion-suppression self-heal is command-triggered: a swallowed soft-assert (`expect.soft` / user `try/catch`) whose matcher hard-throws leaves `#assertionDepth` stuck until the next top-level user command's stack shows no `expect-webdriverio` frame (heal) or the next `beforeAssertion` (reset). A stuck depth with no following command or assertion before test end persists until the next test's `resetStack()` — benign (no later command in that test to suppress).

### File-size (raw line counts; soft cap is 500 logic lines)

None of the entries below trigger the `max-lines` lint rule after `skipBlankLines`/`skipComments`. They're documented because their raw line count is over 500, and the next substantive change to any of them should still look for an extraction opportunity.
Most entries below don't trigger the `max-lines` lint rule after `skipBlankLines`/`skipComments`; they're documented because their raw line count is over 500, and the next substantive change to any of them should still look for an extraction opportunity. The service plugin is the exception — it's now over the *logic*-line cap.

- `packages/service/src/index.ts` — over the 500-**logic**-line soft cap (the WDIO plugin god-file: session/screencast/command/assertion/trace-slice wiring in one class). Split candidate; the trace-slice, assertion-capture, and screencast concerns are the natural extraction seams.

- `packages/nightwatch-devtools/src/index.ts` (~536 raw). Cucumber/test/run-lifecycle, session-init, event-hub modules already extracted; remainder is the `PluginInternals` accessor bag plus per-method delegators plus the factory. Accept-as-is.
- `packages/selenium-devtools/src/index.ts` (~560 raw). Session/test-lifecycle extracted; remainder is the `PluginInternals` accessor bag plus onCommand/onDriverCreated wiring. Same situation as nightwatch.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ When BiDi is active in Selenium or Nightwatch, the per-command Chrome performanc

### 📦 Trace mode (trace.zip)

Headless capture path — no DevTools UI window opens. At session end the adapter writes a trace artifact next to the user's output directory, suitable for offline replay, AI-agent diffing, or any consumer that prefers a portable artifact over a live UI.
Headless capture path — no DevTools UI window opens. At session end the adapter writes trace artifacts into a `test-results/` folder (created next to the resolved spec/config directory), suitable for offline replay, AI-agent diffing, or any consumer that prefers a portable artifact over a live UI.

| Adapter | How to enable |
|---|---|
Expand All @@ -79,7 +79,7 @@ Headless capture path — no DevTools UI window opens. At session end the adapte
| **Nightwatch** | `globals: nightwatchDevtools({ mode: 'trace' })` |

The trace artifact contains:
- `trace.trace` — NDJSON `context-options` + `before`/`after` action events. When test hooks are available (Mocha's `it()` / Cucumber's `Scenario()`), each test becomes a [`Tracing.tracingGroup`](https://github.com/VibiumDev/vibium/blob/main/docs/explanation/recording-format.md#action-groups-user-defined) span — an open/close `before`/`after` pair with `method: "tracingGroup"` and `params.name` set to the test title. Child actions inside the group carry `parentId` pointing back to the group's `callId`, so timeline viewers render tests as labelled spans wrapping their commands.
- `trace.trace` — NDJSON `context-options` + `before`/`after` action events. When test hooks are available (Mocha's `it()` / Cucumber's `Scenario()`), each test becomes a `Tracing.tracingGroup` span — an open/close `before`/`after` pair with `method: "tracingGroup"` and `params.name` set to the test title. Child actions inside the group carry `parentId` pointing back to the group's `callId`, so timeline viewers render tests as labelled spans wrapping their commands.
- `trace.network` — HAR-style network entries derived from the existing capture
- `resources/page@<id>-<ts>.jpeg` — screenshot per user-facing action
- `resources/elements-page@<id>-<ts>.json` — flat interactable element list extracted by the page-injected scripts in `@wdio/devtools-core/element-scripts`
Expand All @@ -103,15 +103,17 @@ npx show-trace path/to/trace.zip # in a project that installs an adapter

The `show-trace` bin is exposed by each adapter (`@wdio/devtools-service`, `@wdio/nightwatch-devtools`, `@wdio/selenium-devtools`), so `pnpm show-trace <zip>` / `npx show-trace <zip>` work in any project that installs one — no extra dependency.

**Other viewers.** Because the format is unchanged, the same `.zip` also drops into [player.vibium.dev](https://player.vibium.dev) or `npx playwright show-trace <path>`. The format follows the [Vibium recording format](https://github.com/VibiumDev/vibium/blob/main/docs/explanation/recording-format.md) spec — a Playwright-compatible NDJSON schema that the ecosystem already renders. This is the same format [`@wdio/mcp`](https://webdriver.io/docs/mcp) uses for AI-driven session recording.
**Other viewers.** The trace uses a portable NDJSON schema, so the same `.zip` also opens in other compatible trace viewers that read the format. This is the same format [`@wdio/mcp`](https://webdriver.io/docs/mcp) uses for AI-driven session recording.

#### Options

| Option | Values | Default | Description |
|--------|--------|---------|-------------|
| `mode` | `'live'` \| `'trace'` | `'live'` | `'live'` launches the DevTools UI; `'trace'` writes an offline artifact. |
| `traceFormat` | `'zip'` \| `'ndjson-directory'` | `'zip'` | Output layout. `'zip'` writes a single archive; `'ndjson-directory'` unpacks into `trace-<id>/`. |
| `traceGranularity` | `'session'` \| `'spec'` | `'session'` | `'session'` writes one trace per worker; `'spec'` writes one trace per spec file — smaller artifacts, easier to navigate. |
| `traceGranularity` | `'session'` \| `'spec'` \| `'test'` | `'session'` | `'session'` writes one trace per worker; `'spec'` one per spec file; `'test'` one per test into its own `<spec>-<title>-<browser>[-retryN]/trace.zip` folder — the smallest, most navigable artifacts, and the best pairing for a retention policy. |
| `tracePolicy` | `'on'` \| `'retain-on-failure'` \| `'retain-on-first-failure'` \| `'on-first-retry'` \| `'on-all-retries'` \| `'retain-on-failure-and-retries'` | `'on'` | Which traces to keep. `'on'` keeps every trace; the rest keep only failing/retried tests — pairs well with `traceGranularity: 'test'`. |
| `captureAssertions` | `boolean` | `true` | Capture assertions as action rows: `node:assert` (all adapters), WebdriverIO `expect(...)` matchers, and Nightwatch `browser.assert`/`verify`. Set `false` to opt out. |

WDIO config example:

Expand All @@ -129,7 +131,7 @@ services: [[DevToolsHookService, {

Adapters detect mobile sessions via `platformName: 'android' | 'ios'` (case-insensitive) and adjust the per-action snapshot to extract elements from the mobile XML tree instead of the DOM. The trace's `context-options` records `title: 'android' — <deviceName>` / `'ios' — <deviceName>` so the viewer labels frames correctly.

A reference WDIO config is at [examples/wdio/wdio.mobile.conf.ts](examples/wdio/wdio.mobile.conf.ts). Prereqs to run it end-to-end with a local emulator:
A reference WDIO config is at [examples/wdio/cucumber/wdio.mobile.conf.ts](examples/wdio/cucumber/wdio.mobile.conf.ts). Prereqs to run it end-to-end with a local emulator:

1. **Java JDK** — `brew install --cask temurin`
2. **Android SDK** — `brew install --cask android-commandlinetools` then `yes | sdkmanager --licenses && sdkmanager "platform-tools" "emulator" "system-images;android-34;google_apis_playstore;arm64-v8a"`. The brew cask installs sdkmanager under `/opt/homebrew/share/android-commandlinetools/`, and sdkmanager downloads other SDK pieces alongside it — set `ANDROID_HOME` to that path (not `~/Library/Android/sdk/`).
Expand Down
18 changes: 18 additions & 0 deletions examples/nightwatch/tests/assert-capture-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Verification harness for native-assert trace capture (browser.assert / verify).
// Run `pnpm demo:nightwatch` and inspect the dashboard Actions: the PASSING
// asserts must render green and the FAILING ones RED. If a failing assert shows
// green, the classic-chained capture is mis-reporting failures (the known risk
// in nativeAssertCapture — the wrapper sees the enqueue, not the queued result).
describe('Native assert capture check', function () {
it('renders passing and failing native asserts', async function (browser) {
await browser.url('https://example.com').waitForElementVisible('body', 5000)

// Soft verify.* first — never aborts the test, so all four always run.
browser.verify.titleContains('Example') // PASS → expect green
browser.verify.titleContains('SOFT_FAIL_ME') // FAIL → expect RED

// Hard assert.* — the classic-chained/queued path under test.
browser.assert.titleContains('Example') // PASS → expect green
browser.assert.titleContains('HARD_FAIL_ME') // FAIL → expect RED
})
})
47 changes: 0 additions & 47 deletions examples/nightwatch/tests/login.js

This file was deleted.

21 changes: 0 additions & 21 deletions examples/nightwatch/tests/sample.js

This file was deleted.

9 changes: 9 additions & 0 deletions examples/wdio/cucumber/features/login-fail.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature: Retention check — deliberately failing login

# Logs in with bad credentials but asserts the SUCCESS message, so the Then
# step fails. Used to verify retain-on-failure keeps this spec's trace while
# dropping the passing login.feature. Reuses the existing step definitions.
Scenario: Failing assertion exercises retain-on-failure
Given I am on the login page
When I login with foobar and barfoo
Then I should see a flash message saying You logged into a secure area!
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ Feature: The Internet Guinea Pig Website

Examples:
| username | password | message |
| tomsmith | SuperSecretPassword! | You logged into a secure area! |
| tomsmith | SuperSecretPassword1! | You logged into a secure area! |
| foobar | barfoo | Your username is invalid! |
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Given, When, Then, After } from '@wdio/cucumber-framework'
import { browser, expect } from '@wdio/globals'

import LoginPage from '../pageobjects/login.page.js'
import SecurePage from '../pageobjects/secure.page.js'
import LoginPage from '../../../pageobjects/login.page.js'
import SecurePage from '../../../pageobjects/secure.page.js'

const pages = {
login: LoginPage
Expand All @@ -26,9 +26,9 @@ When(/^I login with (\w+) and (.+)$/, async (username, password) => {

Then(/^I should see a flash message saying (.*)$/, async (message) => {
console.log(`[TEST] Verifying flash message: ${message}`)
const el = await SecurePage.flashAlert
await expect(el).toBeExisting()
await expect(el).toHaveText(expect.stringContaining(message))
await expect(SecurePage.flashAlert).toBeExisting()
await expect(SecurePage.flashAlert).toHaveText(
expect.stringContaining(message)
)
console.log('[TEST] Flash message verified successfully')
// await browser.pause(15000)
})
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export const config: Options.Testrunner = {
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
//
maxInstances: 10,
// Live mode drives a single-session dashboard; >1 worker streams two feature
// files into it at once and neither renders cleanly. One instance = readable demo.
maxInstances: 1,
//
// If you have trouble getting all important capabilities together, check out the
// Sauce Labs platform configurator - a great tool to configure your capabilities:
Expand Down Expand Up @@ -87,7 +89,7 @@ export const config: Options.Testrunner = {
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'debug',
logLevel: 'warn',
//
// Set specific log levels per logger
// loggers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const config: Options.Testrunner = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any,

logLevel: 'info',
logLevel: 'warn',
bail: 0,
baseUrl: 'http://localhost',
waitforTimeout: 15000,
Expand Down
Loading
Loading