From d2115364ae59d5d4fe206d09b0c99a2fdbe8c1ab Mon Sep 17 00:00:00 2001 From: Tudor Date: Wed, 26 Aug 2026 20:39:33 +0100 Subject: [PATCH] test(e2e): autosuggest journeys, gated on the flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature state is read from its observable effect — whether the search box is a combobox — because /api/flags is denied to the public on purpose. Same approach as the distance journeys. The three endpoint tests are ungated: /api/suggest is live whether or not the UI is, which is what lets it be smoke-tested in an environment where the feature is still dark. The flag-off journey asserts the plain search still works, not just that the combobox is absent. Verified against staging, where the flag is off: it passes and the flag-on journey correctly skips. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj --- e2e/tests/journeys.spec.ts | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index e99174d..db3fab7 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -2111,3 +2111,66 @@ test('the rankings page still orders by score, not name', async ({ page }) => { .filter((v: number | null) => v != null); expect(scores).toEqual([...scores].sort((a: number, b: number) => b - a)); }); + +/* + * School autosuggest (spec 2026-08-26). + */ +async function autosuggestIsOn(page: Page): Promise { + await page.goto('/'); + return (await page.getByRole('combobox').count()) > 0; +} + +test('the suggest endpoint answers from Typesense', async ({ page }) => { + // Not flagged — the endpoint is live even while the UI is dark, so it can + // be smoke-tested before the feature is switched on. + const res = await page.request.get('/api/suggest?q=brecknock'); + expect(res.ok()).toBeTruthy(); + const { suggestions } = await res.json(); + expect(Array.isArray(suggestions)).toBeTruthy(); + if (suggestions.length) { + // Local authority is what tells two "St Mary's" apart. + expect(suggestions[0]).toHaveProperty('school_name'); + expect(suggestions[0]).toHaveProperty('local_authority'); + } +}); + +test('a one-character query is answered, not rejected', async ({ page }) => { + // The keystroke path never errors on ordinary input. + const res = await page.request.get('/api/suggest?q=b'); + expect(res.status()).toBe(200); + expect((await res.json()).suggestions).toEqual([]); +}); + +test('the suggest response is cacheable', async ({ page }) => { + const res = await page.request.get('/api/suggest?q=brecknock'); + expect(res.headers()['cache-control'] ?? '').toContain('s-maxage'); +}); + +test('typing a school name suggests it, and choosing it opens that school', async ({ page }) => { + test.skip(!(await autosuggestIsOn(page)), + 'the school_autosuggest flag is off in this environment'); + + // A school certain to exist in any environment with data. + const { schools } = await (await page.request.get('/api/schools?page_size=1')).json(); + test.skip(!schools?.length, 'no schools in this environment'); + const name = schools[0].school_name as string; + + await page.goto('/'); + await page.getByRole('combobox').first().fill(name.slice(0, 12)); + const option = page.getByRole('option').first(); + await expect(option).toBeVisible(); + await option.click(); + await expect(page).toHaveURL(/\/school\/\d+/); +}); + +test('with autosuggest off, the search box is a plain input', async ({ page }) => { + test.skip(await autosuggestIsOn(page), + 'the school_autosuggest flag is on in this environment'); + + await page.goto('/'); + await expect(page.getByRole('combobox')).toHaveCount(0); + // And the box still works: the existing search must be untouched. + await page.getByPlaceholder(/School name or postcode/i).first().fill('abbey'); + await page.getByRole('button', { name: /Search/i }).first().click(); + await expect(page).toHaveURL(/search=abbey/); +});