# Write Tests for a Wagtail Site

Yes. An agent writes WagtailPageTestCase tests for your page models: where each page may be created, which children it allows, and that each one renders.

[Run this on Rush](https://cloud.getrush.ai/run/write-wagtail-tests/wagtail/), in a Project that has your repository.

## What the agent does

The agent reads the site's own testing conventions first. In a Wagtail site like wagtail/bakerydemo it finds `wagtail>=8.0,<8.1` and `Django>=6.0,<6.1` in `requirements/base.txt`, Python 3.12 in `.python-version`, and an `AGENTS.md` that says render tests live in `bakerydemo/<app>/tests/`, that the test settings `bakerydemo.settings.test` avoid manifest static file errors, and that focused page setup is preferred over loading the full `bakerydemo.json` fixture. The site has two test files, both in `base`: `test_home_page.py`, a `WagtailPageTestCase` that builds the page tree in `setUpTestData` with `Page.get_first_root_node()` and a `Site`, publishes a `HomePage` and checks it renders `base/home_page.html`, and `test_seo.py` for canonical and Open Graph tags. The `blog`, `breads`, `locations`, `people` and `recipes` apps have no tests at all.

It lists the page models and their rules from each `models.py`. `BlogPage`, `BreadPage`, `LocationPage`, `PersonPage` and `RecipePage` each name a single `parent_page_types` entry, their index pages name them as the only `subpage_types`, and `BlogPage`, `RecipePage` and `GalleryPage` allow no children (`subpage_types = []`). `StandardPage` and `HomePage` set no rules. For every model it writes `assertAllowedParentPageTypes` and `assertAllowedSubpageTypes` from `WagtailPageTestCase`, so a change to those lists fails a test, and `assertCanCreateAt` and `assertCanNotCreateAt` for the pairs that matter, such as a `BreadPage` under `HomePage`.

Then it checks that each page can be created through the admin form and renders. It uses `assertCanCreate` with `nested_form_data` and `streamfield` from `wagtail.test.utils.form_data`, the helpers the home page test already imports, filling the fields each model declares: a `BreadPage` with its optional `origin` and `bread_type` foreign keys pointing at snippets the test creates, so the template shows them, a `LocationPage` with a `lat_long` in the `64.144367, -21.939182` form its `RegexValidator` accepts, and one with a value it must reject with `invalid_lat_long`. It publishes each page under its index and checks the response with `assertPageIsRenderable` and `assertTemplateUsed`. `BlogIndexPage` uses `RoutablePageMixin`, so it also requests `tags/<slug>/` for a tag that has posts and for one that does not, which `tag_archive` redirects back to the index with a message.

It runs the suite the way CI does, with `DJANGO_SETTINGS_MODULE=bakerydemo.settings.test`: `./manage.py check`, `./manage.py makemigrations --check --noinput` so the tests add no model drift, and `./manage.py test`. Without `DATABASE_URL`, `settings/base.py` uses SQLite, so no database server is needed. A rule the tests find surprising, such as `HomePage` being creatable anywhere, is kept as the current behaviour and called out in the pull request for a maintainer to decide.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Write tests with wagtail.test.utils.WagtailPageTestCase for this site's page models: creation rules, parent and child page types, and that each page renders. Read AGENTS.md, the requirements, the test settings and the existing tests first and follow their conventions: tests in each app's tests/ package, the page tree built in setUpTestData, focused setup rather than the full demo fixture. For every page model assert its allowed parent and subpage types, check creation through the admin form with nested_form_data, including one invalid value for each field with a validator, and publish a page and check it renders with the expected template. Cover routable sub-URLs where a page defines them. Run python manage.py test with the test settings, plus manage.py check and makemigrations --check. Do not change models or templates. Open a pull request that lists each page model and what is now tested.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-wagtail-site"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The pull request adds a `tests/` package to each app that had none, for bakerydemo `bakerydemo/blog/tests/`, `breads/tests/`, `locations/tests/`, `people/tests/` and `recipes/tests/`, each with an `__init__.py` and a `test_pages.py`, plus tests for `StandardPage` and `GalleryPage` beside the existing ones in `base/tests/`. It changes no model, template or fixture. The description is a table of page models with their parent and child rules as tested, the form cases, and the render check.

The checks are the evidence. bakerydemo's `ci.yml` runs `manage.py check`, `makemigrations --check` and `manage.py test` under the test settings on the pull request, and the session keeps the agent's own runs of the same three commands from its sandbox, with every test name and result.

A reviewer checks that each `assertAllowedParentPageTypes` set matches what editors are meant to be able to do rather than only what the code allows today, that the snippets and images the tests create are minimal and not a second copy of the demo fixture, and that any behaviour called out as surprising gets a decision before it is locked in by a test.

## Run it on every new page model

When a pull request adds or changes a `Page` subclass, its `parent_page_types` or its `subpage_types`, have a GitHub `pull_request` webhook send this request with the pull request URL in `resources`. The agent works on that branch, adds the tests for that model, and runs the suite in its own session and sandbox, so a page that editors cannot create where they expect is caught in review.

A webhook URL on the API key receives `run.completed` or `run.failed` when the turn ends, with the session URL, which your server can post back on the original pull request.

## Why run it on Rush?

Page tests in Wagtail need a working site: a page tree, a default `Site`, images and snippets for the required fields, and a settings module that does not trip over static files. Setting that up on a laptop means a virtualenv, a database and migrations, and keeping them in step with the branch. On Rush the agent installs `requirements/development.txt` in its own sandbox, builds the tree inside each test class, and runs the suite there, and none of it touches your environment.

The result shows its work. Every `manage.py` command the agent ran stays in the session with its output, so the table in the pull request can be checked against the run that produced it. The same request can go to several Wagtail sites at once, each in its own sandbox, each with a pull request someone reviews.

## Frequently asked questions

### Does it load my full demo or production fixture?

No, unless the fixture itself is what you want tested. Each test class builds the pages it needs in `setUpTestData`, as bakerydemo's `AGENTS.md` asks, so tests stay fast and a fixture change in another pull request does not break them.

### What does WagtailPageTestCase give me over Django's TestCase?

Assertions written for page models: `assertCanCreateAt`, `assertAllowedParentPageTypes`, `assertAllowedSubpageTypes`, `assertCanCreate` for posting the admin create form, and `assertPageIsRenderable` and `assertPageIsPreviewable`. It also logs in a superuser for the admin checks, so the tests exercise the same forms editors use.

### Does it need Postgres?

Not for bakerydemo, which falls back to SQLite when `DATABASE_URL` is unset. If your site's settings require Postgres, or your search backend needs it, the agent starts Postgres in its sandbox and sets `DATABASE_URL` for the run.

### Will it test StreamField blocks?

It fills StreamField bodies with the `streamfield` form helper so creation works with real block data, and checks that the rendered page contains each block's content. Ask in the prompt for block-level tests and it adds tests that render each block from `blocks.py` on its own.
