157 lines
7.4 KiB
TypeScript
157 lines
7.4 KiB
TypeScript
import { expect, test } from "./utils/test";
|
|
import { expectNoCriticalA11yViolations } from "./utils/axe";
|
|
import { expectAllPasswordRequirementsMet, expectPasswordRequirementsVisible, expectSomePasswordRequirementsMissing } from "./utils/auth";
|
|
import { expectNoHorizontalOverflow, narrowMobileViewport } from "./utils/mobile";
|
|
import { skipOrFailWhenE2eFixtureMissing } from "./utils/readiness";
|
|
|
|
test.describe("register page a11y", () => {
|
|
test("/register has no critical or serious axe violations", async ({ page }) => {
|
|
await page.goto("/register", { waitUntil: "domcontentloaded" });
|
|
|
|
if (/\/login(?:[?#].*)?$/u.test(page.url())) {
|
|
await expect(page.locator('input[type="email"], input[name="email"], input#email').first()).toBeVisible();
|
|
await expectNoCriticalA11yViolations(page);
|
|
return;
|
|
}
|
|
|
|
await expect(page.locator("body")).toBeVisible();
|
|
await expectNoCriticalA11yViolations(page);
|
|
});
|
|
|
|
test("/register announces password requirement status through accessible names", async ({ page }) => {
|
|
await page.goto("/register", { waitUntil: "domcontentloaded" });
|
|
|
|
if (/\/login(?:[?#].*)?$/u.test(page.url())) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration redirects to login in this environment");
|
|
}
|
|
|
|
const passwordInput = page.locator('input[type="password"], input[name="password"], input#password').first();
|
|
if ((await passwordInput.count()) === 0 || !(await passwordInput.isVisible())) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration password input is not rendered in this environment");
|
|
}
|
|
|
|
const requirements = page.locator('[aria-live="polite"]').first();
|
|
await expectPasswordRequirementsVisible(requirements);
|
|
|
|
await passwordInput.fill("short");
|
|
await expectSomePasswordRequirementsMissing(requirements);
|
|
|
|
await passwordInput.fill("LongEnough1!");
|
|
await expectAllPasswordRequirementsMet(requirements);
|
|
});
|
|
|
|
test("/register submit contract posts expected fields without creating an account", async ({ page }) => {
|
|
const redirectTo = "/app/dashboard";
|
|
await page.goto(`/register?redirect=${encodeURIComponent(redirectTo)}`, { waitUntil: "domcontentloaded" });
|
|
|
|
if (/\/login(?:[?#].*)?$/u.test(page.url())) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration redirects to login in this environment");
|
|
}
|
|
|
|
const form = page.locator("form").first();
|
|
const emailInput = form.locator('[data-ui-id="ui.auth.register.email"], input[type="email"], input[name="email"], input#email').first();
|
|
const passwordInput = form.locator('[data-ui-id="ui.auth.register.password"], input[type="password"], input[name="password"], input#password').first();
|
|
if ((await form.count()) === 0 || (await emailInput.count()) === 0 || (await passwordInput.count()) === 0) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration form is not rendered in this environment");
|
|
}
|
|
|
|
const email = `contract-${Date.now()}@example.test`;
|
|
const firstName = "E2E";
|
|
const lastName = "Contract";
|
|
const company = "E2E Contract";
|
|
await emailInput.fill(email);
|
|
await passwordInput.fill("LongEnough1!");
|
|
|
|
const companyInput = form.locator('[data-ui-id="ui.auth.register.company"], input[name="company"]').first();
|
|
const hasCompanyInput = (await companyInput.count()) > 0;
|
|
if (hasCompanyInput) await companyInput.fill(company);
|
|
|
|
const firstNameInput = form.locator('[data-ui-id="ui.auth.register.firstName"], input[name="first-name"]').first();
|
|
const lastNameInput = form.locator('[data-ui-id="ui.auth.register.lastName"], input[name="last-name"]').first();
|
|
const hasFirstNameInput = (await firstNameInput.count()) > 0;
|
|
const hasLastNameInput = (await lastNameInput.count()) > 0;
|
|
if (hasFirstNameInput) await firstNameInput.fill(firstName);
|
|
if (hasLastNameInput) await lastNameInput.fill(lastName);
|
|
|
|
const currentPathname = new URL(page.url()).pathname;
|
|
let resolveSubmitted!: (formData: URLSearchParams) => void;
|
|
const submitted = new Promise<URLSearchParams>((resolve) => {
|
|
resolveSubmitted = resolve;
|
|
});
|
|
await page.route("**/*", async (route) => {
|
|
const request = route.request();
|
|
const requestUrl = new URL(request.url());
|
|
// React Router single-fetch posts form actions to `${pathname}.data`, so
|
|
// match both the route path and its `.data` sibling.
|
|
const matchesRoute = requestUrl.pathname === currentPathname || requestUrl.pathname === `${currentPathname}.data`;
|
|
if (request.method() !== "POST" || !matchesRoute) {
|
|
await route.continue();
|
|
return;
|
|
}
|
|
|
|
const formData = new URLSearchParams(request.postData() ?? "");
|
|
resolveSubmitted(formData);
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "text/html",
|
|
body: "<!doctype html><title>Registration intercepted</title>",
|
|
});
|
|
});
|
|
|
|
await form.locator('button[type="submit"]').first().click();
|
|
const formData = await submitted;
|
|
|
|
expect(formData.get("csrf") ?? "").not.toBe("");
|
|
expect(formData.get("email")).toBe(email);
|
|
expect(formData.get("password")).toBe("LongEnough1!");
|
|
expect(formData.get("redirectTo")).toBe(redirectTo);
|
|
if (hasCompanyInput) expect(formData.get("company")).toBe(company);
|
|
if (hasFirstNameInput) expect(formData.get("first-name")).toBe(firstName);
|
|
if (hasLastNameInput) expect(formData.get("last-name")).toBe(lastName);
|
|
});
|
|
});
|
|
|
|
test.describe("register validation mobile a11y", () => {
|
|
test.use({ viewport: narrowMobileViewport, isMobile: true, hasTouch: true });
|
|
|
|
test("/register exposes validation feedback accessibly on mobile", async ({ page }) => {
|
|
await page.goto("/register", { waitUntil: "domcontentloaded" });
|
|
|
|
if (/\/login(?:[?#].*)?$/u.test(page.url())) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration redirects to login in this environment");
|
|
}
|
|
|
|
const emailInput = page.locator('input[type="email"], input[name="email"], input#email').first();
|
|
const passwordInput = page.locator('input[type="password"], input[name="password"], input#password').first();
|
|
if ((await emailInput.count()) === 0 || (await passwordInput.count()) === 0) {
|
|
skipOrFailWhenE2eFixtureMissing("Registration form is not rendered in this environment");
|
|
}
|
|
|
|
const companyInput = page.locator('input[name="company"]').first();
|
|
if ((await companyInput.count()) > 0) await companyInput.fill("E2E Validation");
|
|
const firstNameInput = page.locator('input[name="first-name"]').first();
|
|
if ((await firstNameInput.count()) > 0) await firstNameInput.fill("E2E");
|
|
const lastNameInput = page.locator('input[name="last-name"]').first();
|
|
if ((await lastNameInput.count()) > 0) await lastNameInput.fill("Validation");
|
|
|
|
await emailInput.fill("not-an-email");
|
|
await passwordInput.fill("short");
|
|
await page.locator('button[type="submit"]').first().click();
|
|
|
|
const formError = page.locator('#form-error-message [role="alert"]').first();
|
|
const nativeEmailInvalid = await emailInput.evaluate((input) => (input as HTMLInputElement).validationMessage.length > 0);
|
|
if ((await formError.count()) > 0 && (await formError.isVisible())) {
|
|
await expect(formError).toBeVisible();
|
|
} else {
|
|
expect(nativeEmailInvalid).toBe(true);
|
|
}
|
|
|
|
const requirements = page.locator('[aria-live="polite"]').first();
|
|
if ((await requirements.count()) > 0) {
|
|
await expectSomePasswordRequirementsMissing(requirements);
|
|
}
|
|
await expectNoHorizontalOverflow(page);
|
|
await expectNoCriticalA11yViolations(page);
|
|
});
|
|
});
|