a58f42cf86
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.2 KiB
TypeScript
75 lines
3.2 KiB
TypeScript
// Shared E2E helpers — the sign-up journey (SD-0001 §5.1–§5.4) and products-page
|
|
// navigation (SD-0002 §5.2). Selectors are role/label-based against the real screens
|
|
// (Landing.tsx, SignIn.tsx, CreateStorefront.tsx, Admin.tsx, ProductsPage.tsx).
|
|
import { expect, type Page } from "@playwright/test";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
const LOG = join(__dirname, ".backend.log");
|
|
let seq = 0;
|
|
|
|
// The storefront name every helper-created merchant uses; tests assert against it.
|
|
export const STOREFRONT_NAME = "E2E Test Goods";
|
|
|
|
export function freshEmail(): string {
|
|
return `merchant${Date.now()}-${seq++}@example.com`;
|
|
}
|
|
|
|
// LogMailer's exact line (backend/app/platform/mailer.py):
|
|
// INFO:ecomm.mailer: LogMailer -> <to> | Your ecomm code: <6 digits>\n<body>
|
|
// The first 6-digit group after the marker is the code.
|
|
async function codeFor(email: string): Promise<string> {
|
|
for (let i = 0; i < 50; i++) {
|
|
const log = await readFile(LOG, "utf8").catch(() => "");
|
|
const at = log.lastIndexOf(`LogMailer -> ${email}`);
|
|
if (at >= 0) {
|
|
const m = log.slice(at).match(/\b(\d{6})\b/);
|
|
if (m) return m[1];
|
|
}
|
|
await new Promise((r) => setTimeout(r, 200));
|
|
}
|
|
throw new Error(`no code logged for ${email}`);
|
|
}
|
|
|
|
export async function signUpWithStorefront(page: Page, email = freshEmail()): Promise<string> {
|
|
// Landing → the sign-up door.
|
|
await page.goto("/");
|
|
await page.getByRole("button", { name: "Create your storefront →" }).click();
|
|
|
|
// Sign-in step 1: request the one-time code.
|
|
await page.getByLabel("Email").fill(email);
|
|
await page.getByRole("button", { name: "Send code" }).click();
|
|
|
|
// Sign-in step 2: read the code from the backend log and verify it.
|
|
await expect(page.getByRole("heading", { name: "Check your email" })).toBeVisible();
|
|
const code = await codeFor(email);
|
|
await page.getByLabel("One-time code").fill(code);
|
|
await page.getByRole("button", { name: "Continue" }).click();
|
|
|
|
// Create-storefront screen (a new account has none yet).
|
|
await expect(page.getByRole("heading", { name: "Create your storefront" })).toBeVisible();
|
|
await page.getByLabel("Storefront name").fill(STOREFRONT_NAME);
|
|
await page.getByRole("button", { name: "Create storefront", exact: true }).click();
|
|
|
|
// Admin shell: nav strip + the storefront identity in the topbar.
|
|
await expect(page.getByRole("navigation", { name: "Admin sections" })).toBeVisible();
|
|
await expect(page.locator(".storeid__name")).toHaveText(STOREFRONT_NAME);
|
|
return email;
|
|
}
|
|
|
|
export async function gotoProducts(page: Page) {
|
|
await page
|
|
.getByRole("navigation", { name: "Admin sections" })
|
|
.getByRole("link", { name: "Products" })
|
|
.click();
|
|
await expect(page.getByRole("heading", { level: 1, name: "Products" })).toBeVisible();
|
|
}
|
|
|
|
export async function uploadFixture(page: Page, fixture: string) {
|
|
// Two "Import products" links render on the empty products page (header + empty
|
|
// state) — same destination, so take the first.
|
|
await page.getByRole("link", { name: "Import products" }).first().click();
|
|
await expect(page.getByRole("heading", { name: "Import products" })).toBeVisible();
|
|
await page.locator('input[type="file"]').setInputFiles(join(__dirname, "fixtures", fixture));
|
|
}
|