e2e: accept Plex's session-less hand-off to app.plex.tv

The full-suite re-run after the reset fix came back "1 flaky": 12-crawl reached
plex.franksdank.cloud, the guard cleared, and Plex Web — signed out in the
qa-e2e profile — handed the browser to its own hosted sign-in surface, so the
hostname never settled on the vhost (60 s poll, Expected plex.franksdank.cloud /
Received app.plex.tv). settleThroughGuard now takes a HANDOFFS landing list per
vhost; only plex.franksdank.cloud gets one, every other host still has to land on
its own vhost, and the guard assertions (Anubis cleared, no authentik uidField,
status < 500) are unchanged. Worklog and E2E gotcha 6 carry both symptoms: the
ERR_ABORTED reset and the hand-off.
This commit is contained in:
Frank
2026-10-17 22:49:35 -07:00
parent d2587e1d29
commit fc099d9b3c
3 changed files with 80 additions and 11 deletions
+20 -4
View File
@@ -8,15 +8,31 @@ import { expect, test, type Page } from "@playwright/test";
const CHALLENGE = "Making sure you're not a bot";
async function settleThroughGuard(page: Page, targetHost: string): Promise<void> {
// Vendor hand-offs. An app may legitimately carry a session-less browser to its
// OWN hosted surface: Plex Web (plex.franksdank.cloud, served by our stack)
// bounces a signed-out visitor to app.plex.tv after it has already answered.
// That is the app working, not a guard-chain failure — the fleet contract this
// spec owns is still judged: the Anubis challenge cleared, no authentik
// uidField prompt appeared, and the app answered below 500. Any host NOT listed
// here still has to land on its own vhost.
const HANDOFFS: Record<string, string[]> = {
"plex.franksdank.cloud": ["app.plex.tv", "accounts.plex.tv", "clients.plex.tv"],
};
async function settleThroughGuard(
page: Page,
targetHost: string,
foreignHosts: string[] = [],
): Promise<void> {
// Each guarded vhost runs its own Anubis proof-of-work on first visit;
// wait for the challenge to clear before judging the destination.
await expect
.poll(async () => !(await page.locator("body").innerText()).includes(CHALLENGE), { timeout: 90_000 })
.toBe(true);
const landing = [targetHost, ...foreignHosts];
await expect
.poll(() => new URL(page.url()).hostname, { timeout: 60_000 })
.toBe(targetHost);
.poll(() => landing.includes(new URL(page.url()).hostname), { timeout: 60_000 })
.toBe(true);
expect(await page.locator('input[name="uidField"]').count()).toBe(0);
}
@@ -58,7 +74,7 @@ test("every user open link reaches its app", async ({ page }) => {
const target = new URL(link.href);
try {
await page.goto(link.href);
await settleThroughGuard(page, target.hostname);
await settleThroughGuard(page, target.hostname, HANDOFFS[target.hostname] ?? []);
const resp = await page.request.get(page.url());
if (resp.status() >= 500) {
failures.push(`${link.label}: ${page.url()} answered ${resp.status()}`);