Files
flowdeck/e2e/regression_partial_nav.spec.js
T
bruno afbc236cc2
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m57s
FlowDeck CI / test (push) Successful in 15m16s
fix: navigation partielle Alpine sans course + scripts de page idempotents (v7.45.2)
- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé
  uniquement), démontage unique quand tous les <script src> ont exécuté
  (load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) —
  fini les cascades « Undefined variable » (éditeur) et « reading 'has' »
  ($store.fdCtx) : la zone s'initialisait avant composants et stores.
- 5 scripts de page gardés contre la ré-exécution (SyntaxError
  « Identifier 'LW' has already been declared » à la 2e visite
  partielle) : local_workspace, board, settings, database_table,
  page_editor_realtime.
- if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init
  (déjà passé sur swap) : 8 scripts + 7 templates inline.
- app.css : purge des 7 blocs @font-face Inter orphelins (fichiers
  inexistants → 302 HTML → « Failed to decode downloaded font »).
- Gates : e2e/regression_editor_mount + e2e/regression_partial_nav
  (7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke
  = 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK ·
  OpenAPI 7.45.2.
2026-10-02 21:29:46 -04:00

91 lines
4.2 KiB
JavaScript

/** Sweep diag : nav partielle vs complet sur N pages — erreurs + état fdCtx/appState */
const { test } = require('@playwright/test');
const FD_BASE = process.env.FD_BASE_URL || 'http://localhost:8080';
const USER = process.env.FD_USER || '[email protected]';
const PASS = process.env.FD_PASS || 'e2e-secret-123';
async function login(page) {
await page.goto(`${FD_BASE}/auth/login?provider=local`, { waitUntil: 'domcontentloaded' });
await page.fill('#email', USER);
await page.fill('#password', PASS);
await page.click('.btn-primary');
const ok = await page.waitForURL('**/workspaces', { timeout: 8000 }).then(() => true).catch(() => false);
if (!ok) {
await page.request.post(`${FD_BASE}/auth/register`, { data: { email: USER, password: PASS, name: 'E2E' } });
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForURL('**/workspaces', { timeout: 15000 });
}
}
const PAGES = ['/workspaces', '/library', '/local-workspace', '/settings', '/trash', '/accounts', '/import'];
test('sweep', async ({ page }) => {
test.setTimeout(180000);
const msgs = [];
page.on('console', (m) => msgs.push(m.type() + ' | ' + m.text().slice(0, 200).replace(/\s+/g, ' ')));
page.on('pageerror', (e) => msgs.push('PAGEERROR | ' + String(e).slice(0, 160)));
await login(page);
for (const path of PAGES) {
// baseline : chargement complet
msgs.length = 0;
await page.goto(FD_BASE + path, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2200);
const fullErrs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught'));
const stFull = await page.evaluate(() => ({
fdCtx: (() => { try { return typeof Alpine.store('fdCtx'); } catch (e) { return 'ERR'; } })(),
fdCtxHas: (() => { try { var s = Alpine.store('fdCtx'); return s ? typeof s.has + '/' + typeof s.avail : 'no store'; } catch (e) { return 'ERR'; } })(),
appState: typeof window.appState,
}));
// nav partielle : depuis /workspaces
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(700);
msgs.length = 0;
await page.evaluate((p) => {
const a = document.createElement('a');
a.href = p;
a.textContent = 'x';
document.body.appendChild(a);
a.click();
}, path);
await page.waitForTimeout(2500);
const partErrs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught'));
// 2e visite partielle dans LE MEME document (declencheur du SyntaxError
// « Identifier ... has already been declared » sur script sans garde)
await page.evaluate(() => {
const a = document.createElement('a');
a.href = '/workspaces';
a.textContent = 'x';
document.body.appendChild(a);
a.click();
});
await page.waitForTimeout(900);
msgs.length = 0;
await page.evaluate((p) => {
const a = document.createElement('a');
a.href = p;
a.textContent = 'x';
document.body.appendChild(a);
a.click();
}, path);
await page.waitForTimeout(2500);
const part2Errs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught') || m.includes('already been declared'));
const stPart = await page.evaluate(() => ({
fdCtx: (() => { try { return typeof Alpine.store('fdCtx'); } catch (e) { return 'ERR'; } })(),
fdCtxHas: (() => { try { var s = Alpine.store('fdCtx'); return s ? typeof s.has + '/' + typeof s.avail : 'no store'; } catch (e) { return 'ERR'; } })(),
appState: typeof window.appState,
lwGuard: !!window.__fdLocalWorkspaceScriptsLoaded,
}));
console.log(`PAGE ${path}`);
console.log(` complet : errs=${fullErrs.length} ${JSON.stringify(stFull)}`);
fullErrs.slice(0, 2).forEach((m) => console.log(' F>', m));
console.log(` 1ere partiel : errs=${partErrs.length} ${JSON.stringify(stPart)}`);
partErrs.slice(0, 3).forEach((m) => console.log(' P>', m));
console.log(` 2e partiel : errs=${part2Errs.length}`);
part2Errs.slice(0, 3).forEach((m) => console.log(' P2>', m));
}
});