Scheduling
Any test can run on a schedule: every hour, every weekday morning, every night. Scheduled runs catch problems that happen without a deploy, such as an expired certificate, a third-party script that broke, or a slow page after a data import. They behave like any other run: results, retries, flaky marking and alerts all apply.
Schedule a test
Section titled “Schedule a test”-
Open the test and its Settings tab (or the Options step when you create a test).
-
Under Schedule, pick a preset:
Preset Cron expression Every hour 0 * * * *Every 6 hours 0 */6 * * *Daily · 09:00 UTC 0 9 * * *Weekdays · 09:00 UTC 0 9 * * 1-5Weekly · Mon 09:00 UTC 0 9 * * 1 -
Click Save changes. The test’s overview shows its schedule.

To remove a schedule, choose No schedule. To pause a test without losing its schedule, switch off Active under Settings → Status; paused tests are skipped by schedules, Run all and CI.
Custom schedules
Section titled “Custom schedules”Any standard five-field cron expression works (minute, hour, day of month, month, day of week). All times are UTC. Set one through the API; the app then shows it in the menu as a custom entry, for example Custom · 30 2 * * *.
| Expression | Runs |
|---|---|
*/15 * * * * |
every 15 minutes |
30 2 * * * |
every night at 02:30 UTC |
0 8,12,16 * * 1-5 |
at 08:00, 12:00 and 16:00 UTC on weekdays |
0 6 1 * * |
at 06:00 UTC on the first day of every month |
curl -s -X PUT "$PW/api/test-cases/TEST_ID" \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"schedule_cron": "30 2 * * *"}'The response includes next_run, the next time the schedule fires in UTC. If next_run is null, the expression
is invalid; check it before relying on it, because an invalid expression is silently skipped. To clear a schedule
through the API, send "schedule_cron": "". Sending null leaves the schedule unchanged.
How the scheduler decides to run a test
Section titled “How the scheduler decides to run a test”Schedules are run by the scheduler service (python -m app.services.scheduler), a separate process in the
Compose stack. Once a minute it looks at every active test that has a schedule:
- It works out the most recent time the cron expression fired, at or before now.
- If the test has had no run of any kind since that time, it queues one.
This is stateless, so restarts are safe:
- A missed slot fires once. If the scheduler was down over several fire times, the test runs once when it comes back, not once per missed slot.
- Any run counts. If someone ran the test by hand, from CI or with Run all after the last fire time, the scheduler treats that slot as done and waits for the next one.
- A busy queue doesn’t double-fire. The run is created when it is queued, so a run still waiting for a worker counts as well.
Scheduled runs:
| Setting | Value |
|---|---|
| Trigger shown on the run | schedule |
| Base URL | the project URL. Environments selected in the top bar don’t apply. |
| Browser and device | Chromium · Desktop |
| Retries, trials, assertions, visual checks | as configured on the test |
| Alerts and email | as configured for the project |
Stuck runs are cleaned up
Section titled “Stuck runs are cleaned up”On the same one-minute tick, the scheduler looks for runs that started (or were queued) long ago and never finished,
for example because a worker restarted during a deploy. A run still pending or running after its time limit
plus a 60-second grace period is marked error with:
Run did not complete — its worker stopped or it exceeded the time limit (orphaned job reaped). Re-run to try again.
The time limit is 300 seconds per trial, so a single-trial test is cleaned up after 6 minutes and a test with 3 trials after 16 minutes. The live view of that run closes at the same time.
Operate the scheduler
Section titled “Operate the scheduler”- Run one scheduler. The due check is stateless but not locked: two scheduler processes can queue the same slot twice. Scale workers, not schedulers.
- It needs the database and Redis, like the backend. It doesn’t run tests itself; workers do.
- Same
SECRET_KEYas the backend and worker, as for every service. - Logs. It logs each run it queues (
scheduled run for test_case=…) and each run it cleans up (reaped orphaned run …). Check them withdocker compose logs scheduler.