diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index 1ee65e5..65d3812 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -1,4 +1,4 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, Locator, Page } from '@playwright/test'; /** * Journey tests for SchoolCompare, run against the staging environment as the @@ -19,6 +19,31 @@ function schoolLinks(page: Page) { return page.locator('a[href^="/school/"]'); } +/** + * A scroll offset that has stopped moving. + * + * The carousel arrows scroll with `behavior: 'smooth'`, so a reading taken + * straight after a click lands mid-animation. Measured against staging: the + * animation runs ~700ms, and a poll for "has it moved at all" is satisfied + * 50ms in, at 13px of a 1300px journey. A test that then records an offset, + * does something, and records again is measuring the tail of the arrow's + * animation rather than the effect of whatever it did in between. + * + * Two identical readings in a row is the cheapest sound definition of settled. + */ +async function settledScrollLeft(scroller: Locator): Promise { + let previous = -1; + await expect + .poll(async () => { + const current = await scroller.evaluate((node: HTMLElement) => Math.round(node.scrollLeft)); + const settled = current === previous; + previous = current; + return settled; + }, { timeout: 10_000 }) + .toBe(true); + return previous; +} + /** * Two URNs guaranteed to be pure-primary (same phase). The compare page's * phase tabs split all-through schools (which carry KS4 data) onto the @@ -2776,9 +2801,7 @@ test('nearby schools link on to other schools and into compare', async ({ page } await expect(back).toBeDisabled(); await forward.click(); - await expect - .poll(() => scroller.evaluate((node: HTMLElement) => node.scrollLeft)) - .toBeGreaterThan(8); + expect(await settledScrollLeft(scroller)).toBeGreaterThan(8); await expect(back).toBeEnabled(); } @@ -2794,12 +2817,12 @@ test('nearby schools link on to other schools and into compare', async ({ page } // // A few pixels of snap or sub-pixel adjustment are fine; a reset to the // start is not, which is the whole point of the check. - const offsetBefore = await scroller.evaluate((node: HTMLElement) => node.scrollLeft); + const offsetBefore = await settledScrollLeft(scroller); await section.getByRole('button', { name: /Add to compare/ }).last().click(); await expect( section.getByRole('button', { name: /Added to compare/ }).first(), ).toBeVisible(); - const offsetAfter = await scroller.evaluate((node: HTMLElement) => node.scrollLeft); + const offsetAfter = await settledScrollLeft(scroller); expect(Math.abs(offsetAfter - offsetBefore)).toBeLessThanOrEqual(8); });