import { test, expect, type Page } from "@playwright/test"; const BASE = process.env.BASE_URL ?? "http://localhost:3000"; function trackErrors(page: Page): string[] { const errors: string[] = []; page.on("console", (msg) => { if (msg.type() === "error") errors.push(msg.text()); }); page.on("pageerror", (err) => { errors.push(String(err)); }); return errors; } test.describe("character page", () => { test("add a spell, undo it, redo it", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); const vacant = page.locator(".prepared-slot.vacant").first(); await vacant.locator("input").fill("Щит веры"); await vacant.locator("button[title='Записать заклинание']").click(); await expect(page.locator(".spell-name", { hasText: "Щит веры" })).toBeVisible(); // Ctrl+Z on the page (focus is on the button, not an input) undoes // the add; Ctrl+Y redoes it. await page.keyboard.press("Control+z"); await expect(page.locator(".spell-name", { hasText: "Щит веры" })).toHaveCount(0); await page.keyboard.press("Control+y"); await expect(page.locator(".spell-name", { hasText: "Щит веры" })).toBeVisible(); expect(errors).toEqual([]); }); test("remove a spell empties its slot", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); const fireball = page.locator(".prepared-slot.occupied", { hasText: "Огненный шар" }); await fireball.locator("button[title='Убрать заклинание']").click(); await expect(page.locator(".spell-name", { hasText: "Огненный шар" })).toHaveCount(0); await expect(page.locator(".prepared-slot.vacant")).toHaveCount(3); expect(errors).toEqual([]); }); test("a prepared spell survives a reload", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); const vacant = page.locator(".prepared-slot.vacant").first(); await vacant.locator("input").fill("Щит веры"); await vacant.locator("button[title='Записать заклинание']").click(); await expect(page.locator(".spell-name", { hasText: "Щит веры" })).toBeVisible(); // Autosave is debounced by 500 ms; give it time before reloading. await page.waitForTimeout(700); await page.reload(); await expect(page.locator(".spell-name", { hasText: "Щит веры" })).toBeVisible(); expect(errors).toEqual([]); }); test("attune an item, clear it, reload keeps the attunement", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); const slot = page.locator(".attunement-slot").first(); await slot.locator("input").fill("Посох погоды"); await slot.locator("button[title='Привязать предмет']").click(); await expect(page.locator(".attuned-item")).toHaveText("Посох погоды"); // Reload: the attunement persists. await page.waitForTimeout(700); await page.reload(); await expect(page.locator(".attuned-item")).toHaveText("Посох погоды"); // Clear it. await page.locator("button[title='Снять привязку']").click(); await expect(page.locator(".attuned-item")).toHaveCount(0); expect(errors).toEqual([]); }); test("more attunement slots can be added for custom campaigns", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); await expect(page.locator(".attunement-slot")).toHaveCount(3); await page.locator("button[title='Добавить слот привязки']").click(); await expect(page.locator(".attunement-slot")).toHaveCount(4); const fourth = page.locator(".attunement-slot").nth(3); await fourth.locator("input").fill("Волшебный рог"); await fourth.locator("button[title='Привязать предмет']").click(); await expect(page.locator(".attuned-item")).toHaveText("Волшебный рог"); expect(errors).toEqual([]); }); test("armor class is set manually and persists", async ({ page }) => { const errors = trackErrors(page); await page.goto(`${BASE}/`); const ac = page.locator("#ac"); await expect(ac).toHaveValue("16"); await ac.fill("18"); // Autosave is debounced by 500 ms; give it time before reloading. await page.waitForTimeout(700); await page.reload(); await expect(page.locator("#ac")).toHaveValue("18"); expect(errors).toEqual([]); }); });