From 0fb9329916ec259718c5878be54120d7c04e117a Mon Sep 17 00:00:00 2001 From: yaroslav k Date: Sun, 16 Aug 2026 12:43:55 +0300 Subject: [PATCH] e2e: character page spec, verified against a live server - player card shows the mock character (name, class, level, XP) - add a spell via a vacant slot, undo with Ctrl+Z, redo with Ctrl+Y - remove a spell empties its slot back to vacant - a prepared spell survives a reload (autosave round trip) - attune an item, reload keeps it, clear empties the slot --- end2end/tests/character.spec.ts | 100 ++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 end2end/tests/character.spec.ts diff --git a/end2end/tests/character.spec.ts b/end2end/tests/character.spec.ts new file mode 100644 index 0000000..ee16f91 --- /dev/null +++ b/end2end/tests/character.spec.ts @@ -0,0 +1,100 @@ +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("player card shows the campaign character", async ({ page }) => { + const errors = trackErrors(page); + await page.goto(`${BASE}/`); + + await expect(page.locator(".player-name")).toHaveText("Абоба «Такелажник» Амогус"); + await expect(page.locator(".player-class")).toHaveText("Чувствительный молодой человек"); + await expect(page.locator(".player-level")).toHaveText("уровень 8"); + await expect(page.locator(".player-xp")).toHaveText("3231 XP"); + + expect(errors).toEqual([]); + }); + + 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([]); + }); +});