🧪 Test Hub
testing: Demo target v3.0.0 · built-in demo site

DEMO-006 Long job: wait without recording the wait

Edit Load test Duplicate

Starts a job with a per-second countdown and waits for it to finish. Proves a long wait does not bloat the activity reel or the video.

asyncdemoslow version: 1.0 DEMO-006__long_job_wait.py

Runs
2
Pass rate
100%
2 passed / 0 failed
Avg duration
2m 02s
p95 2m 02s
Estimate
2m 02s
avg of last 10 judged runs
Flakiness
0.00
0 stable · 1 alternates
Current streak
2
passed

Last 2 results newest on the right — click a square to open that run

Duration per run point color = outcome; dashed = 7-run average; click a point to open the run

Step timing trends the same step compared across the last 2 runs — slowest steps first, one line each

Outcomes by target version

What this test does plain language, derived from the code

  1. page.goto(f"{ctx.base_url}?timer={JOB_SECONDS}")
  2. Note in the log: “phase 1: start the job (this part is worth watching)”
  3. Click #timer-btn
  4. Wait until #timer-panel:not(.hidden) appears
  5. Take a screenshot (“job-started”)
  6. ctx.log(f"phase 2: waiting up to {JOB_SECONDS}s for it to finish")
  7. ⏱ Timed phase: job wait
  8. page.wait_for_selector("#timer-done:not(.hidden)",
  9. timeout=(JOB_SECONDS + 60) * 1000)
  10. Note in the log: “phase 3: the job finished -- check the outcome”
  11. Take a screenshot (“job-finished”)
  12. Check #timer-done contains “finished”
  13. assert page.is_hidden("#timer-panel"), "the countdown should be gone"
Show the code
"""Wait for a long-running job, without recording the wait.

This is the pattern for "kick something off, then wait 30 minutes for it":
a nightly batch, a report build, a slow backend. The demo page's job shows
a countdown that repaints EVERY SECOND on purpose -- the hard case, because
content-hash de-duplication cannot collapse a clock (every tick genuinely
differs).

Two things to notice in the artifacts afterwards:

  * the run took the full wait, but the ACTIVITY REEL only holds the busy
    parts -- starting the job and checking the result. The idle stretch is
    sampled sparsely and the replay jumps it with a "skipped idle" marker.
  * the per-step timings show the wait as one labelled block, so you can
    compare "how long did the job take" across runs on the trend chart.

Change JOB_SECONDS to 1800 for a real 30-minute rehearsal (and raise
timeout_seconds in the sidecar to match, plus a bit).
"""

JOB_SECONDS = 120


def run(page, ctx):
    page.goto(f"{ctx.base_url}?timer={JOB_SECONDS}")

    ctx.log("phase 1: start the job (this part is worth watching)")
    page.click("#timer-btn")
    page.wait_for_selector("#timer-panel:not(.hidden)")
    ctx.screenshot("job-started")

    ctx.log(f"phase 2: waiting up to {JOB_SECONDS}s for it to finish")
    with ctx.timed("job wait"):
        # One wait, not a polling loop: Playwright re-checks for us, and the
        # harness treats an unattended page as idle for activity capture.
        page.wait_for_selector("#timer-done:not(.hidden)",
                               timeout=(JOB_SECONDS + 60) * 1000)

    ctx.log("phase 3: the job finished -- check the outcome")
    ctx.screenshot("job-finished")
    assert "finished" in page.inner_text("#timer-done").lower()
    assert page.is_hidden("#timer-panel"), "the countdown should be gone"

All runs

RunStatusQueuedDuration VersionTriggerBatch
#461 passed 2026-09-25 00:27:40 2m 02s 3.0.0 cli-adopted 2 tests (terminal)
#459 passed 2026-09-25 00:25:35 2m 02s 3.0.0 cli-adopted 2 tests (terminal)