Click-driven routines
A routine is a script: a list of steps that run top to bottom, loop after loop. Each step does exactly one thing — click, wait, move the cursor, or run your code. One clock. Anything that changes your mock uses { click } (your handlers) or { run } — not a second schedule on the routine.
// Your click handlers and resetApp() — busker does not wire these.
busk(root, { start: [0.55, 0.25], steps: [ {wait: 900}, {click: '[data-nav-item="alerts"]'}, {wait: 1500}, {click: '[data-row="p0"]'}, {wait: 2600}, {click: '[data-close]'}, {wait: 900}, {move: [0.55, 0.25]}, {run: () => resetApp(root)}, ], clickTargets: ['[data-nav-item="home"]', '[data-nav-item="alerts"]', '[data-row="p0"]'],});| Step | What it does |
|---|---|
{ click: 'selector' } | Glide to the element, dwell, press, and really .click() it. |
{ wait: ms } | Pause — reading time after whatever just opened or happened. |
{ move: selector | [x, y] } | Glide somewhere without pressing (park the cursor before the loop ends). |
{ run: () => void } | Your code once at this beat (toggle a class, append a row, update copy — whatever your mock needs). |
That is the whole vocabulary. Pauses are never folded into click steps, so the script reads like a storyboard and nothing drifts when you edit a { wait }.
Cursor motion (motion)
Section titled “Cursor motion (motion)”Glide speed is not on each step. Pass motion once on the routine (defaults are tuned for product mocks):
busk(root, { motion: { pxPerSecond: 580, easing: [0.4, 0, 0.2, 1], dwellMs: 250, }, steps: [/* … */],});Busker measures distance in pixels when each glide starts. Duration = distance ÷ pxPerSecond. Progress along the line uses easing, a CSS-style cubic-bezier (default [0.4, 0, 0.2, 1]).
Each loop re-compiles the script from fresh layout. Hidden step targets are remeasured when the playhead reaches them so glides stay at the same speed.
Click targets
Section titled “Click targets”clickTargets is the list of selectors that should look clickable and count as hits when a visitor clicks (dead clicks still get the miss hint):
clickTargets: ['[data-nav-item="alerts"]', '[data-filter]'],Busker does not change your UI. Wire click handlers (or rely on scripted { click } steps) for navigation, filters, and modals. To reset the mock before the next lap, add a final { run: () => … } step (often after a { move } that parks the cursor).
Where the cursor starts
Section titled “Where the cursor starts”start is where the cursor rests before the first step, as a fraction of the root: [0.55, 0.25] is a bit right of centre, a quarter of the way down. It defaults to the middle.
Delay before the loop
Section titled “Delay before the loop”delay waits after the mock is eligible to play (visibility + canPlay) before the first beat. During that wait the cursor stays hidden and play() / pause() do nothing — unlike a leading { wait }, which keeps the cursor visible at start. If the mock scrolls off screen or canPlay() goes false mid-wait, the timer resets and starts again when eligibility returns.
busk(root, { delay: 900, steps: [ {click: '[data-nav-item="alerts"]'}, // … ],});Migrating from v1
Section titled “Migrating from v1”| v1 | v2 |
|---|---|
TimedRoutine (duration + moves[]) | Removed. Use steps only. |
{ click: '…', wait: 900, moveFor: 550 } | { wait: 900 }, { click: '…' } |
{ to: [0.5, 0.5], wait: 900 } | { wait: 900 }, { move: [0.5, 0.5] } |
{ run: fn, wait: 400 } | { wait: 400 }, { run: fn } |
tasks, toggles, typing, countdowns on the routine | { run } steps in order (or click handlers) |
onLoop | final { run: () => reset() } step in steps |
ScriptRoutine / assertScriptRoutine | Routine / assertRoutine |
compile() → { moves, tasks, … } | { glides, runs, duration } — see compile() helper |
per-step moveFor / dwell | optional routine motion |
Loop length for a test? Use compile().
← Markup · Next: compile() helper