Repository navigation
fix: read navigation performance timings without breaking Nightwatch commands - #467
Merged
Merged
Conversation
Half a second after a navigation the plugin read the page's performance timings with browser.execute, a queued Nightwatch command. Issued from a timer, it became a child of whichever command the test was running, and finishing it resolved that command's await early: in CI a login clicked submit while setValue was still typing the password. The read now goes over the raw WebDriver transport, like the drains.
WebDriver's execute runs a string script as a function body, and the script called its wrapper without returning it, so every adapter that sends core's script got null back and no navigation row carried load timings, cookies or document info. Python's copy already returned.
Contributor
[Medium impact] The PR appears safe to merge; both numbered previous findings are fixed and no new actionable issues remain. SummaryRestores navigation timings without sending Nightwatch reads through its command queue.
DiagramsequenceDiagram
participant N as Nightwatch adapter
participant D as WebDriver
participant U as Dashboard
N->>U: Send navigation row
Note over N: Wait 500 ms
N->>D: Execute capture script at configured path
D-->>N: Return timings
alt Original row remains in commandsLog
N->>U: Replace navigation row with timings
else Retry removed original row
Note over N: Skip old row update
end
Reviews (3) · Last reviewed commit: "fix(nightwatch-devtools): send no timing..." · Reviewed by Greptile |
With the capture script returning its payload, the WebdriverIO service sent the timed row again as a new command, so every navigation was listed twice. It now replaces the row, as Selenium already did. Nightwatch applied the timings to its stored row but never told the dashboard, so live mode never showed them; it now sends the same replace.
The probes, drains and performance read reach the driver outside Nightwatch's queue through one address, which dropped the configured default_path_prefix; only the Appium executor carried it. A driver served under a prefix such as /wd/hub rejected every one of them.
A retried command's row is removed and reissued under a new timestamp. A performance read still pending for the old row then replaced a row the dashboard no longer had, and the dashboard listed it again. The timings are sent only while their row is still in the log.
vishnuv688
added a commit
that referenced
this pull request
Oct 27, 2026
An awaited Nightwatch wait ran after the commands that followed it because the plugin's performance read, a queued browser.execute fired from a timer, became a child of the running command and resolved its await early. With that read off the queue the wait keeps its place in every run and the login test passes, so both gaps went stale. The tests that used the gap to exercise a move scoped to one place now build that gap themselves.
This was referenced Oct 27, 2026
Winify
pushed a commit
to Winify/devtools
that referenced
this pull request
Oct 30, 2026
…ce rules Two bugs fixed this week (webdriverio#467, webdriverio#476) were caught by review, not by the conformance suite, because no rule covered them. navigation-row-carries-timings: the fake browser now runs a script that reads getEntriesByType('navigation') against the page's own timings, title and url, as a WebDriver body, a BiDi function declaration or a BiDi expression, so it answers only what the script returns. A capture script that builds its payload and never returns it is caught the way a real browser exposes it. The rule judges every successful navigation row after its replaceCommand updates are applied, as the dashboard shows it. The script is matched by content, not by import path, so it survives moving the script out of core. page-level-assertion-names-no-element: a new scenario test reads the heading, reads the page title (which now reads the same), and asserts the title is not 'Secure Area', a value an element read earlier. Taking the target from either the colliding read or the expected value boxes the wrong element. The WebdriverIO service fails this rule on main, because it stamps node:assert rows with the last element it resolved and hides the user's own getTitle; that is declared as a scoped gap against webdriverio#481 rather than fixed here. The service's rows carry a testUid the suite tree never reports, so the assertion that ends a test was placed by time and, sharing a millisecond with the next test's start, landed in the wrong test. Rows of a uid the tree does not report are now placed together by the first of them. The performance timings cells are now backed by the rule for all four adapters; the page-level rule is listed as internal, being one property of assertion capture.
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
Two bugs in the same code path: how the JS adapters read a page's performance timings after a navigation.
1. Nightwatch: the read could cut the test's own command short. Half a second after a navigation, the plugin called
browser.execute(CAPTURE_PERFORMANCE_SCRIPT). That is a queued Nightwatch command. Issued from a timer, it becomes a child of whatever command the test is running at that moment, and when it finishes, Nightwatch's async tree resolves that parent command's promise early. Found by the examples smoke job (#463) in CI:await browser.setValue('#password', ...)returned early, so the nextclicksubmitted the form while the password was still being typed.setValuefinished, with astale element referencein between, and the click's screenshot shows "Your password is invalid!".The read now goes over the raw WebDriver transport (
webdriverExecute), the same path the plugin's collector drains already use for this reason.2. The capture script never returned its result.
CAPTURE_PERFORMANCE_SCRIPTin core was(function() { ... })()with no outerreturn. WebDriver's execute runs a string script as a function body, so the driver answerednull. As a result, the WebdriverIO service, Selenium and Nightwatch attached no load timings, cookies or document info to any navigation row. The Python adapter has its own copy, which already starts withreturn. The script now returns the payload.3. Once the timings arrived, two adapters handled them wrongly. This only showed up after fix 2:
sendReplaceCommand), as Selenium already did.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 behaviourThe "Performance timings on navigation rows" cells for the three JS adapters stay "checked by hand": no adapter test asserts the timings reach a row yet.
Notes for reviewers
core/tests/performance-capture.test.tsruns the script as a function body, as WebDriver does, and asserts that the payload comes back. It fails without thereturn.nightwatch-devtools/tests/session.test.tsasserts that the read reaches the driver over HTTP, thatbrowser.executeis never called, and that the timings reach the dashboard as areplaceCommand. It fails without either fix.service/tests/session.test.tsasserts that a navigation is sent once and its timings arrive as areplaceCommand. It fails with the old resend.pnpm test2872 passed; lint, typecheck anddocs:capabilities --checkclean; the conformance suite passed 15/15 twice.pnpm demo:wdio,pnpm demo:seleniumandpnpm demo:nightwatch, each navigation is listed once and its detail shows the load timings and document info.🤖 Generated with Claude Code