Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ updates:
cooldown:
default-days: 7
open-pull-requests-limit: 5
# @playwright/test 1.61 rewrote its config loader to call
# `require(..., {conditions})` and to return module sources from an ESM
# `load` hook. Yarn PnP supports neither, so every version past 1.60
# dies before the first test file is collected. Held back until PnP
# catches up or the frontend moves off it.
ignore:
- dependency-name: "@playwright/test"
versions: [ ">= 1.61" ]
groups:
frontend:
applies-to: version-updates
Expand Down
2 changes: 1 addition & 1 deletion services/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"@eslint/js": "^10.0.1",
"@hey-api/openapi-ts": "0.99.0",
"@originjs/vite-plugin-commonjs": "^1.0.3",
"@playwright/test": "1.60.0",
"@playwright/test": "1.63.0",
"@types/luxon": "^3.7.5",
"@types/node": "^26.2.0",
"@types/webfontloader": "^1.6.38",
Expand Down
9 changes: 2 additions & 7 deletions services/frontend/tests/e2e/board-slice-open.motion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,8 @@ test.describe("a member's slice opening on a phone", () => {

test.describe("a member's slice opening for a visitor who asked for less motion", () => {
test("clamps both movements to the ceiling the island allows", async ({page}) => {
/*
* Asked of the page rather than declared as an option, which is what the season spec next
* door does and for the same reason: on Playwright 1.60 `use.reducedMotion` does not reach
* the page at all, so `matchMedia` answers false however the option is set — #852. A test of
* what a visitor with the preference gets has to actually be one, so it says so here. Once
* #852 is fixed this line is what should go.
*/
// Asked of the page rather than taken from the project: this file runs in the motion
// project, which is the one project that asks for no preference at all.
await page.emulateMedia({reducedMotion: "reduce"})
const watched = await boardOnAPhone(page)

Expand Down
6 changes: 2 additions & 4 deletions services/frontend/tests/e2e/boards-swipe.motion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,8 @@ test.describe("dragging the board page", () => {
/**
* What a visitor who asked for reduced motion gets, which is the gesture without its tails.
*
* The preference is emulated for this test rather than taken from the project, because
* `use.reducedMotion` does not reach the page on Playwright 1.60 — #852 — so every
* "deterministic" project in this suite is in fact running with full motion. The same line and
* the same reasoning are in `esports-season-on-show.spec.ts`; when #852 is fixed, both go.
* The preference is emulated for this test rather than taken from the project: this file runs
* in the motion project, which is the one project that asks for no preference at all.
*
* The durations are read off the animations themselves rather than timed with a clock. Eight
* workers share four vCPUs here, so a wall-clock measurement of "it settled quickly" is a
Expand Down
16 changes: 8 additions & 8 deletions services/frontend/tests/e2e/boards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ const restingDissolve = (page: Page): Promise<number> => page.evaluate(() => (
* it computes. A named direction in here at all is a picture fading the wrong way — to the
* right, across the reading direction, which is the shape this one replaced.
*
* The depth is left loose on purpose. This suite runs with full motion whatever the config says
* (#852), so the depth read a moment after a press is wherever the eased dissolve had got to,
* and a whole number of percent is a coin toss on how busy the machine is. Where it comes to
* rest is asserted frame by frame in the motion spec, which is the layer that can watch it.
* The depth is left loose on purpose. The dissolve is eased even under the reduced ceiling, so
* the depth read a moment after a press is wherever it had got to, and a whole number of percent
* is a coin toss on how busy the machine is. Where it comes to rest is asserted frame by frame in
* the motion spec, which is the layer that can watch it.
*/
const DOWNWARDS = /^linear-gradient\(rgb\(0, 0, 0\) 0px, rgb\(0, 0, 0\) \d+(?:\.\d+)?%, rgba\(0, 0, 0, 0\) 100%\)$/

Expand Down Expand Up @@ -793,10 +793,10 @@ test.describe("board page", () => {
/*
* How far down each of the two has gone, as a percentage of the picture's band.
*
* Both read inside one `evaluate`, so both are the same frame's answer. This suite runs
* with full motion whatever the config says (#852), so the depth a moment after a press is
* wherever the ease had got to — which is exactly why the two have to be read together and
* why the pair is polled until it settles rather than sampled once.
* Both read inside one `evaluate`, so both are the same frame's answer. The dissolve is eased
* even under the reduced ceiling, so the depth a moment after a press is wherever it had got
* to — which is exactly why the two have to be read together and why the pair is polled until
* it settles rather than sampled once.
*/
const depths = () => member.getByRole("button").evaluate((body) => {
const depth = (style: CSSStyleDeclaration) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,10 @@ test.describe("dragging the esports index between seasons", () => {
/**
* What a visitor who asked for reduced motion gets, which is the gesture without its tails.
*
* The preference is emulated for this test rather than taken from the project, because
* `use.reducedMotion` does not reach the page on Playwright 1.60 — #852 — so every
* "deterministic" project in this suite is in fact running with full motion. The same line and
* the same reasoning are in `boards-swipe.motion.spec.ts` and `esports-season-on-show.spec.ts`;
* when #852 is fixed, all of them go.
* The preference is emulated for this test rather than taken from the project, because this
* file runs in the motion project, which asks for no preference on purpose. A reduced-motion
* case living here has to say so itself. The same line and the same reasoning are in
* `boards-swipe.motion.spec.ts` and `board-slice-open.motion.spec.ts`.
*
* The durations are read off the animations themselves rather than timed with a clock. Eight
* workers share four vCPUs here, so a wall-clock measurement of "it settled quickly" is a
Expand Down
25 changes: 10 additions & 15 deletions services/frontend/tests/e2e/esports-season-on-show.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect, test} from "./test"
import {installApiMocks} from "./mocks"
import {landing} from "./sliceBand"
import {landingFrom} from "./sliceBand"

/**
* The season a page opens on, and that it is the same season everything on it describes.
Expand All @@ -9,9 +9,9 @@ import {landing} from "./sliceBand"
* about which season is arrived at. The movement itself is in
* `esports-season-swipe.motion.spec.ts`.
*
* Those projects are meant to emulate reduced motion and do not: see #852. So nothing here may
* assume the choreography is switched off, and the one test below that is about the preference
* sets it for itself.
* Those projects run as a visitor who asked for reduced motion, which shortens the pass rather
* than removing it: the two stops are both on the page for the clamped duration, so a landing is
* watched for from before the season is asked for rather than sampled after the fact.
*
* CS:GO is the game that makes these assertions mean something: it played the older of the two
* seasons and nothing since, so a page that lets the api pick a season per game shows it, and
Expand Down Expand Up @@ -95,14 +95,17 @@ test.describe("the season a page opens on", () => {
await page.getByTestId("esports-game-CS2").hover()
await expect(page.getByTestId("esports-game-CS2")).toHaveClass(/slice--open/)

// Watched from before the change is asked for: this visitor's pass is a tenth of a second,
// which is shorter than the round trip a loaded runner takes to install a watch.
const drawn = await landingFrom(page, SWIPE, "esports-game-CS2", "esports-game-CSGO")

await page.getByTestId("esports-season-node-19").click()
await expect(page.getByTestId("season-swipe")).toHaveAttribute("data-swipe", "past")

// Open in the frame the arriving season is first drawn in, while the pass is still on,
// rather than growing again once it is over. CS:GO is what says the band is the older
// season's, the slice being read having stood on the season before it as well.
const drawn = await landing(page, SWIPE, "esports-game-CS2", "esports-game-CSGO")
expect(drawn).toEqual({open: true, panels: 2})
expect(await drawn()).toEqual({open: true, panels: 2})

// The season travelled. The subject did not change under them on the way.
await expect(page.getByTestId("esports-game-CS2")).toHaveClass(/slice--open/)
Expand All @@ -118,15 +121,7 @@ test.describe("the season a page opens on", () => {
})

test("crosses the seasons over rather than travelling, for a visitor who asked for less motion", async ({page}) => {
// Asked for here rather than left to the project.
//
// `use.reducedMotion: "reduce"` is set on every project but the motion one, and on
// Playwright 1.60 it does not reach the page: `matchMedia("(prefers-reduced-motion:
// reduce)")` answers false throughout the deterministic suites. That is a fault in the
// harness rather than in this behaviour — #852 — and it is not this spec's to fix, but a
// test of what a visitor with the preference gets has to actually be one. Once #852 is
// fixed this line is what should go.
await page.emulateMedia({reducedMotion: "reduce"})
// The preference comes from the project, which every project but the motion one sets.
await installApiMocks(page)

await page.goto("/esports/competitive-scene")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect, test} from "./test"
import {installApiMocks} from "./mocks"
import {eightSeasonFixtures} from "./esportsStrip"
import {landing} from "./sliceBand"
import {landingFrom} from "./sliceBand"

/**
* Moving between seasons, and which way.
Expand Down Expand Up @@ -66,12 +66,13 @@ test.describe("swiping between seasons", () => {
await page.goto("/esports/valorant")
await page.getByTestId("season-swipe").waitFor()

const drawn = await landingFrom(page, SWIPE, "team-roster-52")

await page.getByTestId(`esports-season-node-${OLDER}`).click()

// Its team's slice is open in the frame the band is first drawn in, with the pass still on:
// the pass is the whole animation, so nothing grows once it is over.
const drawn = await landing(page, SWIPE, "team-roster-52")
expect(drawn).toEqual({open: true, panels: 2})
expect(await drawn()).toEqual({open: true, panels: 2})

// The season that left is gone rather than parked off-screen, and the one that arrived is
// where the band always sits.
Expand Down
5 changes: 1 addition & 4 deletions services/frontend/tests/e2e/membership.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,9 @@ test.describe("membership page", () => {
* The one moving thing on the page, for a visitor who asked for less of it: the words are
* simply there, at rest, rather than arriving.
*
* The preference is emulated here rather than left to the project: `use.reducedMotion` does
* not reach the page on Playwright 1.60 (#852), so a spec that assumes it is asserting the
* unreduced path under a reduced name.
* The preference comes from the project, which every project but the motion one sets.
*/
test("does not animate the pitch in for a visitor who asked for less motion", async ({page}) => {
await page.emulateMedia({reducedMotion: "reduce"})
await installApiMocks(page)
await page.goto("/membership")

Expand Down
42 changes: 28 additions & 14 deletions services/frontend/tests/e2e/sliceBand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,25 +103,39 @@ export function arrivedOpen(seen: {open: boolean, height: number}[]): void {
}

/**
* How the band at [swipe] is drawn in the frame [testid] is first on the page: whether that
* slice is open, and how many panels the band holds, two of them saying the pass is still on.
* Watches the band at [swipe] from now on, answering with a read of the frame [gate] is first
* drawn in: whether [testid] is open in it, and how many panels the band holds, two of them
* saying the pass is still on.
*
* Read at a frame boundary rather than polled, since the claim is about the first frame. [gate]
* is what says the arriving stop is the one being read, where the slice is not itself that proof;
* the band leaving answers to no name, its testids taken off it at the swap.
* Begun before the change is asked for, and counted on the page. A pass is over in the time a
* visitor who asked for less motion allows it — a tenth of a second — so a watch installed
* after the click is a race against the runner's own round trips, and a loaded one loses it:
* the band then answers with one panel for a pass that did happen. [gate] is what says the
* arriving stop is the one being read, where the slice is not itself that proof; the band
* leaving answers to no name, its testids taken off it at the swap.
*/
export async function landing(
export async function landingFrom(
page: Page, swipe: string, testid: string, gate = testid,
): Promise<{open: boolean, panels: number}> {
const handle = await page.waitForFunction(([sel, id, key]) => {
if (!document.querySelector(`[data-testid="${key}"]`)) return null
const slice = document.querySelector(`[data-testid="${id}"]`)
return {
open: slice?.className.includes("slice--open") ?? false,
panels: document.querySelectorAll(`${sel} > *`).length,
): Promise<() => Promise<{open: boolean, panels: number}>> {
await page.evaluate(([sel, id, key]) => {
const tick = () => {
if (!document.querySelector(`[data-testid="${key}"]`)) return requestAnimationFrame(tick)
const slice = document.querySelector(`[data-testid="${id}"]`)
;(window as unknown as {__landing?: unknown}).__landing = {
open: slice?.className.includes("slice--open") ?? false,
panels: document.querySelectorAll(`${sel} > *`).length,
}
}
delete (window as unknown as {__landing?: unknown}).__landing
requestAnimationFrame(tick)
}, [swipe, testid, gate] as const)
return handle.jsonValue()

return async () => {
const handle = await page.waitForFunction(() => (window as unknown as {
__landing?: {open: boolean, panels: number}
}).__landing ?? null)
return handle.jsonValue()
}
}

/** The height the band at [swipe] is holding right now, or nothing where it holds none. */
Expand Down
53 changes: 15 additions & 38 deletions services/frontend/yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading