This documentation is available as Markdown. For the complete index, see llms.txt. Skip to content

Scheduling

For the complete documentation index, see llms.txt

A schedule is a declaration: this collection (or this test) runs on this cron, in this environment, after this preparation. It is data that travels with the tests it names — versioned with them, pushed with them — and nothing in @unotest/web ticks: no timer is armed locally, no cron is parsed. Whoever hosts your runs executes the schedule — a box or the CI you already have — and npx @unotest/web schedules says so out loud rather than implying a timer that does not exist.

Where schedules live

Two halves, one merged list:

  • schedules in unotest.config — hand-written entries. Each names a collection, a 5-field cron, and optionally an env (the .env.<name> overlay, with its own .runs.<name> history) and a prepare shell command that runs before the collection inside the same queue slot — seeding a database, resetting a fixture. See the config reference.

  • unotest/schedules.yaml — the half the viewer edits when you schedule a test or a collection from the tree:

    schedules:
    - collection: smoke
    cron: "0 * * * *"
    - scenario: checkout/guest
    cron: "0 3 * * *"
    env: staging

    An entry names either a collection: or a single test as scenario: <ref>, plus cron:; env: and prepare: are optional. A missing file means no viewer-managed schedules; a file that exists but cannot be read is an error, never silently “no schedules” — that would un-schedule every suite it names without telling anyone.

The two halves are merged into one list — the shape schedules --json prints and --scheduled=<index> indexes into. On a duplicate target the file wins. Every merged entry carries its source (config or file); the viewer edits only the file half and shows config entries read-only.

The cron expression is opaque to @unotest/web: it is validated as a non-empty string and interpreted by whoever executes it, in that executor’s timezone (a box container is UTC unless its TZ says otherwise).

Reading them back

Terminal window
npx @unotest/web schedules # human list
npx @unotest/web schedules --json # the merged list, as an executor reads it
npx @unotest/web schedules --check # a gate for a pre-push hook or CI

The human list prints one line per entry — cron, target, and in brackets test for a scenario entry, env <name>, prepare \…`, schedules.yaml for file entries — and a preview of the next tick on this machine's clock (next …). An expression outside the previewable cron subset is marked not previewable — an executor may drop it; —checkturns that mark into exit1(otherwiseschedules: N declared, all previewable.and exit0). An unreadable schedules.yamlexits1; mixing —jsonwith—check, or an unknown flag, exits 2`. With nothing declared the command prints how to declare one.

Running a schedule now

An executor runs an entry the same way you can by hand:

Terminal window
npx @unotest/web collection smoke --scheduled
npx @unotest/web e2e checkout/guest --scheduled

--scheduled runs the target the way its entry does: in the entry’s env (unless you set one yourself) and with its prepare command first, inside the same run slot. A collection or test listed more than once needs --scheduled=<index> — the zero-based position in schedules; without it the command refuses to guess which env and preparation you meant. A failed prepare exits 94: the environment was not prepared and nothing was tested, which is a different alert from a red suite (exit 1).

Schedules travel with the bundle

npx @unotest/web bundle push reads the effective schedules and stamps their fingerprint into the bundle manifest, so the box that deploys the bundle can compare what its own reading of the config yields against what the pusher’s machine saw. A registry the pusher’s machine cannot read is one the box cannot read either: the push is refused (✗ error … → fix the schedules registry — the box reads it to know what runs on its own) while a human is still watching, instead of un-scheduling the suite by tonight.