who_need_help/e2e/tests/auth-settings.spec.ts

87 lines
3.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
captureBrowserFailures,
clickUntilVisible,
gotoWithTransientRetry,
isTransientNetworkError,
loginWithPassword,
projectEmail,
registerAndConfirm,
waitForApplicationEmailLink,
} from "./helpers";
test("registration, email confirmation, password, and email change work end to end", async ({
browser,
request,
}, testInfo) => {
const originalEmail = projectEmail("auth-user", testInfo.project.name);
const changedEmail = projectEmail("auth-user-changed", testInfo.project.name);
const password = "E2E public password 2026!";
const user = await registerAndConfirm(
browser,
request,
originalEmail,
"E2E Auth User",
);
const recoverableOneTimeNavigations = new Set<string>();
const assertBrowserClean = captureBrowserFailures(user.page, {
recoverableOneTimeNavigations,
});
await user.page.goto("/users/settings");
const passwordForm = user.page.locator("#update_password");
await passwordForm.getByLabel("New password", { exact: true }).fill(password);
await passwordForm.getByLabel("Confirm new password", { exact: true }).fill(password);
await clickUntilVisible(
passwordForm.getByRole("button", { name: "Save Password" }),
user.page.getByText("Password updated successfully."),
);
const emailForm = user.page.locator("#update_email");
await emailForm.getByLabel("Email").fill(changedEmail);
await clickUntilVisible(
emailForm.getByRole("button", { name: "Change Email" }),
user.page.getByText("A link to confirm your email change has been sent"),
);
const emailChangeLink = await waitForApplicationEmailLink(
request,
changedEmail,
"/users/settings/confirm-email",
);
recoverableOneTimeNavigations.add(emailChangeLink);
try {
await user.page.goto(emailChangeLink);
await expect(user.page.locator("#email-change-fragment-form")).toBeVisible();
await user.page
.getByRole("button", { name: "Confirm email change" })
.click();
await expect(user.page.getByText("Email changed successfully.")).toBeVisible();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!isTransientNetworkError(message)) {
throw error;
}
// The server may commit and consume the single-use token before a
// transport reset reaches the browser. Never replay that URL: verify the
// committed account state through an idempotent page instead.
await gotoWithTransientRetry(user.page, "/users/settings");
await expect(user.page.locator("#update_email").getByLabel("Email")).toHaveValue(
changedEmail,
);
}
await user.context.close();
const passwordLogin = await loginWithPassword(browser, changedEmail, password);
const assertPasswordLoginClean = captureBrowserFailures(passwordLogin.page);
await expect(passwordLogin.page.getByText(changedEmail, { exact: true })).toBeVisible();
assertBrowserClean();
assertPasswordLoginClean();
await passwordLogin.context.close();
});