Files
EpicNext-Cms/e2e/register.spec.ts
T
openhands ef6c671903
CI / check (push) Successful in 32s
CI / tests-imaging-browsers (push) Successful in 1m34s
CI / tests-integration (push) Successful in 1m59s
CI / tests-unit (push) Successful in 2m2s
CI / tests-quality (push) Successful in 1m1s
CI / tests-ui (push) Failing after 3m20s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
test(ci): deterministic deploy gate + advisory quality suite; sync README versions
The deploy gate ran e2e specs that depend on live content and data, so a missing
upload (the /help article images) or a legacy malformed avatar figure could turn
a safe code deploy red. Split the suites by what they actually guarantee:

- Blocking gate (`pnpm test:e2e`, `playwright.config.ts`): only checks the CMS
  can guarantee itself — anonymous redirects, register form fields, the captcha
  DOM contract and the cache-control headers (deterministic origin-direct).
- Advisory suite (`pnpm test:e2e:quality`, `playwright.quality.config.ts`):
  broken images, layout overflow and warm-page latency, which depend on live
  content/upload state and the host. It runs as a `continue-on-error` CI job
  against the public site so a real content problem stays visible without
  blocking the pipeline.

Also add `scripts/sync-readme-versions.mjs` (+ tests, docs and a CI check) so
the pinned versions in README.md can never drift from package.json / .nvmrc.
2026-10-15 16:26:47 +02:00

70 lines
2.5 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { expectNoApplicationError, isMaintenanceRedirect } from "./helpers";
// The register form is the gate for every new player, so it gets a deeper
// read-only pass than the generic public-page spec. It also pins the captcha
// contract that is configured through `CAPTCHA_PROVIDER` / `TURNSTILE_*` env
// vars (see `src/lib/services/captcha.ts`): whatever provider the running CMS
// resolves, the DOM must expose exactly one widget whose sitekey is non-empty.
const CAPTCHA_SELECTOR = ".cf-turnstile, .g-recaptcha, .h-captcha";
// Optional explicit expectation for a deploy, e.g. `E2E_EXPECT_CAPTCHA=turnstile`
// or `E2E_EXPECT_CAPTCHA=none`. Left unset, the spec only checks consistency.
const EXPECT = process.env.E2E_EXPECT_CAPTCHA?.trim();
test.describe("register", () => {
test.beforeEach(async ({ page }) => {
const response = await page.goto("/register");
test.skip(
isMaintenanceRedirect(page.url()) || response?.status() === 404,
"register is not reachable on this deploy",
);
});
test("renders every field a new player has to fill in", async ({ page }) => {
await expect(page.locator('input[name="username"]')).toBeVisible();
await expect(page.locator('input[name="mail"]')).toBeVisible();
await expect(page.locator('input[name="password"]').first()).toBeVisible();
await expect(
page.locator('input[name="password_confirmation"]'),
).toBeVisible();
await expect(page.locator('input[name="terms"]')).toBeVisible();
await expect(page.locator('button[type="submit"]').first()).toBeVisible();
await expectNoApplicationError(page);
});
test("exposes exactly one captcha widget with a site key", async ({
page,
}) => {
const widgets = page.locator(CAPTCHA_SELECTOR);
if (EXPECT === "none") {
await expect(widgets).toHaveCount(0);
return;
}
if (EXPECT === "turnstile") {
await expect(page.locator(".cf-turnstile")).toHaveCount(1);
}
const count = await widgets.count();
expect(
count,
`expected at most one captcha provider, found ${count}`,
).toBeLessThanOrEqual(1);
for (let index = 0; index < count; index += 1) {
const siteKey = await widgets.nth(index).getAttribute("data-sitekey");
expect(siteKey, "captcha widget without data-sitekey").toBeTruthy();
expect(siteKey?.trim().length).toBeGreaterThan(0);
}
if (count > 0 && (await page.locator(".cf-turnstile").count()) > 0) {
const script = page.locator(
'script[src*="challenges.cloudflare.com/turnstile"]',
);
await expect(script).toHaveCount(1);
}
});
});