/** * Project management E2E tests: * 1. Rename a project inline * 2. Delete a project * 3. Create a folder inside a project * 4. File upload type validation (wrong type rejected) * * Prerequisite: auth.setup.ts has already saved the session to e2e/.auth/user.json. * All tests run with the authenticated storageState configured in playwright.config.ts — * no test.use override is needed here. * * Each test creates its own uniquely-named project so tests are fully isolated. */ import { test, expect } from "@playwright/test"; import path from "path"; const PDF_FIXTURE = path.join(__dirname, "fixtures/test.pdf"); // ─── Shared helper ──────────────────────────────────────────────────────────── /** * Creates a new project via the "New project" modal and waits until * NewProjectModal's onCreated handler redirects to /projects/. * * Pass `filePath` to also upload a document during creation. This matters for * the folder test: ProjectPage only renders the document tree (and therefore * the root "Add Subfolder" input) when the project is NOT empty — an empty * project shows the "Drop PDF or DOCX files here" placeholder instead, which * has no folder input. */ async function createProject( page: import("@playwright/test").Page, projectName: string, filePath?: string, ) { /* Creation is a navigation + modal wizard + (optionally) a file upload; the per-test `{ timeout }` option passed to test() is silently ignored by Playwright (that object only accepts tag/annotation), so raise the budget here, where the slow work happens, for every caller. */ test.setTimeout(60_000); await page.goto("/projects"); await expect(page).toHaveURL(/\/projects/, { timeout: 10_000 }); /* The Plus icon button in the header has aria-label="New project" */ const createBtn = page.getByRole("button", { name: "New project" }); await expect(createBtn).toBeVisible({ timeout: 10_000 }); await createBtn.click(); const nameInput = page.getByPlaceholder("Project name"); await expect(nameInput).toBeVisible({ timeout: 5_000 }); await nameInput.fill(projectName); /* NewProjectModal is a two-step wizard: "Details" (name / CM number / practice / colleagues) then "Add Documents". Only the second step has a submit button — the first step's primary action is a plain "Next". */ await page.getByRole("button", { name: "Next", exact: true }).click(); if (filePath) { /* On the documents step the footer "Upload" button opens a hidden file input, and its label gains a "(n)" count once files are attached. */ const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByRole("button", { name: /^Upload/ }).click(); (await fileChooserPromise).setFiles(filePath); await expect( page.getByRole("button", { name: /^Upload \(1\)/ }), ).toBeVisible({ timeout: 5_000 }); } /* Submit — NewProjectModal's onCreated calls router.push(`/projects/${id}`). The PDF upload runs (awaited) inside handleSubmit before onCreated fires, so allow extra time for navigation when a file is attached. (The modal's FileDirectory used to fan out a getProject() request per existing project on open, which could overwhelm the local Supabase gateway and required settle-waits plus a submit-retry loop here. The directory now loads via one batched listProjects?include=documents request, so a single submit is reliable.) The documents step's primary action submits the form (its label flips to "Creating…" while in flight, so match on the submit type instead). */ const navTimeout = filePath ? 30_000 : 15_000; await page.locator('button[type="submit"]').click(); await page.waitForURL(/\/projects\/.+/, { timeout: navTimeout }); } /** * Navigate to the projects list and return the table row for `projectName`. * Rows are
; the sidebar "Recent Projects" renders the same * name as a