fix(test): null padDeletionToken before pad init to stop modal focus theft (#7643)

PR #7546 added a one-time pad-deletion-token modal that opens via the
clientVars handshake on creator sessions and synchronously focuses its
input through setTimeout(0). `goToNewPad`'s previous mitigation hid the
modal element after `waitForEditorReady`, but the editor iframe
attaches before clientVars arrives, so the hide runs against a still-
hidden modal, short-circuits, and the modal opens later mid-test —
stealing focus and dropping the next Enter / Tab. Visible on develop
in `enter.spec.ts:33` and `indentation.spec.ts:9` across all four
Playwright jobs (run 25214868650).

Intercept `clientVars` assignment via `page.addInitScript` and null out
`padDeletionToken` before `pad.ts`'s `showDeletionTokenModalIfPresent`
can read it, so the modal-show short-circuits at the source. The
deletion-token spec navigates inline with `page.goto` and does not
call this helper, so its modal still appears.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
John McLear 2026-05-01 21:24:23 +08:00 committed by GitHub
parent 4bda757304
commit d036cf0e70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -133,25 +133,33 @@ const waitForEditorReady = async (page: Page) => {
};
export const goToNewPad = async (page: Page) => {
// create a new pad before each test run
// Suppress the one-time pad-deletion-token modal in tests. The modal
// pops up on creator sessions via the clientVars handshake and steals
// focus through a setTimeout, which races with goToNewPad's
// waitForEditorReady — the editor iframe attaches before clientVars
// arrives, so any post-load DOM hide runs too early and the modal
// still opens mid-test, eating Enter / Tab presses (#7546 regression
// observed in enter.spec and indentation.spec). Intercept clientVars
// assignment instead and null out padDeletionToken before pad.ts can
// read it; that way the modal-show short-circuits at the source.
// Tests that need to interact with the modal navigate inline and do
// not call this helper.
await page.addInitScript(() => {
let stored: unknown;
Object.defineProperty(window, 'clientVars', {
configurable: true,
get() { return stored; },
set(v) {
if (v != null && typeof v === 'object') {
(v as {padDeletionToken?: string | null}).padDeletionToken = null;
}
stored = v;
},
});
});
const padId = "FRONTEND_TESTS"+randomUUID();
await page.goto('http://localhost:9001/p/'+padId);
await waitForEditorReady(page);
// Creator sessions see the one-time pad-deletion-token modal on first visit.
// Hide it directly instead of clicking the ack button — clicking the button
// transfers focus out of the pad iframe and breaks subsequent keyboard tests.
// Tests that need to interact with the modal should navigate to a new pad
// inline instead of using this helper.
await page.evaluate(() => {
const modal = document.getElementById('deletiontoken-modal');
if (modal == null || modal.hidden) return;
modal.hidden = true;
modal.classList.remove('popup-show');
const input = document.getElementById('deletiontoken-value') as HTMLInputElement | null;
if (input) input.value = '';
const w = window as unknown as {clientVars?: {padDeletionToken?: string | null}};
if (w.clientVars != null) w.clientVars.padDeletionToken = null;
});
return padId;
}