Overlay and cursor
Overlay
installDemoOverlay(context, persona, device) before newPage(). The package already does this inside runDemoWalk / withDemoPages.
The banner is a fixed HUD — a rounded card inset from the bottom and sides so the app shows around it. It does not wrap the consumer app or rewrite html / body layout. Their 100vh, position: fixed, and modals keep the window as the containing block.
padding-bottom on <html> (and scroll-padding-bottom on <body>) is the bar height so page copy can scroll out from under it. --e2e-demo-bar-height and data-e2e-demo-reserve are that same value.
The bar sits above page chrome (z-index just under the teaching cursor). If it covers a control you need, set E2E_DEMO_OVERLAY=0.
Pass { device: 'desktop' | 'tablet' | 'mobile' } to runDemoWalk so the chip and viewport match. This site’s walk runs all three on stills.
The card shows:
- Persona chip (colors from config)
- Device chip (
DESKTOP/TABLET/MOBILE) - Chapter title and SPA route
- Current-action note (
demoAnnounce)
Caption state lives in sessionStorage (e2e.demo.caption) so a full-page navigation restores the last chapter. The persona chip is e2e.demo.persona — demoFocus writes it from the bound walk.page[id] so stills on the shared-page fast path keep the chip in sync with the chapter. Isolated E2E_DEMO=1 windows already have one overlay per actor.
Test ids: e2e-demo-overlay, e2e-demo-persona, e2e-demo-device, e2e-demo-banner, e2e-demo-banner-title, e2e-demo-banner-route, e2e-demo-banner-note.
Cursor
A SVG pointer (e2e-demo-cursor) is installed on every runDemoWalk, including the fast CI path. It stays hidden until aimed at a control (demoClick, fillField, demoPoint).
On stills, each teaching beat uses one gold ring (e2e-demo-cursor-ring):
- Aim the pointer
- Hold the ring (storyboard
shotcaptures here) - Release — fade that same ring when
E2E_DEMO=1and video is off, snap-remove on CI - Click, type, or leave (
demoPointhas no click)
On video the pulse plays then the click: pointer arrives, ring scales out and fades, click lands when that pulse is done, then a short pause. Video does not hold a static ring or take a still. Stills freeze CSS so the storyboard is the held ring.
The overlay is on by default for stills and video. Set E2E_DEMO_OVERLAY=0 to hide the bar. demoFocus still writes the chapter and the persona chip into the bar when it is shown; pacing dwells stay on E2E_DEMO=1 only.
Why init scripts are not minified
Playwright calls Function#toString() and evals that source in the page. The paint functions take a serializable args object only. Closing over getDemoWalkConfig() would break after the lift into the browser.