import { expect, test } from "@playwright/test"; import { gotoLiveView, projectEmail, projectText, registerAndConfirm, selectOptionContaining, setRequestLocation, waitForLiveViewConnected, } from "./helpers"; test("a disconnected LiveView recovers without leaving a stale error", async ({ browser, request, }, testInfo) => { const user = await registerAndConfirm( browser, request, projectEmail("resilience", testInfo.project.name), "E2E Resilience", ); const title = projectText("Connection recovery request", testInfo.project.name); await gotoLiveView(user.page, "/requests/new"); await selectOptionContaining(user.page, "Category", "Medicine pickup"); await user.page.getByLabel("Medicine pickup status").selectOption("reserved"); await user.page.getByLabel("Short title").fill(title); await user.page .getByLabel("What help do you need?") .fill("A reserved medicine pickup used to verify visible connection recovery."); await user.page.getByLabel("Urgency").selectOption("now"); await user.page.getByRole("button", { name: "In 3 hours" }).click(); await setRequestLocation(user.page, { label: "Connection recovery area", mode: "hidden", }); await user.page.locator("#request-form input[type=checkbox]").last().check(); await user.page.getByRole("button", { name: "Publish request" }).click(); await expect(user.page.getByRole("heading", { name: title })).toBeVisible(); await waitForLiveViewConnected(user.page); const runtimeNode = await user.page .locator("#e2e-runtime-node") .getAttribute("data-node"); expect(runtimeNode).toBeTruthy(); const serverError = user.page.locator("#server-error"); await expect(serverError).toHaveAttribute("role", "alert"); await expect(serverError).toContainText("Attempting to reconnect"); await user.page.evaluate(() => { const state = { sawDisconnected: false }; ( window as typeof window & { __wnhRecoveryState?: { sawDisconnected: boolean }; } ).__wnhRecoveryState = state; window.addEventListener("phx:page-loading-start", (event) => { const detail = (event as CustomEvent<{ kind?: string }>).detail; if (detail?.kind === "error") state.sawDisconnected = true; }); }); const crash = await request.post("/__e2e__/crash-node", { data: { confirmation: "crash-exact-e2e-node", node: runtimeNode, }, }); expect(crash.status()).toBe(202); await expect .poll( () => user.page.evaluate( () => ( window as typeof window & { __wnhRecoveryState?: { sawDisconnected: boolean }; } ).__wnhRecoveryState?.sawDisconnected ?? false, ), { timeout: 30_000 }, ) .toBe(true); await waitForLiveViewConnected(user.page); await expect(serverError).toBeHidden(); await expect(user.page.locator("#client-error")).toBeHidden(); await expect(user.page.getByRole("heading", { name: title })).toBeVisible(); await user.context.close(); });