mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
test(e2e): accept beforeunload prompt in shared dialog fallback (#9122)
The USE_REMOTE crash-resume spec reloads Client B mid-sync. While isSyncInProgress is set, startup.service's beforeunload handler calls preventDefault(), so Chrome raises a beforeunload prompt (the preceding button click supplies the user activation it requires). Playwright auto-accepts such prompts only when no 'dialog' listener is registered — but installDevErrorDialogHandler registers one on every page and early- returned on beforeunload, leaving the prompt unanswered. The navigation then never starts and reload() times out (observed on SuperSync shard 6/6; the earlier load→domcontentloaded de-flake could not help because navigation is blocked before any lifecycle event). Accept beforeunload in the shared fallback, restoring Playwright's default; other dialog types keep flowing to spec-specific handlers. Corrects the two crash-resume comments that encoded the wrong theory.
This commit is contained in:
parent
c18055a341
commit
238b91aa59
2 changed files with 23 additions and 9 deletions
|
|
@ -97,11 +97,8 @@ test.describe('@supersync USE_REMOTE interrupted rebuild recovery', () => {
|
||||||
rebuildCommittedLog: REBUILD_COMMITTED_LOG,
|
rebuildCommittedLog: REBUILD_COMMITTED_LOG,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Wait only for `domcontentloaded`, not the default `load`: an active
|
// `reload()` preserves sessionStorage (unlike close()+newPage()), which
|
||||||
// SuperSync WebSocket/sync connection can keep the page "loading" so the
|
// this test needs; `waitForAppReady` below is the real readiness gate.
|
||||||
// `load` event never fires and `reload()` times out (flaky). `reload()`
|
|
||||||
// preserves sessionStorage (unlike close()+newPage()), which this test
|
|
||||||
// needs, and `waitForAppReady` below is the real readiness gate.
|
|
||||||
await clientB.page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 });
|
await clientB.page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||||
await waitForAppReady(clientB.page);
|
await waitForAppReady(clientB.page);
|
||||||
|
|
||||||
|
|
@ -136,10 +133,12 @@ test.describe('@supersync USE_REMOTE interrupted rebuild recovery', () => {
|
||||||
|
|
||||||
await clientB.sync.syncImportUseRemoteBtn.click();
|
await clientB.sync.syncImportUseRemoteBtn.click();
|
||||||
await crashObserved;
|
await crashObserved;
|
||||||
// domcontentloaded (not the default `load`) — see the note on the first
|
// This reload races the crashed sync's unwind: while `isSyncInProgress` is
|
||||||
// reload above: an active sync connection can block `load` and hang
|
// still set, startup.service's beforeunload handler prevents the unload and
|
||||||
// `reload()`. sessionStorage survives the reload; the assertion below
|
// Chrome prompts (the click above supplies the required user activation).
|
||||||
// depends on it.
|
// installDevErrorDialogHandler accepts that prompt — without it the
|
||||||
|
// navigation never starts and this hangs. sessionStorage survives the
|
||||||
|
// reload; the assertion below depends on it.
|
||||||
await clientB.page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 });
|
await clientB.page.reload({ waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||||
await waitForAppReady(clientB.page);
|
await waitForAppReady(clientB.page);
|
||||||
expect(
|
expect(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,21 @@ export const attachPageErrorCollector = (
|
||||||
// explicitly, or it will be vulnerable to the same hang.
|
// explicitly, or it will be vulnerable to the same hang.
|
||||||
export const installDevErrorDialogHandler = (page: Page, label: string): void => {
|
export const installDevErrorDialogHandler = (page: Page, label: string): void => {
|
||||||
page.on('dialog', async (dialog) => {
|
page.on('dialog', async (dialog) => {
|
||||||
|
// Registering ANY 'dialog' listener switches off Playwright's built-in
|
||||||
|
// handling (accept for beforeunload, dismiss for everything else). This
|
||||||
|
// fallback is installed on every page, so it must restore that default for
|
||||||
|
// beforeunload: startup.service raises one whenever a sync is in flight, and
|
||||||
|
// an unanswered prompt blocks the navigation before it starts — reload()
|
||||||
|
// then times out on any waitUntil, however early.
|
||||||
|
if (dialog.type() === 'beforeunload') {
|
||||||
|
try {
|
||||||
|
await dialog.accept();
|
||||||
|
} catch {
|
||||||
|
// Already handled by another listener — ignore.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const message = dialog.message();
|
const message = dialog.message();
|
||||||
const isDevErrorAlert = dialog.type() === 'alert' && message.startsWith('devERR:');
|
const isDevErrorAlert = dialog.type() === 'alert' && message.startsWith('devERR:');
|
||||||
const isDevErrorConfirm =
|
const isDevErrorConfirm =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue