From 64b54de1259f3ef02b1bcdf0e9cbc295c22488fe Mon Sep 17 00:00:00 2001 From: "fcteas\\KameshRaj.Rajendran" Date: Tue, 22 Sep 2026 09:16:34 +0100 Subject: [PATCH] test(e2e): assert the Repealed tag on offence search results The tag itself shipped in #229; this covers it against the real /api/v1/offences response, where previously only the MSW integration suite asserted it. Expectations come from the search response rather than hardcoded codes: the offences are split into repealed (an "effective to" date is present) and active, and the rendered table is checked against that split, so the test holds as the reference data changes. It fails loudly if a search returns only one kind, rather than passing vacuously. Covers all three acceptance criteria - a tag for every offence with an "effective to" date, no tag for any without one, and the tag sitting in the Description column beneath the description text, carrying the GOV.UK warning styling that distinguishes it. Each was falsified before being kept: treating active offences as repealed fails the tag count (expected 19, received 1), looking for the tag in the Statute column fails on visibility, and expecting the description without the tag appended fails the cell text. --- .../offence-results-repealed-tag.spec.ts | 26 +++++++++ .../pages/chargesOffenceSearchPage.ts | 58 ++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/ui-spa/e2e-tests/offence-results-repealed-tag.spec.ts diff --git a/src/ui-spa/e2e-tests/offence-results-repealed-tag.spec.ts b/src/ui-spa/e2e-tests/offence-results-repealed-tag.spec.ts new file mode 100644 index 00000000..639579e9 --- /dev/null +++ b/src/ui-spa/e2e-tests/offence-results-repealed-tag.spec.ts @@ -0,0 +1,26 @@ +import { test } from "@playwright/test"; +import { ChargesOffenceSearchPage } from "./pages/chargesOffenceSearchPage"; +import { generateUniqueUrn } from "./utils/generateUrn"; +import { expectStep } from "./utils/expectStep"; +import { + startSingleSuspectUpToCharges, + chargeDates, +} from "./journeys/suspectChargeSteps"; + +const SEARCH_TERM = process.env.E2E_REPEALED_SEARCH_TERM ?? "burglary"; + +test("Scenario 22: repealed offences are tagged in the search results and active ones are not", async ({ + page, +}) => { + const urn = generateUniqueUrn(); + const { arrestDate } = chargeDates(); + + await startSingleSuspectUpToCharges(page, { urn, arrestDate }); + + const offenceSearchPage = new ChargesOffenceSearchPage(page); + await expectStep( + page, + "/case-registration/suspect-0/charge-0/charges-offence-search", + ); + await offenceSearchPage.searchAndVerifyRepealedTags(SEARCH_TERM); +}); diff --git a/src/ui-spa/e2e-tests/pages/chargesOffenceSearchPage.ts b/src/ui-spa/e2e-tests/pages/chargesOffenceSearchPage.ts index 25596d87..b02d23f3 100644 --- a/src/ui-spa/e2e-tests/pages/chargesOffenceSearchPage.ts +++ b/src/ui-spa/e2e-tests/pages/chargesOffenceSearchPage.ts @@ -1,6 +1,14 @@ import { type Page, type Locator, expect } from "@playwright/test"; import { ChargesOffenceSearchPagePage as IntegrationChargesOffenceSearchPage } from "../../integration-tests/pages/chargesOffenceSearchPage"; +const REPEALED_TAG_PREFIX = "offence-repealed-warning-"; + +interface Offence { + code: string; + description: string; + effectiveToDate: string | null; +} + // Reuses the integration offence-search page object (identical selectors) and // adds e2e-only assertions against the real /api/v1/offences response: an // offence code with no matches (covering both "zero results" and an invalid @@ -17,7 +25,7 @@ export class ChargesOffenceSearchPage extends IntegrationChargesOffenceSearchPag return this.currentPage.getByTestId("offence-search-results-wrapper"); } - private async submitSearch(searchText: string): Promise { + private async submitSearch(searchText: string): Promise { await this.addOffenceSearchText(searchText); // Match regardless of status so a non-2xx fails on the assertion, not a hang. const offencesResponse = this.currentPage.waitForResponse( @@ -30,6 +38,8 @@ export class ChargesOffenceSearchPage extends IntegrationChargesOffenceSearchPag response.ok(), `offence search for "${searchText}" returned ${response.status()}`, ).toBe(true); + const body = (await response.json()) as { offences: Offence[] }; + return body.offences; } async searchAndExpectNoResults(searchText: string): Promise { @@ -48,4 +58,50 @@ export class ChargesOffenceSearchPage extends IntegrationChargesOffenceSearchPag ).toBeVisible({ timeout: 30_000 }); await this.addOffence(0); } + + async searchAndVerifyRepealedTags(searchText: string): Promise { + const offences = await this.submitSearch(searchText); + const repealed = offences.filter((offence) => offence.effectiveToDate); + const active = offences.filter((offence) => !offence.effectiveToDate); + + expect( + repealed.length, + `"${searchText}" returned no repealed offence, so the tag assertions prove nothing`, + ).toBeGreaterThan(0); + expect( + active.length, + `"${searchText}" returned no active offence, so the absent-tag assertion proves nothing`, + ).toBeGreaterThan(0); + + const resultsTable = this.resultsWrapper().getByRole("table"); + await expect(resultsTable).toBeVisible(); + + await expect( + resultsTable.locator(`[data-testid^="${REPEALED_TAG_PREFIX}"]`), + ).toHaveCount(repealed.length); + + for (const offence of repealed) { + const tagId = `${REPEALED_TAG_PREFIX}${offence.code}`; + const row = resultsTable + .locator("tbody tr") + .filter({ has: this.currentPage.getByTestId(tagId) }); + await expect(row).toHaveCount(1); + await expect(row.locator("td").nth(1)).toHaveText(offence.code); + + const descriptionCell = row.locator("td").nth(2); + const tag = descriptionCell.getByTestId(tagId); + await expect(tag).toBeVisible(); + await expect(tag).toHaveClass(/govuk-warning-text/); + await expect(tag).toHaveText("!Repealed"); + await expect(descriptionCell).toHaveText( + `${offence.description}!Repealed`, + ); + } + + for (const offence of active) { + await expect( + this.currentPage.getByTestId(`${REPEALED_TAG_PREFIX}${offence.code}`), + ).toHaveCount(0); + } + } }