From 9f676273dfc68da86acd68ef017a3504345cbca3 Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 13 Sep 2026 18:24:27 +0000 Subject: [PATCH] test(e2e): wait for overlay CSS transition to settle Poll pane document-block computed styles until two frames match. Pure requires transparent background and box-shadow none; manual/all require a real overlay mark without hard-coded theme colors. Co-authored-by: Cursor --- web/e2e/document-revision-runtime.spec.ts | 64 +++++++++++++++++------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index a7f8b5d0..ba6a2d99 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -436,11 +436,54 @@ function overlayPaneKey(samples: OverlaySample[]): string { .join(';'); } -function overlayPaneHasMark(samples: OverlaySample[]): boolean { - return samples.some((s) => s.overlayKind !== '' || (s.boxShadow !== '' && s.boxShadow !== 'none')); +function overlaySampleIsTransparent(s: OverlaySample): boolean { + const bg = s.backgroundColor.replace(/\s+/g, ''); + return (bg === 'transparent' || bg === 'rgba(0,0,0,0)') && (s.boxShadow === '' || s.boxShadow === 'none'); } -/** Sample every real pane block. Overlay styles live on the document-block wrapper itself. */ +function overlayPaneHasMark(samples: OverlaySample[]): boolean { + return samples.some((s) => s.overlayKind !== '' || !overlaySampleIsTransparent(s)); +} + +async function readOverlaySamples(page: Page): Promise { + return page.getByTestId('active-document-pane').getByTestId('document-block').evaluateAll((els) => + els.map((node) => { + const el = node as HTMLElement; + const s = window.getComputedStyle(el); + return { + backgroundColor: s.backgroundColor, + boxShadow: s.boxShadow, + className: el.className || '', + overlayKind: el.getAttribute('data-overlay-kind') || '', + blockId: el.getAttribute('data-block-id') || '', + }; + }), + ); +} + +/** Poll computed styles until two identical frames; do not sample the 200ms transition mid-frame. */ +async function waitForOverlayStylesSettled(page: Page, mode: 'pure' | 'manual' | 'all') { + let previous = ''; + await expect + .poll( + async () => { + const samples = await readOverlaySamples(page); + if (samples.length === 0) return 'empty'; + const key = overlayPaneKey(samples); + const stable = previous !== '' && previous === key; + previous = key; + if (mode === 'pure') { + return stable && !overlayPaneHasMark(samples) ? 'settled' : 'pending'; + } + const marked = samples.some((s) => s.overlayKind) && overlayPaneHasMark(samples); + return stable && marked ? 'settled' : 'pending'; + }, + { timeout: 15_000 }, + ) + .toBe('settled'); +} + +/** Sample every real pane block after overlay fetch + CSS transition settle. */ async function captureOverlayFingerprint(page: Page, mode: 'pure' | 'manual' | 'all') { const pane = page.getByTestId('active-document-pane'); const modeSelect = pane.locator('select[aria-label="变更显示模式"]'); @@ -463,20 +506,9 @@ async function captureOverlayFingerprint(page: Page, mode: 'pure' | 'manual' | ' } else { await expect(marked.first()).toBeVisible({ timeout: 15_000 }); } + await waitForOverlayStylesSettled(page, mode); - const samples = await blocks.evaluateAll((els) => - els.map((node) => { - const el = node as HTMLElement; - const s = window.getComputedStyle(el); - return { - backgroundColor: s.backgroundColor, - boxShadow: s.boxShadow, - className: el.className || '', - overlayKind: el.getAttribute('data-overlay-kind') || '', - blockId: el.getAttribute('data-block-id') || '', - }; - }), - ); + const samples = await readOverlaySamples(page); if (samples.length === 0) { fail(`UI overlay mode=${mode}: no document-block to sample computed style`); }