refactor: organize E2E tests with page objects

This commit is contained in:
2026-10-07 21:13:40 +09:00
parent 1e03e8f4ff
commit 48ef396ec1
28 changed files with 1494 additions and 834 deletions
+54 -39
View File
@@ -1,83 +1,96 @@
import { openDatabase } from "../../src/features/storage/database.server";
import { expect, test } from "../fixtures";
const origin = "http://127.0.0.1:4173";
test("creates the owner once and enters onboarding with a real session", async ({
page,
context,
a11y,
authPage,
onboardingPage,
homePage,
}) => {
const db = openDatabase(process.env.TWITTER_LITE_E2E_DB_PATH ?? "");
db.$client.exec("DELETE FROM workspace_owner; DELETE FROM auth_throttle;");
db.$client.close();
await context.clearCookies();
await page.goto("/", { waitUntil: "networkidle" });
await homePage.goTo();
await page.waitForLoadState("networkidle");
await expect(page).toHaveURL(/\/setup$/);
expect((await a11y().analyze()).violations).toEqual([]);
await page.getByLabel("Your name", { exact: true }).fill("Yuta");
await page.getByLabel("Email address").fill("[email protected]");
await page.getByLabel("Password", { exact: true }).fill("E2E-only-passphrase-2026");
await page.getByLabel("Confirm password", { exact: true }).fill("E2E-only-passphrase-2026");
await page.getByLabel("Setup code").fill("isolated-e2e-setup-token-not-for-production");
await page.getByRole("button", { name: "Create account", exact: true }).click();
await authPage.createAccount({
name: "Yuta",
email: "[email protected]",
password: "E2E-only-passphrase-2026",
setupCode: "isolated-e2e-setup-token-not-for-production",
});
await expect(page).toHaveURL(/\/onboarding$/);
await page.waitForLoadState("networkidle");
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByRole("button", { name: "Open workspace" }).click();
await expect(page.getByRole("heading", { name: "Good morning, Yuta" })).toBeVisible();
await onboardingPage.complete();
await expect(homePage.greeting("Yuta")).toBeVisible();
});
test("requires login for documents, server functions, and live updates", async ({
page,
context,
authPage,
workspacePage,
deckPage,
journalPage,
baseURL,
}) => {
const serverRequest = page.waitForRequest((request) => request.url().includes("/_serverFn/"));
await page.goto("/deck");
await deckPage.goTo();
const serverUrl = (await serverRequest).url();
await context.clearCookies();
for (const path of ["/api/research/events", serverUrl]) {
const result = await context.request.get(path, {
headers: { Origin: origin, "Sec-Fetch-Site": "same-origin" },
headers: { Origin: new URL("/", baseURL).origin, "Sec-Fetch-Site": "same-origin" },
});
expect(result.status()).toBe(401);
}
await page.goto("/journal");
await journalPage.goTo();
await expect(page).toHaveURL(/\/login(?:\?|$)/);
await expect(page.getByRole("heading", { name: "Welcome back." })).toBeVisible();
await expect(page.getByRole("link", { name: "Journal", exact: true })).toHaveCount(0);
await expect(authPage.welcome).toBeVisible();
await expect(workspacePage.navigationLink("Journal")).toHaveCount(0);
});
test("signs in and revokes the session on sign out", async ({ page, context, a11y }) => {
test("signs in and revokes the session on sign out", async ({
page,
context,
a11y,
authPage,
workspacePage,
connectedAccounts,
}) => {
await context.clearCookies();
await page.goto("/login", { waitUntil: "networkidle" });
await authPage.goTo();
expect((await a11y().analyze()).violations).toEqual([]);
await page.getByLabel("Email address").fill("[email protected]");
await page.getByLabel("Password", { exact: true }).fill("Wrong-passphrase-2026");
await page.getByRole("button", { name: "Sign in", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("Invalid email or password.");
await page.getByLabel("Password", { exact: true }).fill("E2E-only-passphrase-2026");
await page.getByRole("button", { name: "Sign in", exact: true }).click();
await expect(page).toHaveURL(`${origin}/`);
await authPage.signIn("[email protected]", "Wrong-passphrase-2026");
await expect(authPage.error).toContainText("Invalid email or password.");
await authPage.password.fill("E2E-only-passphrase-2026");
await authPage.signInButton.click();
await expect(page).toHaveURL("/");
await page.waitForLoadState("networkidle");
const token = (await context.cookies()).find((cookie) => cookie.name === "workspace_session");
expect(token?.httpOnly).toBe(true);
expect(token?.sameSite).toBe("Lax");
await page.getByRole("button", { name: "Manage connected accounts" }).click();
await page.getByRole("button", { name: "Sign out", exact: true }).click();
await connectedAccounts.open();
await connectedAccounts.signOut.click();
await expect(page).toHaveURL(/\/login(?:\?|$)/);
const replay = await context.request.get("/api/auth", {
headers: { Cookie: `workspace_session=${token?.value}` },
});
expect((await replay.json()).owner).toBeNull();
await page.goBack();
await expect(page.getByRole("link", { name: "Home", exact: true })).toHaveCount(0);
await expect(workspacePage.navigationLink("Home")).toHaveCount(0);
});
test("requires onboarding and remembers its completion and name", async ({
page,
request,
a11y,
onboardingPage,
homePage,
}) => {
const db = openDatabase(process.env.TWITTER_LITE_E2E_DB_PATH ?? "");
db.$client
@@ -85,17 +98,17 @@ test("requires onboarding and remembers its completion and name", async ({
.run();
try {
expect((await request.get("/api/research/events")).status()).toBe(403);
await page.goto("/", { waitUntil: "networkidle" });
await homePage.goTo();
await page.waitForLoadState("networkidle");
await expect(page).toHaveURL(/\/onboarding$/);
expect((await a11y().analyze()).violations).toEqual([]);
await page.getByLabel("What should we call you?").fill("Yuta Test");
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByRole("button", { name: "Open workspace" }).click();
await expect(page.getByRole("heading", { name: "Good morning, Yuta Test" })).toBeVisible();
await onboardingPage.name.fill("Yuta Test");
await onboardingPage.complete();
await expect(homePage.greeting("Yuta Test")).toBeVisible();
await page.reload();
await expect(page.getByRole("heading", { name: "Good morning, Yuta Test" })).toBeVisible();
await page.goto("/onboarding");
await expect(page).toHaveURL(`${origin}/`);
await expect(homePage.greeting("Yuta Test")).toBeVisible();
await onboardingPage.goTo();
await expect(page).toHaveURL("/");
} finally {
db.$client
.prepare("UPDATE workspace_owner SET name = ?, onboarding_completed_at = ? WHERE id = 1")
@@ -108,6 +121,8 @@ test("rejects cross-origin login and further account registration", async ({
request,
page,
context,
authPage,
baseURL,
}) => {
expect(
(
@@ -118,7 +133,7 @@ test("rejects cross-origin login and further account registration", async ({
).status(),
).toBe(403);
const result = await request.post("/api/auth", {
headers: { Origin: origin },
headers: { Origin: new URL("/", baseURL).origin },
data: {
action: "setup",
email: "[email protected]",
@@ -129,6 +144,6 @@ test("rejects cross-origin login and further account registration", async ({
});
expect(result.status()).toBe(409);
await context.clearCookies();
await page.goto("/setup");
await authPage.goTo("setup");
await expect(page).toHaveURL(/\/login(?:\?|$)/);
});