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 <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 18:24:27 +00:00
co-authored by Cursor
parent 58ae25444d
commit 9f676273df
+48 -16
View File
@@ -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<OverlaySample[]> {
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`);
}