// Rôle : couvre les parcours navigateur principaux des toolboxes locales. /* global window, document, navigator, CustomEvent, FileSystemDirectoryHandle */ import { expect, test } from "@playwright/test"; import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { resetSokkoStorage } from "./helpers/storage.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturePath = path.join(__dirname, "fixtures", "toolbox-export.json"); const site = JSON.parse(readFileSync(path.join(__dirname, "..", "website", "public", "data", "site.json"), "utf8")); async function collectBrowserErrors(page) { const errors = []; page.on("pageerror", (error) => errors.push(`pageerror: ${error.stack || error.message}`)); page.on("console", (message) => { if (message.type() === "error") errors.push(`console: ${message.text()}`); }); return errors; } async function createToolbox(page, name) { await page.goto("/#/toolboxes"); await expect(page.getByRole("main")).toBeVisible(); await page.getByRole("button", { name: "Nouvelle toolbox" }).click(); const dialog = page.getByRole("dialog", { name: "Nouvelle toolbox" }); await expect(dialog).toBeVisible(); await dialog.getByLabel("Nom").fill(name); await dialog.getByRole("button", { name: "Créer" }).click(); await expect(page.getByRole("heading", { name })).toBeVisible(); } async function addTool(page, name) { await page.getByRole("button", { name: "Ajouter un outil", exact: true }).click(); const addDialog = page.getByRole("dialog", { name: "Ajouter un outil" }); await expect(addDialog).toBeVisible(); await addDialog.getByRole("button", { name }).click(); } function moduleByType(page, type) { return page.locator(`[data-module-type='${type}']`); } function tableCell(table, address) { return table.getByRole("textbox", { name: `Cellule ${address}`, exact: true }); } test.beforeEach(async ({ page }) => { await resetSokkoStorage(page); page.browserErrors = await collectBrowserErrors(page); }); test.afterEach(async ({ page }) => { expect(page.browserErrors, page.browserErrors.join("\n")).toEqual([]); }); test("creates a toolbox and lazy-loads an added tool", async ({ page }) => { await createToolbox(page, "E2E Toolbox"); await expect(page.getByRole("heading", { name: "E2E Toolbox" })).toBeVisible(); await expect(page.locator("[data-module-type='memos']")).toBeVisible(); await expect(page.locator("[data-module-type='links']")).toBeVisible(); await addTool(page, "Arbre d'objectifs"); await expect(page.locator("[data-module-type='goalTree']")).toBeVisible(); }); test("add-tool modal explains every tool without overflowing compact desktop widths", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 720 }); await createToolbox(page, "Catalogue des outils"); await page.getByRole("button", { name: "Ajouter un outil", exact: true }).click(); const dialog = page.getByRole("dialog", { name: "Ajouter un outil", exact: true }); const cards = dialog.locator(".tool-add-card"); const descriptions = Object.entries(site.toolboxes.addToolDescriptions); await expect(cards).toHaveCount(descriptions.length); for (const [index, [, description]] of descriptions.entries()) { await expect(dialog.getByText(description, { exact: true })).toBeVisible(); await expect(cards.nth(index).locator("strong")).not.toHaveCSS("text-overflow", "ellipsis"); } for (const [width, expectedColumns] of [[768, 4], [960, 5], [1024, 5], [1280, 5]]) { await page.setViewportSize({ width, height: 720 }); await expect(dialog).toBeVisible(); const layout = await dialog.evaluate((element) => { const rect = element.getBoundingClientRect(); const grid = element.querySelector(".tool-add-grid"); return { left: rect.left, right: rect.right, height: rect.height, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, columns: window.getComputedStyle(grid).gridTemplateColumns.split(" ").length, overflowY: window.getComputedStyle(element).overflowY, scrollbarColor: window.getComputedStyle(element).scrollbarColor, scrollbarWidth: window.getComputedStyle(element).scrollbarWidth, webkitScrollbarWidth: window.getComputedStyle(element, "::-webkit-scrollbar").width, scrolls: element.scrollHeight > element.clientHeight, pageFits: document.documentElement.scrollWidth <= window.innerWidth }; }); expect(layout.left).toBeGreaterThanOrEqual(0); expect(layout.right).toBeLessThanOrEqual(layout.viewportWidth); expect(layout.right - layout.left).toBeGreaterThanOrEqual(Math.min(900, width - 32) - 2); expect(layout.height).toBeLessThanOrEqual(layout.viewportHeight); expect(layout.columns).toBe(expectedColumns); expect(layout.overflowY).toBe("auto"); expect(layout.scrollbarColor).toContain("88, 55, 174"); expect(layout.scrollbarWidth).toBe("thin"); expect(layout.webkitScrollbarWidth).toBe("6px"); expect(layout.scrolls).toBe(true); expect(layout.pageFits).toBe(true); } const categorySections = dialog.locator(".tool-add-category-section"); await expect(categorySections).toHaveCount(5); await expect(categorySections.locator("h3")).toHaveText(Object.values(site.library.categories)); await expect(dialog.locator("[aria-pressed]")).toHaveCount(0); await dialog.getByRole("button", { name: "Fermer", exact: true }).click(); await page.getByRole("button", { name: "Afficher l'ajout rapide", exact: true }).click(); const quickImage = page.getByRole("button", { name: "Ajouter Images", exact: true }); await quickImage.focus(); await expect(page.getByRole("tooltip", { name: "Images", exact: true })).toBeVisible(); }); test("keeps an uncommitted memo through unrelated app updates and saves multiline text", async ({ page }) => { await createToolbox(page, "Memo draft toolbox"); const memos = moduleByType(page, "memos"); const editor = memos.getByRole("textbox", { name: "Modifier le mémo" }).first(); await editor.fill("Premiere ligne\nDeuxieme ligne"); await page.evaluate(() => window.dispatchEvent(new CustomEvent("sokkog:notify", { detail: { message: "Memo draft notification" } }))); await expect(page.getByText("Memo draft notification", { exact: true })).toBeVisible(); await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne"); await editor.press("End"); await editor.press("Enter"); await editor.pressSequentially("Troisieme ligne"); await editor.blur(); await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne"); await page.reload(); await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne"); await editor.fill(" Premiere ligne\nDeuxieme ligne\nTroisieme ligne "); await editor.blur(); await expect(editor).toHaveValue("Premiere ligne\nDeuxieme ligne\nTroisieme ligne"); await editor.fill(" "); await editor.blur(); await expect(memos.locator(".memo-card")).toHaveCount(0); }); test("creates, edits and deletes a weekly calendar event", async ({ page }) => { await createToolbox(page, "Calendar E2E Toolbox"); await addTool(page, "Calendrier"); const calendar = page.locator("[data-module-type='calendar']"); await expect(calendar).toBeVisible(); await calendar.getByRole("button", { name: "Hebdomadaire" }).click(); await expect(calendar.locator(".calendar-week-grid")).toBeVisible(); await calendar.getByRole("button", { name: /Afficher l'ajout de Calendrier/ }).click(); await calendar.getByLabel("Titre").fill("Raid E2E"); await calendar.getByRole("textbox", { name: "Début" }).fill("18:00"); await calendar.getByRole("textbox", { name: "Fin" }).fill("20:00"); await calendar.getByRole("button", { name: "Ajouter" }).click(); await expect(calendar.getByRole("button", { name: /Raid E2E/ })).toBeVisible(); await calendar.getByRole("button", { name: /Raid E2E/ }).click(); await calendar.getByLabel("Titre").fill("Raid E2E modifié"); await calendar.getByRole("button", { name: "Enregistrer" }).click(); await expect(calendar.getByRole("button", { name: /Raid E2E modifié/ })).toBeVisible(); await expect(calendar.getByRole("button", { name: /Raid E2E$/ })).toHaveCount(0); await calendar.getByRole("button", { name: /Raid E2E modifié/ }).click(); await calendar.getByRole("button", { name: "Supprimer" }).click(); await expect(calendar.getByRole("button", { name: /Raid E2E modifié/ })).toHaveCount(0); }); test("updates weekly and monthly calendar drafts when extending a selection", async ({ page }) => { await createToolbox(page, "Calendar selection E2E"); await addTool(page, "Calendrier"); const weekly = moduleByType(page, "calendar").first(); await weekly.getByRole("button", { name: "Hebdomadaire" }).click(); await weekly.getByRole("button", { name: /Afficher l'ajout de Calendrier/ }).click(); const slots = weekly.locator(".calendar-day-column").first().locator(".calendar-slot"); await slots.nth(21).dispatchEvent("pointerdown", { buttons: 1 }); await slots.nth(18).dispatchEvent("pointerover", { buttons: 1 }); await slots.nth(18).dispatchEvent("pointerup", { buttons: 0 }); await expect(weekly.getByRole("textbox", { name: "Début" })).toHaveValue("09:00"); await expect(weekly.getByRole("textbox", { name: "Fin" })).toHaveValue("11:00"); await addTool(page, "Calendrier"); const monthly = moduleByType(page, "calendar").last(); await monthly.getByRole("button", { name: "Mensuel" }).click(); await monthly.getByRole("button", { name: /Afficher l'ajout de Calendrier/ }).click(); const days = monthly.locator(".calendar-month-cell:not(.is-outside)"); await days.nth(4).dispatchEvent("pointerdown", { buttons: 1 }); await days.nth(2).dispatchEvent("pointerover", { buttons: 1 }); await expect(monthly.locator(".calendar-month-cell.is-selected")).toHaveCount(3); await days.nth(2).dispatchEvent("pointerup", { buttons: 0 }); await expect(monthly.locator(".calendar-event-form")).toBeVisible(); await expect(monthly.locator(".calendar-month-cell.is-form-selected")).toHaveCount(3); }); test("creates a goal tree checklist with more than three visible lines", async ({ page }) => { await createToolbox(page, "Goal Tree E2E Toolbox"); await addTool(page, "Arbre d'objectifs"); const goalTree = page.locator("[data-module-type='goalTree']"); await expect(goalTree).toBeVisible(); await goalTree.getByRole("button", { name: /Afficher l'ajout de Arbre d'objectifs/ }).click(); await goalTree.getByRole("button", { name: "Ajouter un objectif a la racine" }).click(); const editor = goalTree.getByRole("region", { name: "Édition de l'arbre d'objectifs" }); await expect(editor).toBeVisible(); await editor.getByLabel("Titre de l'objectif").fill("Farm E2E"); await editor.getByLabel("Type de l'objectif").selectOption("checklist"); await editor.getByRole("tab", { name: "Contenu" }).click(); for (const index of [1, 2, 3, 4, 5]) { await editor.getByRole("button", { name: "Ajouter une ligne" }).click(); await editor.getByLabel("Libellé").nth(index - 1).fill(`Objectif ligne ${index}`); } const previewRows = goalTree.locator(".goal-tree-node-content-row"); await expect(previewRows).toHaveCount(5); await expect(previewRows.filter({ hasText: "Objectif ligne 1" })).toBeVisible(); await expect(previewRows.filter({ hasText: "Objectif ligne 5" })).toBeVisible(); }); test("loads randomizer tabs after module split", async ({ page }) => { await createToolbox(page, "Randomizer E2E Toolbox"); await addTool(page, "Randomizer"); const randomizer = moduleByType(page, "randomizer"); await expect(randomizer).toBeVisible(); await expect(randomizer.getByRole("tab", { name: "Dé" })).toBeVisible(); await expect(randomizer.getByRole("button", { name: "Lancer" })).toBeVisible(); await randomizer.getByRole("tab", { name: "Roue" }).click(); await expect(randomizer.getByLabel("Liste de valeurs")).toBeVisible(); await randomizer.getByLabel("Liste de valeurs").fill("Rouge\nBleu"); await randomizer.getByRole("button", { name: "Ajouter" }).click(); await expect(randomizer.locator(".randomizer-wheel-value-row .nds-input").first()).toHaveValue("Rouge"); await randomizer.getByRole("tab", { name: "Équipes" }).click(); await expect(randomizer.getByLabel("Liste de noms")).toBeVisible(); await expect(randomizer.getByRole("button", { name: "Créer les équipes" })).toBeVisible(); await randomizer.getByRole("combobox", { name: "Répartition", exact: true }).selectOption("count"); const count = randomizer.getByRole("spinbutton", { name: "Nombre", exact: true }); await count.fill("5"); await count.blur(); await expect(count).toHaveValue("5"); await randomizer.getByRole("spinbutton", { name: "Équipes", exact: true }).fill("6"); await expect(count).toHaveValue("6"); await count.fill(""); await count.blur(); await expect(count).toHaveValue("6"); await randomizer.getByRole("tab", { name: "Dé", exact: true }).click(); await randomizer.getByRole("tab", { name: "Équipes", exact: true }).click(); await expect(count).toHaveValue("6"); }); test("creates a game-linked toolbox from the drawer", async ({ page }) => { await page.goto("/#/games/mhwilds"); await expect(page.getByRole("heading", { name: /Monster Hunter.*Wilds/ })).toBeVisible(); await page.getByRole("tab", { name: "Toolbox", exact: true }).click(); const drawer = page.locator("#toolbox-drawer .drawer-panel"); await expect(drawer).toBeVisible(); await drawer.getByRole("button", { name: "Créer et associer" }).click(); const dialog = page.getByRole("dialog", { name: "Créer et associer une toolbox" }); await expect(dialog.getByLabel("Nom")).toHaveValue(/Monster Hunter.*Wilds/); await dialog.getByRole("button", { name: "Créer" }).click(); await expect(drawer.getByRole("heading", { name: /Monster Hunter.*Wilds/ })).toBeVisible(); await expect(drawer.locator("[data-module-type='memos']")).toBeVisible(); await expect(drawer.locator("[data-module-type='links']")).toBeVisible(); await expect(page.getByRole("tab", { name: "Toolbox", exact: true })).toHaveAttribute("aria-selected", "true"); }); test("links an available toolbox directly from the empty game drawer", async ({ page }) => { await createToolbox(page, "Toolbox déjà liée"); await page.goto("/#/games/mhwilds"); await page.getByRole("tab", { name: "Toolbox", exact: true }).click(); const drawer = page.locator("#toolbox-drawer .drawer-panel"); await expect(drawer.getByRole("heading", { name: "Ou associer une toolbox déjà existante" })).toBeVisible(); await drawer.getByLabel("Toolbox disponible").selectOption({ label: "Toolbox déjà liée" }); await drawer.getByRole("button", { name: "Associer", exact: true }).click(); await expect(drawer.getByRole("heading", { name: "Toolbox déjà liée", exact: true })).toBeVisible(); await createToolbox(page, "Toolbox libre"); await page.goto("/#/games/diablo4"); await page.getByRole("tab", { name: "Toolbox", exact: true }).click(); await expect(drawer.getByLabel("Toolbox disponible").getByRole("option", { name: "Toolbox déjà liée" })).toHaveCount(0); await drawer.getByLabel("Toolbox disponible").selectOption({ label: "Toolbox libre" }); await drawer.getByRole("button", { name: "Associer", exact: true }).click(); await expect(drawer.getByRole("heading", { name: "Toolbox libre", exact: true })).toBeVisible(); await expect(drawer.getByRole("heading", { name: "Ou associer une toolbox déjà existante" })).toHaveCount(0); }); test("imports and exports a toolbox payload", async ({ page }) => { await page.goto("/#/toolboxes"); await expect(page.getByRole("main")).toBeVisible(); await page.getByText("Importer une toolbox").locator("input[type='file']").setInputFiles(fixturePath); const importedToolboxLink = page.getByRole("link", { name: "Ouvrir Fixture Toolbox" }).first(); await expect(importedToolboxLink).toBeVisible(); await importedToolboxLink.click(); await expect(page.getByRole("heading", { name: "Fixture Toolbox" })).toBeVisible(); const importedNotepad = page.locator("[data-module-type='notepad']"); await expect(importedNotepad).toBeVisible(); await expect(importedNotepad.getByRole("heading", { name: "Fixture Notes" })).toBeVisible(); await expect(importedNotepad.getByText("Imported fixture note")).toBeVisible(); await page.goto("/#/toolboxes"); const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: "Exporter Fixture Toolbox" }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe("fixture-toolbox.sokkog.json"); }); test("opens the local automatic backup settings modal", async ({ page }) => { await page.goto("/#/toolboxes"); await expect(page.getByRole("button", { name: "Sauvegarde automatique", exact: true })).toBeVisible(); await page.getByRole("button", { name: "Sauvegarde automatique", exact: true }).click(); const dialog = page.getByRole("dialog", { name: "Sauvegarde automatique" }); await expect(dialog).toBeVisible(); expect((await dialog.boundingBox()).width).toBeGreaterThan(700); expect(await dialog.evaluate((element) => window.getComputedStyle(element).scrollbarColor)).toContain("88, 55, 174"); await expect(dialog.getByText("Cette fonctionnalité n’est pas disponible dans tous les navigateurs.")).toBeVisible(); await expect(dialog.getByText("Opera GX", { exact: true })).toBeVisible(); await expect(dialog.getByText("Comment fonctionne la sauvegarde", { exact: true })).toBeVisible(); await expect(dialog.getByText("la popup système ne peut apparaître qu’après un clic de votre part.")).toBeVisible(); await expect(dialog.getByText("10 versions sont conservées.", { exact: true })).toBeVisible(); await expect(dialog.getByText("Dossier configuré :", { exact: true })).toBeVisible(); await expect(dialog.getByText("Aucun dossier", { exact: true })).toBeVisible(); await expect(dialog.getByRole("checkbox")).toBeVisible(); await dialog.getByRole("button", { name: "Fermer", exact: true }).last().click(); await expect(dialog).toHaveCount(0); }); test("offers an actionable backup alert without requesting permission at startup", async ({ page }) => { await page.evaluate(async () => { const db = await import("/src/utils/indexedDbStorage.js"); const directoryHandle = await navigator.storage.getDirectory(); await db.setSetting("autoBackup", { enabled: true, directoryHandle, lastBackup: "", lastBackupSignature: "", hasUnsavedChanges: false }); }); await page.addInitScript(() => { window.backupPermissionRequests = 0; window.backupPermissionGranted = false; window.backupReminderVisibleAtPermissionRequest = true; Object.defineProperty(FileSystemDirectoryHandle.prototype, "queryPermission", { configurable: true, value: async () => window.backupPermissionGranted ? "granted" : "prompt" }); Object.defineProperty(FileSystemDirectoryHandle.prototype, "requestPermission", { configurable: true, value: async () => { window.backupPermissionRequests += 1; window.backupReminderVisibleAtPermissionRequest = document.body.textContent.includes("Des modifications ont été détectées dans vos toolboxes."); window.backupPermissionGranted = true; return "granted"; } }); }); await page.reload(); await page.goto("/#/toolboxes"); await expect(page.getByText("Des modifications ont été détectées dans vos toolboxes.")).toHaveCount(0); await createToolbox(page, "Toolbox à sauvegarder"); const reminder = page.getByRole("status").filter({ hasText: "Des modifications ont été détectées dans vos toolboxes." }); await expect(reminder).toBeVisible(); await expect(page.getByRole("dialog", { name: "Sauvegarde automatique" })).toHaveCount(0); expect(await page.evaluate(() => window.backupPermissionRequests)).toBe(0); await reminder.getByRole("button", { name: "Sauvegarder", exact: true }).click(); await expect(reminder).toHaveCount(0); expect(await page.evaluate(() => window.backupPermissionRequests)).toBe(1); expect(await page.evaluate(() => window.backupReminderVisibleAtPermissionRequest)).toBe(false); await expect.poll(async () => page.evaluate(async () => { const db = await import("/src/utils/indexedDbStorage.js"); return (await db.getSetting("autoBackup", {})).hasUnsavedChanges; })).toBe(false); }); test("backs up silently when folder access is still granted", async ({ page }) => { await page.evaluate(async () => { const db = await import("/src/utils/indexedDbStorage.js"); const directoryHandle = await navigator.storage.getDirectory(); for await (const [name, handle] of directoryHandle.entries()) { if (handle.kind === "file" && /^sokkog-global-\d{4}-\d{2}-\d{2}\.json$/.test(name)) await directoryHandle.removeEntry(name); } await db.setSetting("autoBackup", { enabled: true, directoryHandle, lastBackup: "", lastBackupSignature: "", hasUnsavedChanges: false }); }); await page.addInitScript(() => { window.backupPermissionRequests = 0; Object.defineProperty(FileSystemDirectoryHandle.prototype, "queryPermission", { configurable: true, value: async () => "granted" }); Object.defineProperty(FileSystemDirectoryHandle.prototype, "requestPermission", { configurable: true, value: async () => { window.backupPermissionRequests += 1; return "granted"; } }); }); await page.reload(); await expect.poll(async () => page.evaluate(async () => { const directoryHandle = await navigator.storage.getDirectory(); for await (const [name, handle] of directoryHandle.entries()) { if (handle.kind === "file" && /^sokkog-global-\d{4}-\d{2}-\d{2}\.json$/.test(name)) return true; } return false; })).toBe(true); await expect(page.getByText("Des modifications ont été détectées dans vos toolboxes.")).toHaveCount(0); expect(await page.evaluate(() => window.backupPermissionRequests)).toBe(0); }); test("edits a table with formulas and persists values", async ({ page }) => { await createToolbox(page, "Table E2E Toolbox"); await addTool(page, "Tableau"); const table = moduleByType(page, "table"); await expect(table).toBeVisible(); await table.getByRole("button", { name: "Ajouter une ligne" }).click(); await table.getByRole("button", { name: "Ajouter une colonne" }).click(); await tableCell(table, "A1").dblclick(); await tableCell(table, "A1").fill("12"); await tableCell(table, "B1").dblclick(); await tableCell(table, "B1").fill("3"); await tableCell(table, "C1").dblclick(); await tableCell(table, "C1").fill("=A1*B1"); await tableCell(table, "C1").press("Enter"); await expect(tableCell(table, "C1")).toHaveValue("36"); await page.reload(); await expect(tableCell(moduleByType(page, "table"), "A1")).toHaveValue("12"); await expect(tableCell(moduleByType(page, "table"), "C1")).toHaveValue("36"); }); test("creates edits and deletes a combo", async ({ page }) => { await createToolbox(page, "Combos E2E Toolbox"); await addTool(page, "Combos"); const combos = moduleByType(page, "combos"); await expect(combos).toBeVisible(); await combos.getByRole("button", { name: /Afficher l'ajout de Combos/ }).click(); await combos.getByPlaceholder("Nom du combo").fill("Combo E2E"); await combos.getByRole("button", { name: "←" }).click(); await combos.getByRole("button", { name: "□" }).click(); await combos.getByRole("button", { name: "Ajouter le combo" }).click(); await expect(combos.locator(".combo-card .combo-input-token")).toHaveCount(2); await combos.getByRole("button", { name: "Modifier le combo Combo E2E" }).click(); await combos.getByPlaceholder("Nom du combo").fill("Combo E2E modifié"); await combos.getByRole("button", { name: "Enregistrer" }).click(); await expect(combos.getByRole("button", { name: "Modifier le combo Combo E2E modifié" })).toBeVisible(); await combos.getByRole("button", { name: "Supprimer le combo Combo E2E modifié" }).click(); await expect(combos.getByRole("button", { name: "Modifier le combo Combo E2E modifié" })).toHaveCount(0); }); test("creates edits summarizes and deletes equipment", async ({ page }) => { await createToolbox(page, "Equipment E2E Toolbox"); await addTool(page, "Planificateur d'équipements"); const equipmentPlanner = moduleByType(page, "equipmentPlanner"); await expect(equipmentPlanner).toBeVisible(); await equipmentPlanner.getByRole("button", { name: /Afficher l'ajout de Planificateur d'équipements/ }).click(); await equipmentPlanner.getByPlaceholder("Nouvel équipement").fill("Épée E2E"); await equipmentPlanner.getByRole("button", { name: "Ajouter" }).click(); await expect(equipmentPlanner.getByText("Épée E2E")).toBeVisible(); await expect(equipmentPlanner.getByText("1 équipement actif")).toBeVisible(); await equipmentPlanner.locator(".equipment-planner-row-edit-button").click(); await equipmentPlanner.getByPlaceholder("Catégorie").fill("Stats"); await equipmentPlanner.getByPlaceholder("Nom").fill("Attaque"); await equipmentPlanner.getByPlaceholder("Valeur").fill("12"); await equipmentPlanner.locator(".equipment-planner-trait-form").getByRole("button", { name: "Ajouter" }).click(); await expect(equipmentPlanner.getByText("Attaque").first()).toBeVisible(); await expect(equipmentPlanner.getByText("12").first()).toBeVisible(); await page.reload(); const persistedPlanner = moduleByType(page, "equipmentPlanner"); await expect(persistedPlanner.getByText("Épée E2E")).toBeVisible(); await expect(persistedPlanner.getByText("Attaque").first()).toBeVisible(); await persistedPlanner.getByRole("button", { name: "Supprimer Épée E2E" }).click(); await expect(persistedPlanner.getByText("Épée E2E")).toHaveCount(0); }); test("edits rich notepad content and persists it", async ({ page }) => { await createToolbox(page, "Notepad E2E Toolbox"); await addTool(page, "Bloc Note"); const notepad = moduleByType(page, "notepad"); await expect(notepad).toBeVisible(); const editor = notepad.getByRole("textbox"); await editor.click(); await page.keyboard.press("Control+A"); await page.keyboard.press("Backspace"); await notepad.locator(".notepad-toolbar-button[title='Titre']").click(); await page.keyboard.type("Note E2E importante"); await expect(editor).toContainText("Note E2E importante"); await expect(notepad.getByRole("heading", { name: "Note E2E importante" })).toBeVisible(); await page.waitForTimeout(500); await page.reload(); const persistedEditor = moduleByType(page, "notepad").getByRole("textbox"); await expect(persistedEditor).toContainText("Note E2E importante"); await expect(moduleByType(page, "notepad").getByRole("heading", { name: "Note E2E importante" })).toBeVisible(); }); test("creates resets and deletes a countdown timer", async ({ page }) => { await createToolbox(page, "Timer E2E Toolbox"); await addTool(page, "Timer"); const timer = moduleByType(page, "timer"); await expect(timer).toBeVisible(); await timer.getByRole("tab", { name: "Compte à rebours" }).click(); await timer.getByLabel("Libellé").fill("Boss E2E"); const durationParts = timer.getByRole("group", { name: "Format heure minute seconde" }).first().locator("input"); await durationParts.nth(1).fill("01"); await timer.getByRole("button", { name: "Ajouter" }).click(); const countdown = timer.locator(".timer-list-item").filter({ hasText: "Boss E2E" }); await expect(countdown).toBeVisible(); await countdown.locator(".timer-inline-reset").click(); await expect(countdown).toBeVisible(); await countdown.getByRole("button", { name: "Supprimer" }).click(); await expect(countdown).toHaveCount(0); });