Repository navigation
fix(nightwatch-devtools): key late row replaces by a per-capturer id - #473
Merged
Merged
Conversation
A command's screenshot arrives after its row was sent and is re-sent with replaceCommand. Nightwatch driver rows carried no public id, so the shared replace fell back to the last row with that timestamp, and an assertion stamped in the same millisecond was overwritten: the read showed twice and the assertion vanished (3 of 9 conformance runs). Driver rows now carry an id, a retry keeps the id of the row it replaces, and a screenshot for a row a retry has already superseded is dropped, since it would otherwise bring the old row back under the shared id. The id counter now starts at a random base per capturer. A rerun child's rows join its parent's in the backend accumulator (rerun children keep activeRun), and Nightwatch test_workers share one dashboard, so a counter that restarts at 0 in every process would let one process's replace land on another's row. That covers Selenium and Nightwatch's assertion rows, which already stamped ids from the same counter.
The heading test's failure went unexplained only when its assertion row was overwritten by the read's late screenshot, so with rows keyed by id the gap no longer occurs and Nightwatch is tested on that rule like the others.
Contributor
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryNightwatch rows now carry public ids, and retries keep the id of the row they replace.
No actionable issues were found. This review checked code paths; it did not run tests or a browser. Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Capture command with public id] --> B[Send row]
B --> C{Command retried?}
C -->|Yes| D[Store new row with same public id]
D --> E[Replace dashboard row by id]
B --> F[Late screenshot arrives]
F --> G{Original object still stored?}
G -->|Yes| H[Update its row by id]
G -->|No| I[Drop stale screenshot]
Reviews (1) · Last reviewed commit: "Drop the failed-test conformance gap tha..." · Reviewed by Greptile |
2 of 18 tasks
vishnuv688
added a commit
that referenced
this pull request
Oct 27, 2026
#473 fixed Nightwatch's failed-test gap and this branch fixes Python's, so every adapter is now tested on failed-test-has-error-row: the cell is allTested(FAILED_TEST_RULES), defined once. With both gaps gone the failureUnexplained matcher has no user and is deleted.
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes #458.
A Nightwatch command's screenshot arrives after its row was sent, and
attachScreenshotre-sends the row withsendReplaceCommand(timestamp, row). Nightwatch driver rows carried only the internal_id, so sharedreplaceCommandIndexfell back to the last row with that timestamp. When a node:assert row was stamped in the same millisecond as the read before it, the read's screenshot overwrote the assertion: the stream listed the read twice and lost the assertion (about 3 of 9 conformance runs).Changes:
nightwatch-devtools/src/session.ts:captureCommandstamps a publicidon driver rows (Selenium's rows and Nightwatch's owncaptureAssertCommandalready did).replaceCommand(retry) keeps the replaced row'sid, so the dashboard updates the row it already shows.nightwatch-devtools/src/helpers/browserProxy.ts: a late screenshot for a row no longer incommandsLog(a retry superseded it) sends nothing. With the id kept across a retry this guard is required, or the stale row would replace its successor by id.core/src/session-capturer.ts: the command counter starts at a random base per capturer instead of 0 (see cross-process finding below).agreeOnActionshunk gap and thefailedTestExplainedgap feat(conformance): judge failed tests, call sites and per-test rows #466 added), with theKNOWN_DEBT.nightwatchSameMsReplaceentry and the CLAUDE.md Known debt bullet.features.tsnow tests Nightwatch onfailedTestExplainedlike the service and Selenium.agreement.test.tsused the gap as a fixture and now has its own fixture gap.Cross-process ids
A reviewer asked whether an
idfrom a per-process counter starting at 0 can collide with rows that survive from another process. It can:backend/src/index.tsdeliberately keepsmessageBufferandbaselineStore.activeRunwhen a rerun child connects (consumeRerunChildFlag), so Preserve & Rerun on another failed test still finds data. The child's capturer restarts its counter at 0, andbaselineStore.#replaceCommanduses the same id-firstreplaceCommandIndex, so the child'sreplaceCommandfor id N landed on the parent's row N in the accumulator that baselines and the backend trace export read. The app is not affected in this case because the run start broadcastsclearExecutionDatawithrunStart, which wipes its rows.test_workers(and Selenium jest/vitest workers). Each worker has its own capturer, and all of them stream into one dashboard with no clear between them, so id N from one worker matched another worker's row N in the app too.commandCounterresets per worker/spec, so a bareidcollides across specs".This collision already existed on main for every Selenium row and every Nightwatch assertion row; stamping ids on Nightwatch driver rows would have spread it further. So the counter now starts at
randomInt(2 ** 32) * 2 ** 20inSessionCapturerBase, which coverscaptureCommand,captureAssertCommandand Selenium's rows in one place. Each capturer gets a 2^20-id run, ids stay safe integers (below 2^52), and two capturers collide only if they pick the same base (1 in 2^32)._idis still local dedup bookkeeping and only needs to be unique within the capturer, which it is.Tests
New
nightwatch-devtools/tests/rowIds.test.tsdrives a realSessionCapturerthroughBrowserProxywithDate.nowfrozen, and replays the sent frames through sharedreplaceCommandthe way the app does:getText+ assertion: the late screenshot lands on the read and the assertion survives (fails without the fix: the read showed twice)commandsLogguard)core/tests/session-capturer-base.test.tsgains "numbers each capturer apart" (fails with the counter at 0). Each failure was confirmed by temporarily reverting that part of the fix.Conformance:
npx vitest run packages/conformance/tests/conformance.test.tsran 10 times after merging main with both gaps removed, 15/15 green every time. Before the fix, with the first gap removed, the first run failed in all three BiDi modes withdevtools-service shows [Assert.equal #flash], nightwatch-devtools shows [Element.getText #flash].pnpm build,pnpm test(3022 passed),pnpm lint(0 errors),pnpm typecheckandpnpm docs:capabilities --checkall pass.Type of change
Packages touched
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)selenium-devtools-py(Selenium Python adapter)backend(server)app(UI)script(page-injected runtime)trace(Trace mode)Capability matrix
packages/conformance/src/features.tsupdated, and tables regenerated withpnpm docs:capabilities, if this adds or changes user-facing behaviour (the Nightwatch cell gains thefailedTestExplainedrule; the rendered tables are unchanged)Notes for reviewers
pnpm demo:nightwatch, then confirm each assertion row after a read keeps its own row and the read has its screenshot.@wdio/selenium-devtoolsas a patch too. Its test that asserted the first_idwas0now asserts consecutive ids.corebecause the counter is shared by every adapter's capturer; the Nightwatch-specific part (stamping and keeping the id, the screenshot guard) stays in the adapter._command_counterincapturer.pyalso starts at 0 per process and has the same rerun exposure.🤖 Generated with Claude Code