Understand where time goes
Open Slowest tests in the reporter, then a test’s Timings tab. Dyno always records document startup, hooks, the test body, queued errors, and operations such as navigation, requests, screenshots, and native calls.
Give meaningful workflows a name with step():
describe('Contacts', () => { test('saves a contact', async ({ app, I, step }) => { await app.goto('/contacts/new'); await step('Save contact', async () => { await I.find('Name').then.fill('Robert'); await I.click('Save'); await I.find('$([role=status])').text('Contact saved'); }); });});Use steps in hooks too. Await them before finishing; they return the callback’s value and preserve its error. Custom Interactor actions stay inside the enclosing test or hook unless you add a named step.
Read the numbers
Section titled “Read the numbers”Durations include waiting, not just CPU work. Nested steps include their children and parallel operations overlap, so don’t add all rows together. Debug pauses are included and marked. Requests end when headers arrive; body reading belongs to the surrounding step.
Check session.json when browser tests are fast but the command is slow. It separates config, bundling, project setup, server readiness, browser startup/results, and cleanup. Cleanup includes report saving and stopping the app and browser.
jq '.performance.slowestTests' .dyno/reports/<run-id>/run.jsonjq '.timings.entries | sort_by(-.duration)' .dyno/reports/<run-id>/session.jsonUse timings in tools and agents
Section titled “Use timings in tools and agents”JSON test events and saved results include timings: { duration, entries, truncated }. performance.slowestTests ranks up to five tests and their slowest work. Use these to compare runs or identify the next workflow to investigate.
Timing fields and clocks
Each entry has id, kind, name, start, duration, and status (passed, failed, or cancelled). Operations and steps include phase. Test numbers are milliseconds from that runtime’s monotonic clock. Hook rows retain function names and execution order.
Single-worker summaries include outsideTestsDuration when run duration is available. It covers time outside accepted test runtimes, including frame startup, reporting, and between-test inspection. Concurrent summaries instead use coordination: coordinator wall time stays separate from summed browser work. Its clock starts at the first registered catalog and ends after terminal draining, before report persistence.
Worker timings show assigned execution, handoff after result acceptance, queue time for assigned requests, and remaining unassigned time. Handoff includes document retirement, project cleanup, and final completion waiting. projectCleanupDuration is already part of handoff; don’t add it again. Unassigned time includes waiting after the last assignment.
Session timing uses its own clock and includes startup, report writing, and shutdown. It stays running until resources close, then is finalized atomically with a session-timing event. Watch and manual runs share one session across browser runs. The final report write itself is outside measured wall time. browserCleanup.graceful and the browser-stopped event show whether process cleanup was forced.
Each test keeps up to 500 entries; truncated counts omissions. A runtime that can’t report has no timings, rather than invented zeros. Request labels omit queries, bodies, and headers. Keep secrets out of step and screenshot names: those appear in reports.