e2e/clerk-navigation.spec.ts
Linked repository file from this build.
import type { Clerk } from "@clerk/shared/types";
import { expect, test } from "@playwright/test";
type ClerkWindow = Window & { Clerk?: Clerk };
test("Clerk avatar reserves its painted slot while the destination user button mounts", async ({
page,
request,
}) => {
test.setTimeout(60_000);
test.skip(process.env.QC_E2E_CLERK !== "1", "Requires the provisioned Clerk demo roster");
await page.goto("/dashboard");
const response = await request.post("/api/demo/view-as", { data: { staffId: "401" } });
expect(response.ok()).toBe(true);
const { ticket } = await response.json();
await page.evaluate((value) => sessionStorage.setItem("qccrm.demo-ticket", value), ticket);
await page.goto("/demo-switch");
await page.waitForURL("**/dashboard");
await expect(page.locator(".cl-userButtonTrigger")).toBeVisible();
// Make Clerk's independent mount deterministic; normal route data can finish first.
await page.evaluate(() => {
const clerk = (window as ClerkWindow).Clerk!;
const mount = clerk.mountUserButton.bind(clerk);
clerk.mountUserButton = (node, props) => {
setTimeout(() => {
if (node.isConnected) mount(node, props);
}, 750);
};
});
for (const layout of ["classic", "workbench", "desk"]) {
for (const path of ["/reviews", "/search"]) {
if (path === "/search")
await page.getByRole("textbox", { name: "Search reviews", exact: true }).press("Enter");
else await page.locator(`.app-nav a[href="${path}"]`).click();
await page.waitForURL((url) => url.pathname === path);
// Exercise layout geometry without changing the demo account's preferences.
await page.evaluate((value) => {
document.documentElement.dataset.layout = value;
document.documentElement.dataset.sidebar = "icons";
}, layout);
const fallback = page.getByRole("img", { name: "Loading account menu" });
await expect(fallback).toBeVisible();
const before = await fallback.boundingBox();
await expect(fallback.locator("img")).toBeVisible();
const blankFrames = await page.evaluate(
() =>
new Promise<number>((resolve) => {
const end = performance.now() + 1000;
let missing = 0;
const frame = () => {
const target = [...document.querySelectorAll(".account-profile-trigger")].find(
(element) =>
element.getClientRects().length &&
getComputedStyle(element).visibility !== "hidden",
);
if (!target) missing++;
if (performance.now() >= end) resolve(missing);
else requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
}),
);
expect(blankFrames, "account icon must remain painted throughout Clerk mounting").toBe(0);
const trigger = page.locator(".cl-userButtonTrigger");
await expect(trigger).toBeVisible();
await expect(fallback).toHaveCount(0);
const after = await trigger.boundingBox();
expect(after!.width).toBeCloseTo(before!.width, 0);
expect(after!.height).toBeCloseTo(before!.height, 0);
expect(after!.x).toBeCloseTo(before!.x, 0);
expect(after!.y).toBeCloseTo(before!.y, 0);
await trigger.click();
await expect(page.locator(".cl-userButtonPopoverCard")).toBeVisible();
await page.keyboard.press("Escape");
}
}
});