From 802019183275493095671979b24d4bd24a70be76 Mon Sep 17 00:00:00 2001 From: Tudor Date: Fri, 2 Oct 2026 22:50:44 +0100 Subject: [PATCH] test(e2e): journeys for row tags and pupil counts, and ask for page_size 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 --- e2e/tests/journeys.spec.ts | 73 +++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index 13ebecf..4e09915 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -53,7 +53,7 @@ async function settledScrollLeft(scroller: Locator): Promise { * 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 }) =>