Skip to content

feat(journeys): prescale run replays journeys at load - #8

Merged
pyjeebz merged 5 commits into
mainfrom
feat/journeys-at-load
Nov 2, 2026
Merged

pyjeebz merged 5 commits into
mainfrom
feat/journeys-at-load

Conversation

@pyjeebz

@pyjeebz pyjeebz commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Stacked on #7 (journey format). This PR's base is #7's branch, so the diff shows only this slice. It gets retargeted to main once #7 merges.

When prescale/journeys/ has journeys, prescale run now replays them at load. Each virtual user walks a whole journey (log in, add to cart, place an order), pauses for each step's think time, then starts again as a new user with its own cookies and values. Each journey is checked once and then ramped on its own, so its verdict doesn't depend on any traffic mix. The report names the journey and the step that break first, with per-step timings. A stale journey isn't load-tested and is listed with the reason. --journey NAME picks journeys; --urls (or --path, --from-sitemap) tests URLs exactly as before.

Saved runs gain journeys (one entry per journey: survives, band, culprit step, per-step timings, or the reason it was skipped), verdict.culprit_journey and config.mode: "journeys". All are additive under schema_version: 1. The top-level verdict and stages are the first-to-break journey's, so existing readers keep working. prescale compare treats a URL run and a journey run as not comparable instead of reporting a regression.

How it was verified

Against the example shop (checkout = login → cart → order; the order draws from a 25-connection pool):

$ SHOP_PASSWORD=demo prescale run http://localhost:8416 --dir examples/journeys -u 400 -s 2
First failure  checkout › POST /api/carts/{{cart_id}}/order  errors climb at ~30 users.
                    Journeys (each ramped on its own)
 browse     400+ (held)  —
 checkout   ~20 (20–30)  3. place order  POST /api/carts/{{cart_id}}/order   Server returned 5xx under load…
  • --json: verdict survives 20, culprit_journey: checkout. Per step at 30 users: login 65ms with 0% errors, cart 48ms with 0%, order 142ms with 8.3% errors. prescale show re-renders the table from the saved run.
  • In URL mode, /checkout alone survives about 100. Walking the real journey finds the lower breaking point.
  • One stale journey with --json: the note goes to stderr, stdout is only the Result. Every journey stale: "no journey replays against this app…", exit 1.
  • Each order goes out with its own login's token and cookie under concurrency (mock shop; 0 mixed across more than 20 logins).
  • ruff check cli/src: clean · pytest cli/tests -q: 298 passed. All the pre-existing tests pass unchanged, so URL mode is unaffected.

Risk

Tier: risky · Public surface: the Result fields above plus the JSON Schema; run flags --journey, --urls, --allow-writes, --dir; run switches to journeys when prescale/journeys/ has files; compare treats URL and journey runs as not comparable.

Rollback: revert. Nothing is written outside local .prescale/ runs, and older readers ignore the extra fields (the schema allows additional properties).

Fresh-context review blocked on three bugs. All were reproduced, fixed and pinned by tests:

  • Cookies were lost on redirect hops. httpx rebuilds a redirect's cookies from the client jar, so a login that redirected to /account looked stale. Redirects are now followed one hop at a time, each hop carrying the run's cookies and passing the same-origin check.
  • With --json, notes about stale journeys went to stdout, so the output wasn't valid JSON. They now go to stderr.
  • An app whose / redirects to an SSO host crashed the ramp in the / preflight. Journey runs skip that probe now, since each journey has already been replayed once.

Also from the review:

  • culprit_journey is null when nothing broke.
  • --think-time and launch profiles apply to steps without their own think.
  • A walk that fails without awaiting still yields to the event loop.

Notes for review

  • The dashboard's server-side comparison (private repo) should also treat URL and journey runs as not comparable. That's a follow-up there.
  • robots.txt isn't checked in journey mode: journey files are explicit, not crawled.


Generated by Claude Code

Copilot AI balanced review requested due to automatic review settings November 2, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

# Conflicts:
#	CHANGELOG.md
#	README.md
#	cli/src/prescale_cli/commands/journeys.py
#	cli/src/prescale_cli/journeys/replay.py
@pyjeebz
pyjeebz changed the base branch from feat/journey-format to main November 2, 2026 15:57
@pyjeebz
pyjeebz merged commit e4444a1 into main Nov 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants