From f56342479b8e288580acaab63375e40562a150e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 20:39:20 +0300 Subject: [PATCH] test(dialog): wait for the room dialog by condition, not by two frames (#712) smoke_dialog_polish_603 switches the card language on every step and then opened the room dialog with a private _openRoomEdit call followed by updateComplete and two frames. de and fr (and the editor's settings dictionaries) are lazy chunks: until they arrive the card's language gate keeps the previous frame, inert and aria-busy, so the dialog is not in the tree yet. When the chunk took longer than two frames (CI, 1 of 2 runs) the next line read querySelector of null. Delaying the de/fr chunks by 400 ms in the harness reproduces the TypeError every time. The step now waits until the new language is painted (no aria-busy, lang equals the requested code), enters Plan with __hpTest.setMode, opens the dialog with __hpTest.openRoomEdit (the real gear; the facade waits for [data-kind="room"]) and waits until the dialog's basics card is laid out. The covered private calls _setMode and _openRoomEdit are gone; every check and its oracle is unchanged. Issue: #712 User-Visible: no Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd --- demo/smoke_dialog_polish_603.mjs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/demo/smoke_dialog_polish_603.mjs b/demo/smoke_dialog_polish_603.mjs index 15b98ffd..9824d177 100644 --- a/demo/smoke_dialog_polish_603.mjs +++ b/demo/smoke_dialog_polish_603.mjs @@ -20,20 +20,41 @@ for (const dpr of [1, 2]) { const key = `${language}_${theme}_dpr${dpr}_w${width}`; const result = await page.evaluate(async ({ language }) => { const c = window.__card; + const hp = window.__hpTest; const root = () => c.shadowRoot || c.renderRoot; const settle = async () => { c.requestUpdate(); await c.updateComplete; await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))); }; + const waitFor = async (what, predicate) => { + const deadline = performance.now() + 5000; + for (;;) { + const value = predicate(); + if (value) return value; + if (performance.now() > deadline) throw new Error(`smoke_dialog_polish_603 (${language}): ${what}`); + await new Promise((resolve) => setTimeout(resolve, 16)); + } + }; + const laidOut = (node) => { + const box = node?.getBoundingClientRect(); + return !!box && box.width > 0 && box.height > 0; + }; c._config = { ...(c._config || {}), language }; - c._setMode('plan'); + // A lazy locale keeps the previous frame, inert and aria-busy, until + // its dictionaries arrive: nothing opened meanwhile reaches the DOM + // (#712). Wait for the new language to be painted, then open the + // dialog through its gear and wait for its content to be laid out. await settle(); + await waitFor('язык не отрисован', + () => !c.hasAttribute('aria-busy') && c.getAttribute('lang') === language); + await hp.setMode('plan'); c._roomDialogCancel(); await settle(); - c._openRoomEdit(c._curSpaceCfg.rooms[0]); - await settle(); - const dialog = root().querySelector('hp-dialog[data-kind="room"]'); - const basics = dialog.querySelector('[data-card="basics"]'); + const dialog = await hp.openRoomEdit(c._curSpaceCfg.rooms[0].id); + const basics = await waitFor('диалог комнаты не отрисован', () => { + const card = dialog.isConnected && dialog.querySelector('[data-card="basics"]'); + return laidOut(card) ? card : null; + }); const roomFields = !!basics && !basics.querySelector('.hpf-head') && !!basics.querySelector('#room-name') && !!basics.querySelector('#room-area') && !!basics.querySelector('#room-area')?.closest('.hpf-field')?.querySelector('hp-help')