mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
test(e2e): the suite reads the clock the page is rendered on
The v0.1.13 tag went red here, and none of it was the release. WALL CLOCKS Snooze times are computed in the BROWSER, deliberately — the server is never told the session's zone — but the row is rendered server-side in the user's zone, which falls back to Europe/Berlin. The spec then read the hour back with Node's getHours(), which answers in the RUNNER's zone. All three agree on a developer's machine, which is why this passed eighteen consecutive local runs. On a GitHub runner the browser and Node are UTC while the server is still Berlin: the mail came back at 18:00, the row said 20:00, and the assertion reported the wrong TIME being shown when the truth was the wrong CLOCK being read. APP_TIMEZONE now names the zone rendered times are in, in one documented place; the assertion converts into it rather than trusting the process. And playwright.config pins the browser to it, so a menu that offers six in the evening cannot produce a row that says eight — the divergence is an artefact of an unpinned runner and no real user has it, since their browser and their configured zone are the same place. Reproduced before fixing: reverting the conversion under TZ=UTC fails with the CI failure exactly. TWO FIXTURES LEFT TO CHANCE, both found by running under TZ=UTC at CI's two workers. calendar.spec seeds calendars and never seeded a mailbox, but its pane cases assert on the mail list beside the calendar. Those rows were whatever the last spec on the worker left: bulk-select-all archives two hundred messages and then clears them, after which `[data-row-layout]` matches nothing and the failure reads as a broken container query when there is simply no row to lay out. admin-panels assumed the Processes panel starts open. Collapse is stored per admin user, the admin is shared rather than per worker, and a run that fails anywhere after the first toggle leaves it collapsed — so the next run failed on its opening assertion, reporting a broken feature and really reporting its own earlier failure. What collapsed it this time is not established; the test establishes its own precondition now, which is the part that was missing either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7b2334614
commit
cbfca378f8
5 files changed
+80
-3
No files matched your search
+17
-1
@@ -1,5 +1,5 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import { BASE_URL } from "./tests/e2e/support/config";
|
||||
import { APP_TIMEZONE, BASE_URL } from "./tests/e2e/support/config";
|
||||
|
||||
/**
|
||||
* plMail end-to-end configuration.
|
||||
@@ -77,6 +77,22 @@ export default defineConfig({
|
||||
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
// The browser keeps the same clock the server renders in.
|
||||
//
|
||||
// Times reach a page two ways: formatted server-side in the user's zone,
|
||||
// which falls back to Europe/Berlin, and computed in the browser — the
|
||||
// snooze and schedule menus work out "later today" and "tomorrow morning"
|
||||
// locally, on purpose, because the server is never told the session's zone.
|
||||
// With the browser on UTC those two disagree by the offset: the menu offers
|
||||
// six in the evening, the row that comes back says eight.
|
||||
//
|
||||
// Unpinned this is the runner's zone, so it is Europe/Berlin on a
|
||||
// developer's machine and UTC on a GitHub runner — which is the difference
|
||||
// between a suite that passes locally and a tag that fails, two hours out,
|
||||
// for reasons that look like flakiness. Pinning it also makes the suite
|
||||
// describe the ordinary case, where a person's browser and their configured
|
||||
// zone are the same place.
|
||||
timezoneId: APP_TIMEZONE,
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure",
|
||||
// Off, not "retain-on-failure": that still screencasts every test and then
|
||||
|
||||
@@ -46,6 +46,19 @@ test.describe("admin panels", () => {
|
||||
await page.goto("/admin?section=system");
|
||||
|
||||
await expect(panel(page, "processes")).toBeVisible();
|
||||
|
||||
// Put the panel where this test needs to start, rather than trusting it
|
||||
// to be there. Collapse is stored PER ADMIN USER on the server and the
|
||||
// admin is shared, not per worker, so the starting state is whatever the
|
||||
// last run of this test left behind — and a run that fails anywhere
|
||||
// after the first toggle leaves it collapsed. The next run then failed
|
||||
// on its opening assertion, reporting a broken feature and really
|
||||
// reporting its own previous failure. One red run became a red run
|
||||
// every time until somebody clicked it open by hand.
|
||||
if (null === await panel(page, "processes").getAttribute("open")) {
|
||||
await toggle(page, "processes");
|
||||
}
|
||||
|
||||
await expect(panel(page, "processes")).toHaveAttribute("open", "");
|
||||
|
||||
await toggle(page, "processes");
|
||||
|
||||
@@ -546,6 +546,22 @@ test.describe("a meeting on two calendars", () => {
|
||||
});
|
||||
|
||||
test.describe("calendar pane", () => {
|
||||
/**
|
||||
* The mail this describe reads, seeded rather than inherited.
|
||||
*
|
||||
* Several cases here assert on the LIST beside the calendar — that its rows
|
||||
* stack when the pane takes the width, that the mail is still there behind
|
||||
* a full-width pane. This file seeds calendars and never seeded a mailbox,
|
||||
* so those rows were whatever the previous spec on this worker happened to
|
||||
* leave: bulk-select-all archives two hundred messages and then clears
|
||||
* them, and after it the inbox is empty. `[data-row-layout]` then matches
|
||||
* nothing and the failure reads as the container query being broken, when
|
||||
* there is simply no row to lay out.
|
||||
*/
|
||||
test.beforeEach(() => {
|
||||
seed("seed-mail");
|
||||
});
|
||||
|
||||
/**
|
||||
* The switch has three positions — mail, split, calendar — and cycles
|
||||
* through them in that order, wrapping. Which one it is in is a stored user
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "./support/test";
|
||||
import { INBOX_SUBJECTS, mailRow, seed } from "./support/config";
|
||||
import { APP_TIMEZONE, INBOX_SUBJECTS, mailRow, seed } from "./support/config";
|
||||
import { rowAction } from "./support/rows";
|
||||
|
||||
/**
|
||||
@@ -86,7 +86,24 @@ test("a snoozed conversation shows its wake time and can be woken", async ({ pag
|
||||
// arrival time too and would pass with this fix reverted. Both clock
|
||||
// formats are accepted: which one renders is a user setting, and the claim
|
||||
// here is about WHICH TIME is shown rather than how it is written.
|
||||
const hour = new Date(wakeAt).getHours();
|
||||
// Read in the zone the PAGE renders in, not the one Node is running in.
|
||||
//
|
||||
// The row is formatted server-side in the user's zone, which falls back to
|
||||
// Europe/Berlin; `getHours()` answers in the runner's. Those are the same
|
||||
// thing on a developer's machine and two hours apart on a GitHub runner, so
|
||||
// this passed here and failed only on the tag: the mail came back at 18:00,
|
||||
// the row said 20:00, and the assertion reported the wrong TIME being shown
|
||||
// rather than the wrong CLOCK being read.
|
||||
//
|
||||
// See APP_TIMEZONE, and compose-scheduled-send's wallClockIn, which is the
|
||||
// same lesson learned in the same place.
|
||||
const hour = Number(
|
||||
new Intl.DateTimeFormat("en-GB", {
|
||||
timeZone: APP_TIMEZONE,
|
||||
hour: "2-digit",
|
||||
hour12: false,
|
||||
}).format(new Date(wakeAt)),
|
||||
) % 24;
|
||||
const shown = await row.innerText();
|
||||
|
||||
// `0?` because the hour is zero-padded on screen and not in a Date: at
|
||||
|
||||
@@ -59,6 +59,21 @@ const SLOT = WORKER_SLOT;
|
||||
* A side effect worth having: the suite becomes re-runnable against a warm
|
||||
* database, because nothing depends on a fixed address whose rows accumulate.
|
||||
*/
|
||||
/**
|
||||
* The zone the SERVER renders wall-clock times in.
|
||||
*
|
||||
* Mirrors `app.fallback_timezone` in config/services.yaml. The seeded users
|
||||
* leave `User::$timezone` null, so every time on a rendered page is formatted in
|
||||
* this zone regardless of where the browser or the runner think they are.
|
||||
*
|
||||
* A test that reads an hour off a page must therefore convert into THIS zone,
|
||||
* never with Node's `new Date().getHours()` — that is the runner's zone, which
|
||||
* is Europe/Berlin on a developer's machine and UTC on a GitHub runner. Tests
|
||||
* that get it wrong pass locally and fail only in CI, two hours out, looking
|
||||
* like a timing flake.
|
||||
*/
|
||||
export const APP_TIMEZONE = "Europe/Berlin";
|
||||
|
||||
export const TEST_USER = {
|
||||
email: `e2e-w${SLOT}@plmail.test`,
|
||||
password: process.env.APP_DEV_USER_PASSWORD ?? "e2e-password-change-me",
|
||||
|
||||
Reference in new issue
Block a user