Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
4a0accc
feat(core,backend): emit console/stdout/stderr events into trace.zip …
vishnuv688 Jul 2, 2026
fcded20
feat(app): trace-player layout parity — full workbench with top-docke…
vishnuv688 Jul 2, 2026
2a3c14e
feat(app): reference-style timeline strip with inline playback controls
vishnuv688 Jul 3, 2026
fef6978
refactor(shared,backend,core): derive LogLevel from runtime LOG_LEVEL…
vishnuv688 Jul 3, 2026
dfa8856
fix(backend): skip tracing-group markers when reconstructing player c…
vishnuv688 Jul 3, 2026
d6d07b4
feat(shared,backend,app): nested action tree with failure rollup in t…
vishnuv688 Jul 3, 2026
f25173b
feat(shared,core): trace retention policy foundations
vishnuv688 Jul 7, 2026
66620c8
feat(core,backend): carry network response bodies through the trace
vishnuv688 Jul 7, 2026
372a870
feat(shared,core,backend,adapters): assertions as first-class trace a…
vishnuv688 Jul 7, 2026
5153fa8
refactor(core,adapters): consolidate trace-export orchestration into …
vishnuv688 Jul 7, 2026
c341226
feat(core,backend): carry command result through the trace
vishnuv688 Jul 8, 2026
fa0d41e
feat(core,adapters): wire tracePolicy so retain-on-failure gates trac…
vishnuv688 Jul 8, 2026
fdbdbc7
feat(core,service): surface expect/step failures on the triggering ac…
vishnuv688 Jul 8, 2026
244a13f
feat(app): Errors tab in the workbench with jump-to-source
vishnuv688 Jul 8, 2026
26c98bd
fix(service,nightwatch): distinct rerun-stable uids for Cucumber step…
vishnuv688 Jul 8, 2026
3cc47e3
fix(core,service): collapse same-timestamp snapshots so blank frames …
vishnuv688 Jul 8, 2026
5ea851f
chore: Added more actions to the action mapping to appear in the time…
vishnuv688 Jul 8, 2026
1ff07cc
chore(examples): split wdio example into cucumber/ and mocha/ with sh…
vishnuv688 Jul 8, 2026
3534224
chore: Update path in readme and update pnpm-lock.yaml
vishnuv688 Jul 8, 2026
174f9b5
fix(core): drop dependency-internal assertions from the trace
vishnuv688 Jul 8, 2026
cc4f595
fix(service): give Cucumber scenario-outline rows distinct trace groups
vishnuv688 Jul 8, 2026
d25d39f
fix(service): show waits as actions and suppress their internal polling
vishnuv688 Jul 8, 2026
6aeb37b
feat(core,adapters): capture per-test retry attempts for retry-aware …
vishnuv688 Jul 9, 2026
379122e
feat(service,core): render expect-webdriverio matchers as trace actions
vishnuv688 Jul 9, 2026
bca2d43
test(backend): remove skip-forever foreign-zip tests
vishnuv688 Jul 9, 2026
3b9f9ac
chore(examples): add WDIO retry harness for retry-aware trace policies
vishnuv688 Jul 9, 2026
ca6b786
docs: correct node:assert note; record retain-on-first-failure limita…
vishnuv688 Jul 9, 2026
78a9635
refactor(shared,adapters): consolidate adapter types into shared
vishnuv688 Jul 9, 2026
7d6578a
test(service): cover expect-webdriverio matcher capture
vishnuv688 Jul 9, 2026
2c117d5
feat(core): per-test trace granularity foundation
vishnuv688 Jul 9, 2026
647e396
feat(trace): per-test granularity, output folders, and test-results/ …
vishnuv688 Jul 10, 2026
9db3873
feat(service): capture expect-webdriverio matchers as assertion actions
vishnuv688 Jul 10, 2026
d552ab2
feat(nightwatch): capture native browser.assert/verify as assertion a…
vishnuv688 Jul 10, 2026
6e0377f
fix(core): rate-limit repeated terminal lines and fix failLastAction
vishnuv688 Jul 10, 2026
6541240
fix(app): real per-action duration and span-based active highlight
vishnuv688 Jul 10, 2026
8f5cb9f
fix(app): sidebar icon alignment and resizable-pane drag
vishnuv688 Jul 10, 2026
0b9609b
chore(examples): assertion + retry harnesses, chromedriver bump
vishnuv688 Jul 10, 2026
4a03f83
docs: test-results/ layout, traceGranularity:'test', tracePolicy, and…
vishnuv688 Jul 10, 2026
96f7807
fix(core): bound the pending-snapshot settle in trace finalize
vishnuv688 Jul 13, 2026
3a9493f
fix(core): order trace actions chronologically, not by insertion
vishnuv688 Jul 13, 2026
9fdbd6a
fix(core): capture strict-mode node:assert and label asserts from args
vishnuv688 Jul 13, 2026
a8f8cf4
fix(nightwatch): emit native asserts at test-end with real outcomes
vishnuv688 Jul 13, 2026
aa7b852
fix(service): keep expect matchers to a single row
vishnuv688 Jul 13, 2026
eef4d8f
fix(app): render assertions consistently across frameworks
vishnuv688 Jul 13, 2026
47637f7
fix(backend): escape test names in grep-style rerun filters
vishnuv688 Jul 13, 2026
527fa7c
fix(selenium): read snapshot state via unpatched driver; forward trac…
vishnuv688 Jul 13, 2026
5c46fee
chore(selenium): bump chromedriver to ^150 for Chrome 150
vishnuv688 Jul 13, 2026
609ff1d
chore(examples): live/trace config-ladder harnesses + retry scripts
vishnuv688 Jul 13, 2026
530018e
docs: record Nightwatch BDD per-test and WDIO assert-capture debt
vishnuv688 Jul 13, 2026
7fdc499
refactor: dedupe assert-result type into shared; drop unused trace de…
vishnuv688 Jul 13, 2026
4a80d00
refactor(core): one findFlushableRange for the three adapters' slice …
vishnuv688 Jul 13, 2026
c04020f
refactor(service): rebuild expect-matcher capture around before/after…
vishnuv688 Jul 13, 2026
2a75a4a
fix(service): exclude the devtools bundle from user-frame detection
vishnuv688 Jul 13, 2026
346ca3b
fix(nightwatch): record negated native asserts (assert.not.* / verify…
vishnuv688 Jul 14, 2026
0ce8e11
refactor(service): extract assertion capture into AssertionTracker
vishnuv688 Jul 14, 2026
d0f84a5
docs: sync WDIO assert-capture debt to the fold model; drop fixed ent…
vishnuv688 Jul 14, 2026
e6cd949
feat(core): per-attempt outcome ledger + group-by-test retention
vishnuv688 Jul 14, 2026
a06669f
fix(service): feed the retention ledger for correct retry-aware policies
vishnuv688 Jul 14, 2026
8a3d609
fix(core): fall back to metadata when a scoped retention ledger view …
vishnuv688 Jul 14, 2026
1c9b006
fix(core): infer the failed attempt from a retry in the outcome ledge…
vishnuv688 Jul 14, 2026
25754be
fix(nightwatch): feed the retention ledger for correct retry-aware po…
vishnuv688 Jul 14, 2026
2ac7f25
fix(selenium): feed the retention ledger for correct retry-aware poli…
vishnuv688 Jul 14, 2026
440014d
docs: retry-aware retention scope + assert-capture debt sync
vishnuv688 Jul 14, 2026
6794687
feat(core): artifacts manifest + WDIO Allure trace/video attach
vishnuv688 Jul 14, 2026
82cff0c
feat: per-test screenshot + video options with inline Allure attach
vishnuv688 Jul 15, 2026
cd75b72
fix(core): replace polynomial-ReDoS edge-trims with a linear trimChar
vishnuv688 Jul 15, 2026
92aff89
fix: scope screenshot/video options to the WDIO ServiceOptions
vishnuv688 Jul 15, 2026
c838078
fix(app): de-duplicate live-mode Errors tab (command + reworded test …
vishnuv688 Jul 15, 2026
f57ab54
feat: dense screencast filmstrip in trace mode, and fix per-test slicing
vishnuv688 Jul 15, 2026
b21adf8
fix(core): window command-less trace slices; drop dead SpecRange.snap…
vishnuv688 Jul 15, 2026
37ddf45
fix(core): drop the sparse per-action filmstrip when a dense one is p…
vishnuv688 Jul 15, 2026
b275b39
fix(core): cap the screencast frame buffer to bound memory
vishnuv688 Jul 15, 2026
cd32ba1
fix(core): render node:assert failures as clean Expected/Received
vishnuv688 Jul 16, 2026
712faf7
feat: cross-adapter Allure artifact attachment via pluggable sink
vishnuv688 Jul 16, 2026
602a62e
docs: record Nightwatch BDD per-test artifact limitation as known debt
vishnuv688 Jul 16, 2026
c29607a
docs: record Nightwatch BDD per-test limitation + Selenium seam extra…
vishnuv688 Jul 17, 2026
fa5de7b
feat: DOM time-travel in the trace player (mutations + field state)
vishnuv688 Jul 17, 2026
ada2adf
fix(service): re-capture DOM across reloadSession and before navigation
vishnuv688 Jul 17, 2026
69dd363
fix(core): only capture asserts fired directly by user code
vishnuv688 Jul 17, 2026
8c47aac
feat(app): trace player Transcript tab + Copy-for-LLM
vishnuv688 Jul 17, 2026
a063f4c
feat(app): trace player A11y tab + element overlay (pick-locator)
vishnuv688 Jul 19, 2026
ef0bc69
polish(app): float the transcript copy control as an in-corner icon
vishnuv688 Jul 19, 2026
2d503bc
feat(app): bidirectional element-overlay ↔ A11y-tree linking
vishnuv688 Jul 20, 2026
1f4064d
refactor(shared): single-source the a11y snapshot-text format tokens
vishnuv688 Jul 20, 2026
abc7548
refactor(selenium): extract per-test artifact emit into test-artifact…
vishnuv688 Jul 20, 2026
e9a2cb7
feat(trace): input markers + Cucumber step nesting
vishnuv688 Jul 20, 2026
33e590c
fix(trace): drop replayed comment nodes + settle layout before overla…
vishnuv688 Jul 20, 2026
c46be98
fix(trace): correct replay fidelity — comments, overlay alignment, zo…
vishnuv688 Jul 20, 2026
4a651ec
fix(service): Gate the devtools-artifacts manifest behind an emitArti…
vishnuv688 Jul 20, 2026
39ec0a6
test(service): add addCommand to browser mocks for main's getSnapshot…
vishnuv688 Jul 20, 2026
c1a27c7
fix: green the branch after integrating main
vishnuv688 Jul 20, 2026
47f9582
Merge branch 'main' into vv/trace-mode-parity
vishnuv688 Jul 20, 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
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,22 @@ 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

# Local Allure exploration / trace-parity comparison (throwaway — not committed)
examples/**/allure-results*/
examples/**/allure-report*/
packages/**/allure-results*/
packages/**/allure-report*/
examples/playwright-allure/
examples/wdio/cucumber/wdio.allure.conf.ts

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
21 changes: 18 additions & 3 deletions CLAUDE.md

Large diffs are not rendered by default.

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
8 changes: 7 additions & 1 deletion eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ const security = require('eslint-plugin-security')

module.exports = [
{
ignores: ['node_modules/**', '**/dist/**', '**/.tsup/**']
ignores: [
'node_modules/**',
'**/dist/**',
'**/.tsup/**',
'**/allure-report/**',
'**/allure-results/**'
]
},
// Base JS config
{
Expand Down
24 changes: 16 additions & 8 deletions examples/nightwatch/nightwatch.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,24 @@ module.exports = {
},
'goog:loggingPrefs': { performance: 'ALL' }
},
// Simple configuration - just call the function to get globals.
// - screencast: polling-mode .webm written to cwd as
// nightwatch-video-<sessionId>.webm.
// - bidi: opt-in WebDriver BiDi capture for console + network. When
// attached, the per-command Chrome perf-log network path is gated
// off to avoid duplicate entries.
// bidi: opt-in WebDriver BiDi capture for console + network. When
// attached, the per-command Chrome perf-log network path is gated off to
// avoid duplicate entries.
globals: nightwatchDevtools({
port: 3000,
mode: 'live',
screencast: { enabled: true, pollIntervalMs: 200 },
// ── Config ladder — change ONLY this block per rung ───────────────
// 1 live: mode: 'live'
// 2 trace: mode: 'trace'
// 3 per-test: mode: 'trace', traceGranularity: 'test'
// 4 fail: mode: 'trace', traceGranularity: 'test', tracePolicy: 'retain-on-failure'
// 5 retry: mode: 'trace', traceGranularity: 'test', tracePolicy: 'on-first-retry'
// (rung 5 needs retries → run `pnpm demo:nightwatch:retry`)
// NOTE: the BDD describe/it interface fires the plugin's beforeEach once
// per module (no per-`it` hook), so traceGranularity:'test' collapses to
// a single session-scoped slice here. See CLAUDE.md § Known debt.
mode: 'trace',
traceGranularity: 'session',
tracePolicy: 'retain-on-first-failure',
bidi: true
})
}
Expand Down
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.

48 changes: 48 additions & 0 deletions examples/nightwatch/tests/smoke-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Example + config-sweep harness for @wdio/nightwatch-devtools.
*
* Walk the live/trace ladder by editing ONLY the mode/traceGranularity/
* tracePolicy block in ../nightwatch.conf.cjs. The suite carries a passing
* pair, an always-failing test (retain-on-failure target), and a flaky
* fail-then-pass test (on-first-retry / attempt-capture target).
*
* Native asserts (browser.assert.*) double as the assertion-capture check:
* the passing ones must render green ✓, the failing one red ✗.
*
* Run from repo root:
* pnpm demo:nightwatch (rungs 1-4)
* pnpm demo:nightwatch:retry (rung 5 — adds --retries 1)
*/

// Survives Nightwatch's testcase retry so the flaky test fails once, then passes.
let flakyAttempts = 0

describe('nightwatch-devtools smoke test', function () {
it('loads example.com and reads the heading', async function (browser) {
await browser.url('https://example.com')
await browser.waitForElementVisible('body', 5000)
browser.assert.titleContains('Example')
})

it('navigates and reads the page title', async function (browser) {
await browser.url('https://example.org')
await browser.waitForElementVisible('body', 5000)
browser.assert.titleContains('Example')
})

it('fails on a wrong title (retain-on-failure target)', async function (browser) {
await browser.url('https://example.com')
await browser.waitForElementVisible('body', 5000)
browser.assert.titleContains('This Is Not The Title')
})

it('flaky: fails the first attempt, then passes (retry target)', async function (browser) {
await browser.url('https://example.com')
await browser.waitForElementVisible('body', 5000)
flakyAttempts += 1
if (flakyAttempts === 1) {
throw new Error('intentional first-attempt failure — should retry')
}
browser.assert.titleContains('Example')
})
})
40 changes: 38 additions & 2 deletions examples/selenium/mocha-test/test/example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/**
* Smoke test for @wdio/selenium-devtools.
* Example + config-sweep harness for @wdio/selenium-devtools (Mocha runner).
*
* Walk the live/trace ladder by editing ONLY the DevTools.configure({...})
* block below. The suite carries a passing pair, an always-failing test (for
* retain-on-failure), and a flaky fail-then-pass test with a per-test retry
* (for on-first-retry / attempt capture).
*
* Run from the package root: pnpm example:mocha
*/
Expand All @@ -8,11 +13,22 @@ import { strict as assert } from 'node:assert'
import { Builder, By, until } from 'selenium-webdriver'
import { DevTools } from '@wdio/selenium-devtools'

// ── Config ladder — change this block per rung ──────────────────────────────
// 1 live: { mode: 'live' }
// 2 trace: { mode: 'trace' }
// 3 per-test:{ mode: 'trace', traceGranularity: 'test' }
// 4 fail: { mode: 'trace', traceGranularity: 'test', tracePolicy: 'retain-on-failure' }
// 5 retry: { mode: 'trace', traceGranularity: 'test', tracePolicy: 'on-first-retry' }
DevTools.configure({
screencast: { enabled: true, quality: 70, maxWidth: 1280, maxHeight: 720 },
mode: 'trace',
traceGranularity: 'session',
tracePolicy: 'retain-on-first-failure',
headless: true
})

// Survives Mocha's in-process retry so the flaky test fails once, then passes.
let flakyAttempts = 0

describe('selenium-devtools smoke test', function () {
let driver

Expand Down Expand Up @@ -40,4 +56,24 @@ describe('selenium-devtools smoke test', function () {
const title = await driver.getTitle()
assert.match(title, /Example/i)
})

it('fails on a wrong heading (retain-on-failure target)', async function () {
await driver.get('https://example.com')
await driver.sleep(1000)
const heading = await driver.wait(until.elementLocated(By.css('h1')), 10000)
const text = await heading.getText()
assert.equal(text, 'This Is Not The Heading')
})

it('flaky: fails the first attempt, then passes (retry target)', async function () {
this.retries(1)
await driver.get('https://example.com')
await driver.sleep(1000)
flakyAttempts += 1
if (flakyAttempts === 1) {
throw new Error('intentional first-attempt failure — should retry')
}
const title = await driver.getTitle()
assert.match(title, /Example/i)
})
})
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
@@ -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)
})
Loading
Loading