Skip to content

Commit 23d8bf2

Browse files
authored
Merge pull request #515 from webdriverio/test/510-trace-format-fixtures
Test the trace reader against real zips from an older writer
2 parents 5d4b9a4 + 857f076 commit 23d8bf2

13 files changed

Lines changed: 655 additions & 0 deletions

‎CLAUDE.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ The venv's editable install of `selenium_devtools` points at the primary checkou
212212

213213
The Python adapter cannot import `core`, so some pure logic exists twice. `packages/shared/mirror-vectors.json` holds input/expected-output cases for it, one section per mirrored function; `packages/selenium-devtools/tests/mirror-vectors.test.ts` runs every section against the TypeScript side under `pnpm test`, and `packages/selenium-devtools-py/tests/test_mirror_vectors.py` runs the same file against the Python side in the Python CI job (whose path filter includes the file). Both runners refuse a section they do not handle. Covered today: assertion target resolution, native-session detection, run id, output directory, command row id range, wait poll keys, nested-wait identity, wait row naming (whose negated case the TypeScript runner skips, selenium-webdriver having no negated wait) and performance payload shaping; `notCovered` in the file says why screencast thinning is not. Add a case whenever a change touches either side of a mirrored function, and a section when new logic is mirrored, provided its output is deterministic without a driver. A deliberate difference between the two goes in the section's `notes`, not around the case.
214214

215+
### Trace zips from older writers
216+
217+
The hosted player runs the newest reader against zips any adapter version wrote. `packages/trace/tests/fixtures/trace-formats/` holds real zips from older writers, one per adapter, reduced by `reduce-fixture.ts` beside them (images become 1x1 images of the same format; every other entry keeps its bytes), each with a manifest counted from its raw entries; `trace-format-fixtures.test.ts` checks the current reader still yields what each one held. A release that changes what the writer puts in a zip adds a fixture for it, as the README there describes, and older ones stay.
218+
215219
### Skipping tests that depend on workspace-internal build artifacts
216220

217221
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.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Trace format fixtures
2+
3+
The hosted player always runs the newest reader, while the zips people open were written by whatever adapter version recorded them. Each zip here is a real trace from an older writer, and `../../trace-format-fixtures.test.ts` checks that the current `parseTraceZip` still opens it with its commands, console output, network requests, DOM mutations, screencast frames, sources and metadata intact. A reader change that breaks an older trace fails `pnpm test`.
4+
5+
## What is here
6+
7+
| Fixture | Written by | Why it is kept |
8+
|---|---|---|
9+
| `wdio-mocha-2026-10-28-14977b93` | WDIO service, mocha example | Written before #508, so every frame-snapshot inlines its screenshot as a base64 data URI; PNG screenshots sit under `.jpeg` names |
10+
| `selenium-mocha-2026-10-28-14977b93` | Selenium adapter, mocha example | Same format; a failed wait row, a failed `node:assert` row, and JPEG screencast frames beside PNG ones |
11+
| `nightwatch-bdd-2026-10-28-14977b93` | Nightwatch adapter, BDD example | Same format; `waitForElementVisible` rows and a failed native assert row |
12+
| `python-pytest-2026-10-28-14977b93` | Python adapter, pytest example | Same format; three browser sessions in one zip |
13+
14+
All four come from one `examples-smoke` CI artifact (run 40774299701 at `14977b93`, 2026-10-28). The two earlier smoke artifacts (2026-10-27) were inspected and wrote the same format, so they add nothing a reader could break separately.
15+
16+
Each `<name>.json` is the fixture's manifest: counts and key fields taken from the zip's raw entries when it was reduced, not through the reader, so a reader bug cannot write itself into the expectation.
17+
18+
## How a fixture is reduced
19+
20+
Real zips are mostly screenshots. `reduce-fixture.ts` keeps every entry, in the same order and under the same name, and changes only images:
21+
22+
- Each image resource (detected by its bytes, not its name) becomes a 1x1 image of the same format carrying its own resource name as a comment (a JPEG `COM` segment or a PNG `tEXt` chunk). The test reads that name back from each frame the reader returns, so a frame paired with the wrong resource still fails.
23+
- In `.trace` streams, each `data:image/...;base64,` payload is replaced by a 1x1 image of the same format. The rest of each line, the data URI prefix included, keeps its bytes.
24+
- Every other entry keeps its bytes. The zip is rewritten with yazl, as the writer does, with a fixed entry time so reducing the same source twice gives the same file.
25+
26+
This does not weaken what is tested: the reader never decodes an image. It base64-encodes resource bytes into frames and never reads a frame-snapshot's HTML, so what it can get wrong (which entries it finds, how it pairs frames with commands, how it parses every stream) is all exercised on the original structure.
27+
28+
## Adding a fixture
29+
30+
When a release changes what the writer puts in a zip, add a trace from that release for each adapter whose output changed:
31+
32+
1. Take a short run's zip, ideally from a green `examples-smoke` CI artifact (`gh run download <run-id> -n examples-smoke`), which drives the local fixture site and so holds only its public demo credentials.
33+
2. Reduce it, naming it `<example>-<capture date>-<short commit>`, with the example name starting with the adapter (`wdio`, `selenium`, `nightwatch`, `python`):
34+
35+
```sh
36+
pnpm exec tsx packages/trace/tests/fixtures/trace-formats/reduce-fixture.ts <trace.zip> <name> "<where the zip came from>"
37+
```
38+
39+
3. Check what the zip carries before committing it: no tokens, private hostnames, user names or home paths outside the CI runner's `/home/runner/work`.
40+
4. Run `pnpm test`. Keep older fixtures: each one is a format still in users' hands.
41+
42+
A zip from a foreign tool may need the manifest's counting rules in `reduce-fixture.ts` extended, for instance for structural steps other than `Tracing` rows.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** What a fixture zip held when it was reduced, counted from its raw entries rather than through the reader under test. */
2+
export interface TraceFormatManifest {
3+
source: string
4+
runner?: string
5+
browserName?: string
6+
commands: {
7+
count: number
8+
failed: number
9+
titles: string[]
10+
withSnapshotText: number
11+
withCallSource: number
12+
}
13+
consoleLogs: number
14+
networkRequests: { count: number; urls: string[] }
15+
mutations: { count: number; documentAnchors: number }
16+
/** Screencast frames in timeline order, by the resource stem each reduced image carries as its marker. */
17+
frames: string[]
18+
sourceFiles: string[]
19+
transcript: boolean
20+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"source": "examples-smoke CI artifact, run 40774299701 at 14977b93, 2026-10-28 (nightwatch-bdd/1)",
3+
"runner": "nightwatch",
4+
"browserName": "chrome",
5+
"commands": {
6+
"count": 21,
7+
"failed": 1,
8+
"titles": [
9+
"Element.click(\"a.button\")",
10+
"Element.click(\"button[type=\"submit\"]\")",
11+
"Element.click(\"button[type=\"submit\"]\")",
12+
"Element.fill(\"#password\")",
13+
"Element.fill(\"#password\")",
14+
"Element.fill(\"#username\")",
15+
"Element.fill(\"#username\")",
16+
"Element.waitForDisplayed(\"#flash\")",
17+
"Element.waitForDisplayed(\"#flash\")",
18+
"Element.waitForDisplayed(\"#username\")",
19+
"Element.waitForDisplayed(\"#username\")",
20+
"Element.waitForDisplayed(\"#username\")",
21+
"Element.waitForDisplayed(\"#username\")",
22+
"Element.waitForDisplayed(\"a.button\")",
23+
"Page.navigate(\"http://127.0.0.1:4567/login\")",
24+
"Page.navigate(\"http://127.0.0.1:4567/login\")",
25+
"Page.navigate(\"http://127.0.0.1:4567/login\")",
26+
"assert.textContains(\"#flash\", \"You logged into a secure area\")",
27+
"assert.textContains(\"#flash\", \"You logged into a secure area\")",
28+
"assert.urlContains(\"/login\")",
29+
"assert.urlContains(\"/secure\")"
30+
],
31+
"withSnapshotText": 21,
32+
"withCallSource": 21
33+
},
34+
"consoleLogs": 86,
35+
"networkRequests": {
36+
"count": 6,
37+
"urls": [
38+
"http://127.0.0.1:4567/login",
39+
"http://127.0.0.1:4567/secure"
40+
]
41+
},
42+
"mutations": {
43+
"count": 48,
44+
"documentAnchors": 6
45+
},
46+
"frames": [
47+
"6cdde8df7352d373f00f5f8332320a6a2aa09db8",
48+
"7d988f14b29194d3731015f773597fdf90d23930",
49+
"fbb6a64d1f669e9ec0ff80c091fadb3a0b9607e3",
50+
"c8d83b9e30d261ad8585499495db0d7efcd71477",
51+
"7d988f14b29194d3731015f773597fdf90d23930",
52+
"66d600759c3fda30844627f3f3b9f6b6c0103811",
53+
"7d988f14b29194d3731015f773597fdf90d23930"
54+
],
55+
"sourceFiles": [
56+
"/home/runner/work/devtools/devtools/examples/nightwatch/bdd/smoke-test.js"
57+
],
58+
"transcript": true
59+
}
Binary file not shown.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"source": "examples-smoke CI artifact, run 40774299701 at 14977b93, 2026-10-28 (python-pytest/1)",
3+
"runner": "selenium-webdriver",
4+
"browserName": "chrome",
5+
"commands": {
6+
"count": 22,
7+
"failed": 0,
8+
"titles": [
9+
"Element.click(\"{\"id\":\"f.742D14747C86DBDBA1C513BADD248F99.d.9831E87A746BCA0C3A4FB67344DD937D.e.13\"}\")",
10+
"Element.click(\"{\"id\":\"f.742D14747C86DBDBA1C513BADD248F99.d.CEBF4E3C7D57CCC66E9DAAB16684B9B1.e.4\"}\")",
11+
"Element.click(\"{\"id\":\"f.8D1DD0F93A1C8D18FB3BB9EF1415C69B.d.0E25887790809E9D37C9B825965BC35F.e.4\"}\")",
12+
"Element.fill(\"{\"text\":\"SuperSecretPassword!\",\"value\":[\"S\",\"u\",\"p\",\"e\",\"r\",\"S\",\"e\",\"c\",\"r\",\"e\",\"t\",\"P\",\"a\",\"s\",\"s\",\"w\",\"o\",\"r\",\"d\",\"!\"],\"id\":\"f.742D14747C86DBDBA1C513BADD248F99.d.CEBF4E3C7D57CCC66E9DAAB16684B9B1.e.3\"}\")",
13+
"Element.fill(\"{\"text\":\"tomsmith\",\"value\":[\"t\",\"o\",\"m\",\"s\",\"m\",\"i\",\"t\",\"h\"],\"id\":\"f.742D14747C86DBDBA1C513BADD248F99.d.CEBF4E3C7D57CCC66E9DAAB16684B9B1.e.2\"}\")",
14+
"Element.fill(\"{\"text\":\"tomsmith\",\"value\":[\"t\",\"o\",\"m\",\"s\",\"m\",\"i\",\"t\",\"h\"],\"id\":\"f.8D1DD0F93A1C8D18FB3BB9EF1415C69B.d.0E25887790809E9D37C9B825965BC35F.e.2\"}\")",
15+
"Element.fill(\"{\"text\":\"wrong-password\",\"value\":[\"w\",\"r\",\"o\",\"n\",\"g\",\"-\",\"p\",\"a\",\"s\",\"s\",\"w\",\"o\",\"r\",\"d\"],\"id\":\"f.8D1DD0F93A1C8D18FB3BB9EF1415C69B.d.0E25887790809E9D37C9B825965BC35F.e.3\"}\")",
16+
"Element.getText(\"{\"id\":\"f.742D14747C86DBDBA1C513BADD248F99.d.9831E87A746BCA0C3A4FB67344DD937D.e.11\"}\")",
17+
"Element.getText(\"{\"id\":\"f.8D1DD0F93A1C8D18FB3BB9EF1415C69B.d.3770805972768FCE568F32302BB7EE47.e.15\"}\")",
18+
"Page.getTitle()",
19+
"Page.getUrl()",
20+
"Page.getUrl()",
21+
"Page.getUrl()",
22+
"Page.navigate(\"{\"url\":\"http://127.0.0.1:4567/login\"}\")",
23+
"Page.navigate(\"{\"url\":\"http://127.0.0.1:4567/login\"}\")",
24+
"Page.navigate(\"{\"url\":\"http://127.0.0.1:4567/login\"}\")",
25+
"assert.equal(\"title == \\\"The Internet\\\"\")",
26+
"assert.ok(\"\\\"/login\\\" in current_url\")",
27+
"assert.ok(\"\\\"/login\\\" in current_url\")",
28+
"assert.ok(\"\\\"/secure\\\" in current_url\")",
29+
"assert.ok(\"\\\"You logged into a secure area\\\" in f…)",
30+
"assert.ok(\"\\\"Your password is invalid\\\" in flash\")"
31+
],
32+
"withSnapshotText": 22,
33+
"withCallSource": 22
34+
},
35+
"consoleLogs": 37,
36+
"networkRequests": {
37+
"count": 6,
38+
"urls": [
39+
"http://127.0.0.1:4567/login",
40+
"http://127.0.0.1:4567/secure"
41+
]
42+
},
43+
"mutations": {
44+
"count": 58,
45+
"documentAnchors": 6
46+
},
47+
"frames": [
48+
"a8b8d1b58931d9f904bb21a785568726b2b0dc66",
49+
"36a5bcaa0dbe7fd08de055c9032c89864f01934b",
50+
"1ef9bbd8e5f51e11f8579d4a5005affa88b78aa8",
51+
"dd365f897eb8ac06ba865c29803db88b1ee60635",
52+
"3d395b9e24737082192eb7e2d4df67400e91ceb8",
53+
"02f270c0fd87b5d2cfb31999d733d24c77dff87e",
54+
"a8b8d1b58931d9f904bb21a785568726b2b0dc66",
55+
"1a93371bf34b22299253a50eed016febcdcf9133",
56+
"3393e0aec47396ec413d2b8349f2e8d1f06d4927",
57+
"166c6cf749984b868abe4d9125962971f35185cc",
58+
"a8b8d1b58931d9f904bb21a785568726b2b0dc66",
59+
"1a93371bf34b22299253a50eed016febcdcf9133"
60+
],
61+
"sourceFiles": [
62+
"/home/runner/work/devtools/devtools/examples/selenium-py/pytest/test_login_pytest.py"
63+
],
64+
"transcript": true
65+
}
Binary file not shown.

0 commit comments

Comments
 (0)