test(e2e): journeys for row tags and pupil counts, and ask for page_size
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m13s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 17s

Three journeys pin the fixes: a non-selective secondary's row has no
Selective tag, a school recorded with no religious character has no faith
tag, and the list and the school page agree on a secondary's pupil count.
Run against staging before the fix, all three fail.

The list API reads page_size. Nine journeys asked for per_page, which it
ignores, so each got the default 25 rows whatever it asked for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5.5 committed 2026-10-02 22:51:31 +01:00
1 parent 5f9caad7f4
commit 8020191832
1 file changed
+64 -9
+64 -9
View File
@@ -53,7 +53,7 @@ async function settledScrollLeft(scroller: Locator): Promise<number> {
* whatever primaries the environment holds.
*/
async function twoPrimaryUrns(page: Page): Promise<[string, string]> {
const res = await page.request.get('/api/schools?search=primary&per_page=50');
const res = await page.request.get('/api/schools?search=primary&page_size=50');
expect(res.ok()).toBeTruthy();
const body = await res.json();
const urns: string[] = (body.schools ?? [])
@@ -66,7 +66,7 @@ async function twoPrimaryUrns(page: Page): Promise<[string, string]> {
}
async function twoSecondaryUrns(page: Page): Promise<[string, string]> {
const res = await page.request.get('/api/schools?search=school&per_page=100');
const res = await page.request.get('/api/schools?search=school&page_size=100');
expect(res.ok()).toBeTruthy();
const body = await res.json();
const urns: string[] = (body.schools ?? [])
@@ -355,6 +355,61 @@ test('school type groups and the faith filter narrow to what they name', async (
}
});
/*
* Search rows printed tags the register does not hold. Every non-selective
* secondary was "Selective" ("non-selective" contains "selective"), and a
* school with no religious character got "Faith priority" or a bare "None"
* chip, because only "Does not apply" was excluded. Data-invariant: each test
* picks its school from the API and reads only that school's row.
*/
async function rowTags(page: Page, school: { urn: number; school_name: string }) {
await searchByName(page, school.school_name);
const link = page.locator(`a[href^="/school/${school.urn}-"]`).first();
await expect(link).toBeVisible({ timeout: 15_000 });
return link.locator('xpath=ancestor::div[contains(@class, "__rowContent")][1]')
.locator('[class*="__line2"]');
}
test('a non-selective secondary is not tagged Selective in search', async ({ page }) => {
const res = await page.request.get(
'/api/schools?search=school&phase=secondary&admissions_policy=non-selective&page_size=1');
expect(res.ok()).toBeTruthy();
const [school] = (await res.json()).schools ?? [];
test.skip(!school, 'no non-selective secondary in this environment');
expect(school.admissions_policy, 'the admissions filter was ignored').toBe('Non-selective');
const tags = await rowTags(page, school);
await expect(tags).toBeVisible();
await expect(tags.getByText('Selective', { exact: true })).toHaveCount(0);
});
test('a school with no religious character carries no faith tag in search', async ({ page }) => {
const res = await page.request.get('/api/schools?search=school&faith=none&page_size=100');
expect(res.ok()).toBeTruthy();
// Not a selective school: the Selective tag would win and hide the bug.
const school = ((await res.json()).schools ?? []).find(
(s: { religious_denomination?: string; admissions_policy?: string }) =>
s.religious_denomination === 'None' && !/selective/i.test(s.admissions_policy ?? ''));
test.skip(!school, 'no school recorded with religious character "None" here');
const tags = await rowTags(page, school);
await expect(tags).toBeVisible();
await expect(tags.getByText('Faith priority', { exact: true })).toHaveCount(0);
await expect(tags.getByText('None', { exact: true })).toHaveCount(0);
});
test('search and the school page agree on how many pupils a secondary has', async ({ page }) => {
// Search showed the GCSE year group as "pupils": Burntwood had 245 in
// search and 1,462 on its page. Both now carry the register's count.
const res = await page.request.get('/api/schools?search=school&phase=secondary&page_size=20');
expect(res.ok()).toBeTruthy();
const school = ((await res.json()).schools ?? []).find(
(s: { total_pupils?: number | null }) => s.total_pupils != null);
test.skip(!school, 'no secondary with a pupil count in this environment');
const detail = await (await page.request.get(`/api/schools/${school.urn}`)).json();
expect(school.total_pupils).toBe(detail.school_info.total_pupils);
});
test('a phase outside primary/secondary filters to that phase, not to everything', async ({ page }) => {
// The search page offers every GIAS phase, but the API only knew the grouped
// ones and silently dropped the rest — so "Nursery" returned primaries.
@@ -544,7 +599,7 @@ test('school with no performance data still gets a working detail page', async (
const candidates: number[] = [];
for (const q of ['post 16', 'specialist college', 'sixth form']) {
const resp = await page.request.get(
`/api/schools?search=${encodeURIComponent(q)}&per_page=20`
`/api/schools?search=${encodeURIComponent(q)}&page_size=20`
);
if (!resp.ok()) continue;
const body = await resp.json();
@@ -1123,7 +1178,7 @@ test('compare metric-help popover stays within the mobile viewport', async ({ pa
test('admissions year/trend toggle still switches views after the server/client split', async ({ page }) => {
// Find a school with at least two years carrying an offer rate — the toggle
// only appears then. Data-invariant: uses whatever the environment holds.
const res = await page.request.get('/api/schools?search=primary&per_page=50');
const res = await page.request.get('/api/schools?search=primary&page_size=50');
expect(res.ok()).toBeTruthy();
const candidates: number[] = ((await res.json()).schools ?? []).map((s: { urn: number }) => s.urn);
@@ -2083,7 +2138,7 @@ test('English schools with Welsh postcodes are kept', async ({ page }) => {
test('a Welsh school URL 404s while an English one still resolves', async ({ page }) => {
// Paired on purpose: the Welsh assertion alone would also pass if the whole
// site were down, which is the failure this test most needs to distinguish.
const english = await page.request.get('/api/schools?search=primary&per_page=1');
const english = await page.request.get('/api/schools?search=primary&page_size=1');
expect(english.ok()).toBeTruthy();
const [first] = (await english.json()).schools ?? [];
expect(first, 'no English school available to compare against').toBeTruthy();
@@ -2193,7 +2248,7 @@ test('a filtered homepage still canonicalises to the bare root', async ({ page }
});
test('a school page canonicalises to its own slug on the www host', async ({ page }) => {
const res = await page.request.get('/api/schools?search=primary&per_page=1');
const res = await page.request.get('/api/schools?search=primary&page_size=1');
expect(res.ok()).toBeTruthy();
const [first] = (await res.json()).schools ?? [];
expect(first, 'no school available').toBeTruthy();
@@ -2268,7 +2323,7 @@ function blocksEverything(robots: string, agent: string): boolean {
}
test('a school page on staging is noindexed too, not just the homepage', async ({ page }) => {
const list = await page.request.get('/api/schools?search=primary&per_page=1');
const list = await page.request.get('/api/schools?search=primary&page_size=1');
const [first] = (await list.json()).schools ?? [];
expect(first, 'no school available').toBeTruthy();
@@ -2879,7 +2934,7 @@ test('with autosuggest off, the search box is a plain input', async ({ page }) =
async function secondaryWithDestinations(page: Page): Promise<{
urn: string; destinations: any;
}> {
const res = await page.request.get('/api/schools?search=school&per_page=100');
const res = await page.request.get('/api/schools?search=school&page_size=100');
expect(res.ok()).toBeTruthy();
const body = await res.json();
const urns: string[] = (body.schools ?? [])
@@ -2975,7 +3030,7 @@ test('switching to disadvantaged pupils never reveals a withheld figure', async
});
test('a school with no sixth form has no post-16 destinations section', async ({ page }) => {
const res = await page.request.get('/api/schools?search=school&per_page=100');
const res = await page.request.get('/api/schools?search=school&page_size=100');
const body = await res.json();
const noSixthForm = (body.schools ?? [])
.filter((s: { phase?: string; has_sixth_form?: boolean }) =>