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.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/** Diag temporaire : navigation partielle vers l'éditeur, erreurs par phase */
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
function alpineErrs(msgs) {
|
||||
return msgs.filter((m) => m.includes('Alpine Expression Error') || m.includes('PAGEERROR'));
|
||||
}
|
||||
|
||||
async function partialClick(page, pid) {
|
||||
await page.evaluate((id) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = '/pages/' + id;
|
||||
a.textContent = 'x';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
}, pid);
|
||||
}
|
||||
|
||||
test('diag phases', async ({ page }) => {
|
||||
test.setTimeout(90000);
|
||||
const msgs = [];
|
||||
page.on('console', (m) => msgs.push(m.type() + ' | ' + m.text().slice(0, 160).replace(/\s+/g, ' ')));
|
||||
page.on('pageerror', (e) => msgs.push('PAGEERROR | ' + String(e).slice(0, 200)));
|
||||
|
||||
await login(page);
|
||||
const pid = await page.evaluate(async () => {
|
||||
const csrf = (document.cookie.match(/csrf_token=([^;]+)/) || [])[1] || '';
|
||||
const r = await fetch('/api/local-workspace/items', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
|
||||
body: JSON.stringify({ name: 'Diag2', type: 'page' }),
|
||||
});
|
||||
const d = await r.json();
|
||||
return d.id || (d.item && d.item.id);
|
||||
});
|
||||
|
||||
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(800);
|
||||
msgs.length = 0;
|
||||
|
||||
// PHASE A : navigation partielle (clic intercepte par fdLoad)
|
||||
await partialClick(page, pid);
|
||||
await page.waitForTimeout(4000);
|
||||
const stackA = await page.evaluate(() => {
|
||||
const el = document.querySelector('[x-data="editorState()"]');
|
||||
const st = el && el._x_dataStack;
|
||||
return { stack: st ? st.map((o) => (o ? Object.keys(o).length : -1)) : 'none', refreshed: !!(el && el.__fdRefreshed) };
|
||||
});
|
||||
const trace = await page.evaluate(() => window.__fdTrace || []);
|
||||
stackA.trace = trace;
|
||||
console.log('PHASE A (partielle): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length, JSON.stringify(stackA));
|
||||
console.log('TRACE:', JSON.stringify(trace));
|
||||
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' A>', m));
|
||||
msgs.filter((m) => !m.includes('Alpine Expression')).slice(0, 5).forEach((m) => console.log(' A-other>', m));
|
||||
|
||||
// A2 : les erreurs s'arretent-elles apres le montage (fenetre transitoire) ?
|
||||
const a1 = alpineErrs(msgs).length;
|
||||
await page.waitForTimeout(3000);
|
||||
const a2 = alpineErrs(msgs).length;
|
||||
const ui = await page.evaluate(() => ({
|
||||
title: !!document.getElementById('_titleEl'),
|
||||
blocks: document.querySelectorAll('[data-bid]').length,
|
||||
editorVisible: !!document.querySelector('.page-editor-wrapper, .editor-wrap, [x-data^="editorState"]'),
|
||||
}));
|
||||
console.log('PHASE A2: erreurs a t0=', a1, '-> apres +3s=', a2, 'UI=', JSON.stringify(ui));
|
||||
|
||||
// PHASE B : rechargement complet de la meme page
|
||||
msgs.length = 0;
|
||||
await page.goto(`${FD_BASE}/pages/${pid}`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(3000);
|
||||
console.log('PHASE B (complet): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length);
|
||||
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' B>', m));
|
||||
|
||||
// PHASE C : deuxieme navigation partielle vers la meme page
|
||||
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(600);
|
||||
msgs.length = 0;
|
||||
await partialClick(page, pid);
|
||||
await page.waitForTimeout(3500);
|
||||
console.log('PHASE C (2e partielle): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length);
|
||||
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' C>', m));
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/** 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));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user