/** * Playwright helpers that drive the live-mode bar UI exactly the way a user * would: pick an element, configure, Go, cycle, accept. * * Selector strategy: live-browser.js uses deterministic ids (`impeccable-live-*`) * for the global bar, per-element bar, action picker, and params panel. Buttons * inside the per-element bar are matched by visible text or unicode glyph * (`← / →`, `✓ Accept`, `✕`), or by aria-label for icon-only buttons (the * configure submit button). All selectors below come from * skill/scripts/live-browser.js — keep this file in sync if * the bar's text content changes. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; const BAR_ID = '#impeccable-live-bar'; const GLOBAL_BAR_ID = '#impeccable-live-global-bar'; const PICKER_ID = '#impeccable-live-picker'; const EDIT_BADGE_ID = '#impeccable-live-edit-badge'; const PENDING_DOCK_ID = '#impeccable-live-pending-dock'; // The configure-row submit button is icon-only; its accessible name is the // only stable handle (see buildConfigureSubmitButton in live-browser.js). const GO_BUTTON_ARIA_LABEL = 'Generate variants'; const STEER_CHAT_ID = '#impeccable-live-page-chat'; const STEER_INPUT_ID = '#impeccable-live-page-chat-input'; const PICK_TOGGLE = '#impeccable-live-pick-toggle'; // Alias kept so references introduced via origin/main (PICK_TOGGLE_ID) // continue to resolve to the same selector as the older PICK_TOGGLE name. const PICK_TOGGLE_ID = PICK_TOGGLE; const INSERT_TOGGLE = '#impeccable-live-insert-toggle'; const DETECT_TOGGLE = '#impeccable-live-detect-toggle'; const DETECT_BADGE = '#impeccable-live-detect-badge'; const DESIGN_TOGGLE = '#impeccable-live-design-toggle'; const DESIGN_HOST = '#impeccable-live-design-host'; const EXIT_BUTTON = '#impeccable-live-exit'; const INSERT_INPUT_ID = '#impeccable-live-insert-input'; const INSERT_CREATE_ID = '#impeccable-live-insert-create'; const ANNOTATION_ID = '#impeccable-live-annot'; const ANNOTATION_PINS_ID = '#impeccable-live-annot-pins'; const ANNOTATION_CLEAR_ID = '#impeccable-live-annot-clear'; /** * Wait for the live handshake to complete: * - window.__IMPECCABLE_LIVE_INIT__ set * - global bar mounted * - SSE connection established (state transitioned to PICKING) * * Times out generously since some frameworks delay first render. */ export async function waitForHandshake(page, { timeout = 20_000 } = {}) { await page.waitForFunction( () => window.__IMPECCABLE_LIVE_INIT__ === true, { timeout }, ); await installLiveQueryHelpers(page); await page.waitForFunction( (sel) => Boolean(window.__impeccableLiveQuery?.(sel)), GLOBAL_BAR_ID, { timeout }, ); // Wait for the picker mode to be active (live.js flips state PICKING after // SSE 'connected' arrives). We can detect it via the global bar's pick // toggle being in its ready state. Soft wait — fall through after a beat // even if the toggle hasn't visibly shifted. await page.waitForTimeout(250); } export async function assertBottomBarIdle(page, { timeout = 5_000 } = {}) { await installLiveQueryHelpers(page); await page.waitForFunction( ({ ids }) => ids.every((sel) => Boolean(window.__impeccableLiveQuery(sel))), { ids: [ GLOBAL_BAR_ID, PICK_TOGGLE, INSERT_TOGGLE, DETECT_TOGGLE, DESIGN_TOGGLE, STEER_CHAT_ID, STEER_INPUT_ID, '#impeccable-live-page-chat-voice', EXIT_BUTTON, ], }, { timeout }, ); const snapshot = await page.evaluate(({ pickSel, insertSel, detectSel, designSel }) => { const q = window.__impeccableLiveQuery; return { pick: controlSnapshot(q(pickSel)), insert: controlSnapshot(q(insertSel)), detect: controlSnapshot(q(detectSel)), design: controlSnapshot(q(designSel)), }; function controlSnapshot(el) { return { exists: !!el, text: (el?.textContent || '').replace(/\s+/g, ' ').trim(), ariaLabel: el?.getAttribute('aria-label') || '', active: el?.dataset?.active || null, disabled: !!el?.disabled, }; } }, { pickSel: PICK_TOGGLE, insertSel: INSERT_TOGGLE, detectSel: DETECT_TOGGLE, designSel: DESIGN_TOGGLE, }); for (const [name, value] of Object.entries(snapshot)) { if (!value.exists) throw new Error(`bottom bar ${name} control is missing`); if (value.disabled) throw new Error(`bottom bar ${name} control unexpectedly disabled`); } } export async function runLiveChromeBottomBarSmoke(page, { expectDetectMinCount = 1, designTitle = '', designRawText = '', } = {}) { await assertBottomBarIdle(page); await runPickInsertToggleSmoke(page); await runDetectSmoke(page, { expectMinCount: expectDetectMinCount }); await runDesignPanelSmoke(page, { title: designTitle, rawText: designRawText }); } function installLiveQueryHelpersInPage() { window.__impeccableLiveQuery = (selector) => { const root = window.__IMPECCABLE_LIVE_CHROME_CORE__?.root?.() || window.__IMPECCABLE_LIVE_UI_ROOT__ || null; return root?.querySelector?.(selector) || document.querySelector(selector); }; window.__impeccableLiveQueryAll = (selector) => { const root = window.__IMPECCABLE_LIVE_CHROME_CORE__?.root?.() || window.__IMPECCABLE_LIVE_UI_ROOT__ || null; const fromRoot = root?.querySelectorAll ? [...root.querySelectorAll(selector)] : []; const fromDoc = [...document.querySelectorAll(selector)]; return [...new Set([...fromRoot, ...fromDoc])]; }; } export async function installLiveQueryHelpers(page, { timeout = 5_000 } = {}) { await page.addInitScript(installLiveQueryHelpersInPage).catch(() => {}); await withTimeout( page.evaluate(installLiveQueryHelpersInPage), timeout, 'install live query helpers', ); } function withTimeout(promise, timeout, label) { let timer; const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeout}ms`)), timeout); }); return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer)); } async function clickLiveControl(page, selector) { await installLiveQueryHelpers(page); const clicked = await page.evaluate((sel) => { const el = window.__impeccableLiveQuery(sel); if (!el || el.disabled) return false; el.click(); return true; }, selector); if (clicked) return; await page.locator(selector).click({ timeout: 5_000 }); } async function readControlActive(page, selector) { await installLiveQueryHelpers(page); return page.evaluate((sel) => window.__impeccableLiveQuery(sel)?.dataset.active === 'true', selector); } async function ensureLiveControlActive(page, selector, active) { if (await readControlActive(page, selector) === active) return; await clickLiveControl(page, selector); await page.waitForFunction( ({ sel, expected }) => window.__impeccableLiveQuery(sel)?.dataset.active === (expected ? 'true' : 'false'), { sel: selector, expected: active }, { timeout: 5_000 }, ); } export async function runPickInsertToggleSmoke(page) { await ensureLiveControlActive(page, PICK_TOGGLE, true); await page.waitForFunction( ({ pickSel, insertSel }) => window.__impeccableLiveQuery(pickSel)?.dataset.active === 'true' && window.__impeccableLiveQuery(insertSel)?.dataset.active === 'false', { pickSel: PICK_TOGGLE, insertSel: INSERT_TOGGLE }, { timeout: 5_000 }, ); await ensureLiveControlActive(page, INSERT_TOGGLE, true); await page.waitForFunction( ({ pickSel, insertSel }) => window.__impeccableLiveQuery(pickSel)?.dataset.active === 'false' && window.__impeccableLiveQuery(insertSel)?.dataset.active === 'true', { pickSel: PICK_TOGGLE, insertSel: INSERT_TOGGLE }, { timeout: 5_000 }, ); await ensureLiveControlActive(page, INSERT_TOGGLE, false); await ensureLiveControlActive(page, PICK_TOGGLE, false); } export async function runDetectSmoke(page, { expectMinCount = 1 } = {}) { await ensureLiveControlActive(page, DETECT_TOGGLE, true); await page.waitForFunction( ({ badgeSel, expectMin }) => { const badge = window.__impeccableLiveQuery(badgeSel); const count = parseInt(badge?.textContent || '0', 10); const overlays = document.querySelectorAll('.impeccable-overlay').length; return count >= expectMin && overlays >= expectMin && badge?.style.display !== 'none'; }, { badgeSel: DETECT_BADGE, expectMin: expectMinCount }, { timeout: 15_000 }, ); await ensureLiveControlActive(page, PICK_TOGGLE, true); await page.waitForFunction( () => [...document.querySelectorAll('.impeccable-overlay')] .every((overlay) => overlay.style.pointerEvents === 'none'), { timeout: 5_000 }, ); await ensureLiveControlActive(page, PICK_TOGGLE, false); await ensureLiveControlActive(page, DETECT_TOGGLE, false); await page.waitForFunction( ({ badgeSel }) => { const badge = window.__impeccableLiveQuery(badgeSel); return document.querySelectorAll('.impeccable-overlay').length === 0 && (!badge || badge.style.display === 'none' || (badge.textContent || '') === '0'); }, { badgeSel: DETECT_BADGE }, { timeout: 5_000 }, ); } export async function runDesignPanelSmoke(page, { title = '', rawText = '' } = {}) { await ensureLiveControlActive(page, DESIGN_TOGGLE, true); await page.waitForFunction( ({ hostSel, titleText }) => { const host = window.__impeccableLiveQuery(hostSel); const root = host?.shadowRoot; const panel = root?.querySelector('.panel'); const bodyText = root?.querySelector('#panel-body')?.textContent || ''; return panel?.getAttribute('data-open') === 'true' && bodyText.trim().length > 0 && !bodyText.includes('Loading design system') && !bodyText.includes('No DESIGN.md yet') && !bodyText.includes('Failed to load design system') && (!titleText || bodyText.includes(titleText)); }, { hostSel: DESIGN_HOST, titleText: title }, { timeout: 15_000 }, ); await page.evaluate((hostSel) => { const root = window.__impeccableLiveQuery(hostSel)?.shadowRoot; const raw = [...(root?.querySelectorAll('.tab') || [])].find((btn) => /Raw/i.test(btn.textContent || '')); raw?.click(); }, DESIGN_HOST); await page.waitForFunction( ({ hostSel, expected }) => { const text = window.__impeccableLiveQuery(hostSel)?.shadowRoot?.textContent || ''; return !expected || text.includes(expected); }, { hostSel: DESIGN_HOST, expected: rawText }, { timeout: 10_000 }, ); await page.evaluate((hostSel) => { const root = window.__impeccableLiveQuery(hostSel)?.shadowRoot; root?.querySelector('.panel-close')?.click(); }, DESIGN_HOST); await page.waitForFunction( ({ hostSel, toggleSel }) => { const panel = window.__impeccableLiveQuery(hostSel)?.shadowRoot?.querySelector('.panel'); const toggle = window.__impeccableLiveQuery(toggleSel); return panel?.getAttribute('data-open') === 'false' && toggle?.dataset.active === 'false'; }, { hostSel: DESIGN_HOST, toggleSel: DESIGN_TOGGLE }, { timeout: 5_000 }, ); } export async function clickExitLiveMode(page) { await clickLiveControl(page, EXIT_BUTTON); await page.waitForFunction( ({ barSel }) => { const bar = window.__impeccableLiveQuery?.(barSel); return window.__IMPECCABLE_LIVE_INIT__ === false && (!bar || !bar.isConnected); }, { barSel: GLOBAL_BAR_ID }, { timeout: 5_000 }, ); } export async function drawAnnotationPinAndStroke(page, { comment = 'Make this area easier to scan', } = {}) { await installLiveQueryHelpers(page); const rect = await waitForAnnotationRect(page); const pinPoint = { x: rect.left + Math.min(28, Math.max(12, rect.width * 0.2)), y: rect.top + Math.min(28, Math.max(12, rect.height * 0.35)), }; await page.mouse.click(pinPoint.x, pinPoint.y); await page.waitForFunction( (pinsSel) => Boolean(window.__impeccableLiveQuery(pinsSel)?.querySelector('input')), ANNOTATION_PINS_ID, { timeout: 5_000 }, ); await page.evaluate(({ pinsSel, value }) => { const input = window.__impeccableLiveQuery(pinsSel)?.querySelector('input'); if (!input) return false; input.value = value; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); return true; }, { pinsSel: ANNOTATION_PINS_ID, value: comment }); await page.waitForFunction( ({ pinsSel, expected }) => { const pins = window.__impeccableLiveQuery(pinsSel); return pins && pins.textContent.includes(expected); }, { pinsSel: ANNOTATION_PINS_ID, expected: comment }, { timeout: 5_000 }, ); const strokeStart = { x: rect.left + rect.width * 0.58, y: rect.top + rect.height * 0.28 }; const strokeEnd = { x: rect.left + rect.width * 0.86, y: rect.top + rect.height * 0.72 }; await page.mouse.move(strokeStart.x, strokeStart.y); await page.mouse.down(); await page.mouse.move((strokeStart.x + strokeEnd.x) / 2, (strokeStart.y + strokeEnd.y) / 2, { steps: 4 }); await page.mouse.move(strokeEnd.x, strokeEnd.y, { steps: 4 }); await page.mouse.up(); await page.waitForFunction( ({ annotSel, clearSel }) => { const annot = window.__impeccableLiveQuery(annotSel); const clear = window.__impeccableLiveQuery(clearSel); const stroke = annot?.querySelector('[data-annot-stroke]'); return Boolean(stroke) && clear?.style.display !== 'none'; }, { annotSel: ANNOTATION_ID, clearSel: ANNOTATION_CLEAR_ID }, { timeout: 5_000 }, ); } export async function assertAnnotationUploadEvent(event) { if (!event) throw new Error('expected recorded generate event'); if (!Array.isArray(event.comments) || event.comments.length < 1) { throw new Error('expected generate event to include annotation comments'); } if (!Array.isArray(event.strokes) || event.strokes.length < 1) { throw new Error('expected generate event to include annotation strokes'); } if (!event.screenshotPath || typeof event.screenshotPath !== 'string') { throw new Error('expected generate event to include screenshotPath'); } } async function waitForAnnotationRect(page) { await page.waitForFunction( (sel) => { const el = window.__impeccableLiveQuery(sel); if (!el || el.style.display === 'none') return false; const rect = el.getBoundingClientRect(); return rect.width > 20 && rect.height > 20; }, ANNOTATION_ID, { timeout: 5_000 }, ); return page.evaluate((sel) => { const rect = window.__impeccableLiveQuery(sel).getBoundingClientRect(); return { left: rect.left, top: rect.top, width: rect.width, height: rect.height, }; }, ANNOTATION_ID); } /** * Click an in-page element to select it. live-browser.js's picker only acts * when state === 'PICKING' AND pickActive is true. Both interaction toggles * default off on a fresh page — enable pick mode before hovering. */ export async function pickElement(page, selector, opts = {}) { const position = opts.position || null; if (opts.resetPickMode) await resetPickMode(page); else await enablePickMode(page); for (let attempt = 0; attempt < 3; attempt++) { const el = await page.waitForSelector(selector, { timeout: 5_000 }); await ensurePickerActive(page); await hideAnnotationOverlay(page); try { await el.hover(position ? { position } : undefined); // Tiny settle: live-browser updates `hoveredElement` on mousemove, and the // click handler reads from it. await page.waitForTimeout(50); await clickPickTarget(page, el, position); } catch (err) { if (attempt === 2) throw err; await page.waitForTimeout(250); await resetPickMode(page); continue; } // Per-element bar mounts on click → wait for it. Dialog fixtures can // briefly hide the global live chrome while preActions open a portal, so // retry once after explicitly re-arming picker mode. const visible = await page .waitForSelector(BAR_ID, { state: 'visible', timeout: 5_000 }) .then(() => true, () => false); if (visible) break; await resetPickMode(page); if (attempt === 2) { const snapshot = await page.evaluate(({ selector, barSel, pickSel }) => { const target = document.querySelector(selector); const rect = target?.getBoundingClientRect(); const hit = rect ? document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2) : null; const query = window.__impeccableLiveQuery || ((sel) => document.querySelector(sel)); const bar = query(barSel); const pick = query(pickSel); return { liveState: window.__IMPECCABLE_LIVE_STATE__ || null, target: target ? { tag: target.tagName, classes: target.className, rect: rect?.toJSON?.() || null } : null, hit: hit ? { tag: hit.tagName, classes: hit.className, text: (hit.textContent || '').slice(0, 80) } : null, pickActive: pick?.dataset.active || null, bar: bar ? { display: bar.style.display, text: bar.textContent } : null, debugState: window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null, }; }, { selector, barSel: BAR_ID, pickSel: PICK_TOGGLE_ID }).catch((error) => ({ error: error.message })); throw new Error(`pick did not open configure bar for ${selector}: ${JSON.stringify(snapshot)}`); } } // Wait specifically for the Configure-row submit button to be in the bar. // pickElement returning before that race-conditions with clickGo on // fixtures whose framework re-renders right after pick (modal open, tab // switch). Anchoring the wait on the submit button's accessible name is // robust: the bar can be visible-but-empty (state=PICKING) before // showBar('configure') populates the row, and the button itself is // icon-only. await page.waitForFunction( ({ barSel, goLabel }) => { const bar = window.__impeccableLiveQuery(barSel); if (!bar) return false; const btns = [...bar.querySelectorAll('button')]; return btns.some((b) => (b.getAttribute('aria-label') || '') === goLabel); }, { barSel: BAR_ID, goLabel: GO_BUTTON_ARIA_LABEL }, { timeout: 5_000 }, ); } async function hideAnnotationOverlay(page) { await page.evaluate(() => { const annot = window.__impeccableLiveQuery('#impeccable-live-annot'); if (annot) annot.style.display = 'none'; }).catch(() => {}); } async function clickPickTarget(page, el, position = null) { const box = await el.boundingBox(); if (box) { const x = position ? box.x + position.x : box.x + box.width / 2; const y = position ? box.y + position.y : box.y + box.height / 2; await page.mouse.click(x, y); return; } await el.evaluate((node) => node.click()); } async function ensurePickerActive(page) { await page.waitForSelector(GLOBAL_BAR_ID, { timeout: 5_000 }); const active = await page .locator(PICK_TOGGLE_ID) .evaluate((el) => el.dataset.active === 'true') .catch(() => false); if (active) return; const clicked = await page.evaluate((sel) => { const btn = window.__impeccableLiveQuery(sel); if (!btn) return false; btn.click(); return true; }, PICK_TOGGLE_ID); if (!clicked) { await page.locator(PICK_TOGGLE_ID).click({ timeout: 5_000 }); } await page.waitForFunction( (sel) => window.__impeccableLiveQuery(sel)?.dataset.active === 'true', PICK_TOGGLE_ID, { timeout: 5_000 }, ); } async function resetPickMode(page) { await page.keyboard.press('Escape').catch(() => {}); await page.waitForTimeout(100); await page.keyboard.press('Escape').catch(() => {}); await page.waitForTimeout(100); await page.evaluate((sel) => { const btn = window.__impeccableLiveQuery(sel); if (!btn) return; const active = btn.dataset.active === 'true'; if (active) btn.click(); btn.click(); }, PICK_TOGGLE_ID).catch(() => {}); await page.waitForFunction( (sel) => window.__impeccableLiveQuery(sel)?.dataset.active === 'true', PICK_TOGGLE_ID, { timeout: 5_000 }, ).catch(() => {}); } /** * Set the variant count by clicking the count button (cycles 2 → 3 → 4 → 2). * Default is 3. If the desired count is already showing, this is a no-op. */ export async function setCount(page, count) { if (count < 2 || count > 4) throw new Error('count must be 2..4'); for (let i = 0; i < 4; i++) { const current = await page.evaluate((barSel) => { const bar = window.__impeccableLiveQuery(barSel); if (!bar) return null; const btns = [...bar.querySelectorAll('button')]; const btn = btns.find((b) => /^×\d+$/.test((b.textContent || '').trim())); if (!btn) return null; return parseInt((btn.textContent || '').trim().slice(1), 10); }, BAR_ID); if (current === count) return; await page.locator(`${BAR_ID} button`, { hasText: /^×\d+$/ }).click(); } throw new Error(`could not cycle count to ${count}`); } /** Select a named Impeccable sub-command from the configure-row picker. */ export async function selectAction(page, action) { const pickerSelector = '#impeccable-live-picker'; const opened = await page.evaluate(({ barSel, pickerSel }) => { const query = window.__impeccableLiveQuery || ((selector) => document.querySelector(selector)); const bar = query(barSel); const picker = query(pickerSel); const actionControl = [...(bar?.querySelectorAll('button') || [])] .find((button) => (button.textContent || '').includes('\u25BE')); if (!actionControl || !picker) return false; actionControl.click(); return true; }, { barSel: BAR_ID, pickerSel: pickerSelector }); if (!opened) throw new Error('could not open Live action picker'); await page.waitForFunction((selector) => { const picker = window.__impeccableLiveQuery(selector); return picker && picker.style.display !== 'none'; }, pickerSelector, { timeout: 5_000 }); const selected = await page.evaluate(({ pickerSel, value }) => { const picker = window.__impeccableLiveQuery(pickerSel); const chip = picker?.querySelector(`button[data-action="${CSS.escape(value)}"]`); if (!chip) return false; chip.click(); return true; }, { pickerSel: pickerSelector, value: action }); if (!selected) throw new Error(`Live action ${JSON.stringify(action)} is unavailable`); } /** * Click Go. Browser POSTs the generate event; the agent picks it up. Headed * browser runs can occasionally accept the click without leaving configure * mode after a long manual Apply, so verify the bar advanced and retry the * visible click if it did not. */ export async function clickGo(page) { let lastErr; for (let attempt = 0; attempt < 3; attempt++) { await clickBarButton(page, { ariaLabel: GO_BUTTON_ARIA_LABEL }); const advanced = await page.waitForFunction( ({ barSel, goLabel }) => { const bar = window.__impeccableLiveQuery(barSel); if (!bar) return false; const text = bar.textContent || ''; if (/Generating\b/.test(text)) return true; if (/\d+\s*\/\s*\d+/.test(text)) return true; return ![...bar.querySelectorAll('button')].some((button) => (button.getAttribute('aria-label') || '') === goLabel); }, { barSel: BAR_ID, goLabel: GO_BUTTON_ARIA_LABEL }, { timeout: 3_000 }, ).then(() => true, (err) => { lastErr = err; return false; }); if (advanced) return; await page.waitForTimeout(500); } throw lastErr || new Error('Go click did not leave configure mode'); } /** * The generating shader is the visible loader: a frozen capture of the * original painted over the target while variants are being written. Every * transition into CYCLING has to take it down, so a cycling bar with the * shader still up is the stuck loader from issue #719, not a cosmetic * detail. The transition is synchronous, so a short window is generous. */ async function assertGeneratingShaderCleared(page) { const stillUp = await page .waitForFunction( () => !(window.__impeccableLiveQuery || document.querySelector.bind(document))('#impeccable-live-shader'), undefined, { timeout: 5_000 }, ) .then(() => false) .catch(() => true); if (stillUp) { const err = new Error( 'CYCLING reached but the generating shader overlay (#impeccable-live-shader) is still up: ' + 'the loader never handed off to the variant cycler', ); // Cycling was reached, so the recovery paths in waitForCyclingRobust // (retrace preActions, reload) have nothing to recover and a reload would // hide the defect by taking the shader down with the page. err.impeccableNoRetry = true; throw err; } } /** * Wait for the bar to enter CYCLING state — happens after the agent's * variants land in the DOM via HMR and the MutationObserver counts them. * * The cycling row has the visible counter `N/M` in monospaced font; we * detect it by content. The bar can also auto-reload if HMR was slow, so * we give it a generous window. */ export async function waitForCycling(page, expectedCount, { timeout = 30_000 } = {}) { await installLiveQueryHelpers(page); try { await page.waitForFunction( ({ barSel, expected }) => { const bar = window.__impeccableLiveQuery(barSel); if (!bar) return false; const text = bar.textContent || ''; // Counter format: "1/3", "2/3" etc. Look for any "i/N" with N matching. const m = text.match(/(\d+)\s*\/\s*(\d+)/); if (!m) return false; const wrapper = window.__impeccableLiveQuery('[data-impeccable-variants]'); const debugState = window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.(); const arrived = /^(?:svelte|vue)-component$/.test(wrapper?.dataset.impeccablePreview || '') ? Number(debugState?.arrivedVariants || 0) : wrapper ? wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])').length : 0; return parseInt(m[2], 10) === expected && arrived >= expected; }, { barSel: BAR_ID, expected: expectedCount }, { timeout }, ); await assertGeneratingShaderCleared(page); } catch (err) { if (process.env.IMPECCABLE_E2E_DEBUG) { const snapshot = await page.evaluate((barSel) => { const query = window.__impeccableLiveQuery || ((sel) => document.querySelector(sel)); const root = window.__IMPECCABLE_LIVE_CHROME_CORE__?.root?.() || window.__IMPECCABLE_LIVE_UI_ROOT__ || null; const bar = query(barSel); const toast = query('#impeccable-live-toast'); const wrapper = query('[data-impeccable-variants]'); return { liveInit: window.__IMPECCABLE_LIVE_INIT__, adapter: window.__IMPECCABLE_LIVE_ADAPTER__, rootText: root?.textContent?.replace(/\s+/g, ' ').trim().slice(0, 600) || null, bar: bar ? { display: bar.style.display, text: bar.textContent } : null, toast: toast?.textContent || null, wrapper: wrapper ? { preview: wrapper.dataset.impeccablePreview, count: wrapper.dataset.impeccableVariantCount, html: wrapper.outerHTML.slice(0, 600) } : null, debugState: window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null, storage: localStorage.getItem('impeccable-live-session'), scripts: document.querySelectorAll('script[data-impeccable-live-script]').length, bodyText: document.body.textContent.replace(/\s+/g, ' ').trim().slice(0, 600), }; }, BAR_ID).catch((snapErr) => ({ error: snapErr.message })); console.error('--- waitForCycling snapshot ---\n' + JSON.stringify(snapshot, null, 2)); } throw err; } } /** * Click the next variant button (right arrow). */ export async function clickNext(page) { await clickBarButton(page, '→'); } export async function clickPrev(page) { await clickBarButton(page, '←'); } /** * Wait until one variant step has fully landed: the bar counter, the bar's own * notion of the visible variant, and (on component previews) the variant that * is actually mounted all agree. * * Reading the counter alone is not enough. The counter can still show the * previous step when the next click goes out, and two clicks that arrive * inside one settle window leave the session on a variant nobody asked for. */ export async function waitForVariantSettled(page, expected, count, { timeout = 15_000 } = {}) { await installLiveQueryHelpers(page); try { await page.waitForFunction( ({ expected, count, barSel }) => { const bar = window.__impeccableLiveQuery(barSel); if (!bar || !(bar.textContent || '').includes(`${expected}/${count}`)) return false; const debugState = window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.(); if (!debugState) return true; if (debugState.visibleVariant !== expected) return false; if (debugState.hasSvelteComponentSession && debugState.mountedSvelteVariant !== expected) return false; return true; }, { expected, count, barSel: BAR_ID }, { timeout }, ); } catch (err) { const debugState = await page .evaluate(() => window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null) .catch(() => null); throw new Error( `variant ${expected}/${count} never settled; debugState=${JSON.stringify(debugState)} (${err.message})`, ); } } /** * Step the comparison to `targetVariant`, one settled click at a time. * Returns the variant now visible. */ export async function cycleToVariant(page, targetVariant, count, { settleTimeout = 15_000 } = {}) { let visible = await getVisibleVariant(page); let steps = 0; while (visible !== targetVariant) { if (steps++ > count + 6) { throw new Error(`variant ${targetVariant} did not become visible; last visible=${visible}`); } const forward = visible == null || visible < targetVariant; const next = visible == null ? 1 : (forward ? visible + 1 : visible - 1); if (forward) await clickNext(page); else await clickPrev(page); await waitForVariantSettled(page, next, count, { timeout: settleTimeout }); visible = await getVisibleVariant(page); } return visible; } function barButtonMatch(label) { if (label instanceof RegExp) return { kind: 'regex', source: label.source, flags: label.flags }; if (label && typeof label === 'object' && label.ariaLabel) return { kind: 'aria', value: label.ariaLabel }; return { kind: 'text', value: String(label) }; } async function clickBarButton(page, label) { const textMatch = barButtonMatch(label); let lastErr; for (let attempt = 0; attempt < 3; attempt++) { try { await installLiveQueryHelpers(page); const button = textMatch.kind === 'aria' ? page.locator(`${BAR_ID} button[aria-label="${textMatch.value}"]`) : page.locator(`${BAR_ID} button`, { hasText: label }); await button.click({ timeout: 5_000 }); return; } catch (err) { lastErr = err; await page.waitForTimeout(500); } } // Real-LLM fixtures can leave Vite/Tailwind HMR settling for longer than a // human-visible click target stays Playwright-stable. Dispatch the click on // the current button if normal user-like clicks lost the remount race. for (let attempt = 0; attempt < 3; attempt++) { try { const clicked = await page.evaluate(findAndClickBarButton, { barSel: BAR_ID, textMatch }); if (clicked) return; } catch (err) { lastErr = err; } await page.waitForSelector(BAR_ID, { timeout: 5_000 }).catch(() => {}); await page.waitForTimeout(500); } throw lastErr; } async function dispatchBarButton(page, label) { try { await installLiveQueryHelpers(page); const textMatch = barButtonMatch(label); return await withTimeout( page.evaluate(findAndClickBarButton, { barSel: BAR_ID, textMatch }), 5_000, 'dispatch bar button', ); } catch { return false; } } function findAndClickBarButton({ barSel, textMatch }) { const bar = window.__impeccableLiveQuery(barSel); if (!bar) return false; const btn = [...bar.querySelectorAll('button')] .find((candidate) => { const text = candidate.textContent || ''; if (textMatch.kind === 'regex') return new RegExp(textMatch.source, textMatch.flags).test(text); if (textMatch.kind === 'aria') return (candidate.getAttribute('aria-label') || '') === textMatch.value; return text.includes(textMatch.value); }); if (!btn) return false; btn.click(); return true; } /** * Read the currently visible variant index (the "i" in "i/N"). */ export async function getVisibleVariant(page) { try { await installLiveQueryHelpers(page); return await withTimeout( page.evaluate((barSel) => { const wrapper = window.__impeccableLiveQuery('[data-impeccable-variants]'); if (wrapper) { const variants = [...wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])')]; const visible = variants.find((variant) => getComputedStyle(variant).display !== 'none'); const idx = visible ? parseInt(visible.dataset.impeccableVariant || '0', 10) : 0; if (idx > 0) return idx; } const bar = window.__impeccableLiveQuery(barSel); if (!bar) return null; const m = (bar.textContent || '').match(/(\d+)\s*\/\s*(\d+)/); return m ? parseInt(m[1], 10) : null; }, BAR_ID), 5_000, 'read visible variant', ); } catch { return null; } } // --------------------------------------------------------------------------- // Tune popover // // buildParamsPanel renders one row per param with no stable ids: a label row // (label span + readout span) followed by the control (range input, toggle // track button, or a segmented row of buttons). The label text is the only // handle a user has too, so that is what these helpers match on. // --------------------------------------------------------------------------- const TUNE_BUTTON = '[data-iceq-tune="1"]'; const PARAMS_PANEL_ID = '#impeccable-live-params-panel'; /** Open the Tune popover and wait for its rows to render. */ export async function openTunePanel(page, { timeout = 5_000 } = {}) { await installLiveQueryHelpers(page); await page.waitForFunction( (sel) => { const tune = window.__impeccableLiveQuery(sel); return Boolean(tune) && tune.disabled === false; }, TUNE_BUTTON, { timeout }, ); await clickLiveControl(page, TUNE_BUTTON); await page.waitForFunction( (sel) => (window.__impeccableLiveQuery(sel)?.querySelectorAll(':scope > div > div').length || 0) > 0, PARAMS_PANEL_ID, { timeout }, ); } /** * Drag a `range` knob to `value`. Setting `.value` + dispatching `input` is * what a real drag produces; the panel's listener reads the input, not the * event, so this exercises the same code path. */ export async function setTuneRange(page, label, value) { await installLiveQueryHelpers(page); const applied = await page.evaluate(({ panelSel, label, value }) => { const panel = window.__impeccableLiveQuery(panelSel); const row = [...(panel?.querySelectorAll(':scope > div > div') || [])] .find((candidate) => candidate.querySelector('span')?.textContent?.trim() === label); const input = row?.querySelector('input[type="range"]'); if (!input) return null; input.value = String(value); input.dispatchEvent(new Event('input', { bubbles: true })); return parseFloat(input.value); }, { panelSel: PARAMS_PANEL_ID, label, value }); if (applied == null) { throw new Error(`Tune range "${label}" not found. ${await describeTunePanel(page)}`); } return applied; } /** Panel contents + variant state, for failures that would otherwise be mute. */ async function describeTunePanel(page) { const snapshot = await page.evaluate((panelSel) => { const panel = window.__impeccableLiveQuery(panelSel); const rows = [...(panel?.querySelectorAll(':scope > div > div') || [])] .map((row) => (row.querySelector('span')?.textContent || '').trim()); const debugState = window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null; return { rows, barText: debugState?.barText || null, visibleVariant: debugState?.visibleVariant ?? null, mountedSvelteVariant: debugState?.mountedSvelteVariant ?? null, state: debugState?.state || null, }; }, PARAMS_PANEL_ID).catch((err) => ({ error: err.message })); return `Panel snapshot: ${JSON.stringify(snapshot)}`; } /** Click one option of a `steps` param by its visible option label. */ export async function chooseTuneStep(page, label, optionLabel) { await installLiveQueryHelpers(page); const clicked = await page.evaluate(({ panelSel, label, optionLabel }) => { const panel = window.__impeccableLiveQuery(panelSel); const row = [...(panel?.querySelectorAll(':scope > div > div') || [])] .find((candidate) => candidate.querySelector('span')?.textContent?.trim() === label); if (!row) return 'no-row'; const button = [...row.querySelectorAll('button')] .find((btn) => (btn.textContent || '').trim() === optionLabel); if (!button) return 'no-option'; button.click(); return 'ok'; }, { panelSel: PARAMS_PANEL_ID, label, optionLabel }); if (clicked !== 'ok') { throw new Error(`Tune steps "${label}" option "${optionLabel}" not found (${clicked})`); } } // --------------------------------------------------------------------------- // Mount-error card (component previews) // --------------------------------------------------------------------------- const MOUNT_ERROR_ID = '#impeccable-live-mount-error'; const MOUNT_RETRY = '[data-impeccable-mount-retry="true"]'; /** Wait for the persistent mount-error card and return its text. */ export async function waitForMountErrorCard(page, { variant, timeout = 20_000 } = {}) { await installLiveQueryHelpers(page); await page.waitForFunction( ({ sel, variant }) => { const card = window.__impeccableLiveQuery(sel); if (!card) return false; if (variant == null) return true; return (card.textContent || '').includes(`Variant ${variant} failed to load`); }, { sel: MOUNT_ERROR_ID, variant: variant ?? null }, { timeout }, ); return page.evaluate((sel) => window.__impeccableLiveQuery(sel)?.textContent || '', MOUNT_ERROR_ID); } export async function isMountErrorCardVisible(page) { await installLiveQueryHelpers(page); return page.evaluate((sel) => Boolean(window.__impeccableLiveQuery(sel)), MOUNT_ERROR_ID); } /** Click the card's Retry button (re-imports the manifest's current revision). */ export async function clickMountRetry(page) { await installLiveQueryHelpers(page); const clicked = await page.evaluate(({ cardSel, retrySel }) => { const button = window.__impeccableLiveQuery(cardSel)?.querySelector(retrySel); if (!button) return false; button.click(); return true; }, { cardSel: MOUNT_ERROR_ID, retrySel: MOUNT_RETRY }); if (!clicked) throw new Error('mount-error Retry button not found'); } export async function waitForMountErrorCardGone(page, { timeout = 20_000 } = {}) { await installLiveQueryHelpers(page); await page.waitForFunction( (sel) => !window.__impeccableLiveQuery(sel), MOUNT_ERROR_ID, { timeout }, ); } /** * Wait until the picked element renders with `expected` computed font-weight. * The fake agent gives every variant a distinct weight, so this is the render * proof that variant N actually mounted (see FAKE_VARIANT_FONT_WEIGHTS). */ export async function waitForComputedFontWeight(page, selector, expected, { timeout = 10_000 } = {}) { try { await page.waitForFunction( ({ sel, expected }) => { const query = window.__impeccableLiveQuery || ((s) => document.querySelector(s)); const el = query(sel) || document.querySelector(sel); return Boolean(el) && getComputedStyle(el).fontWeight === expected; }, { sel: selector, expected: String(expected) }, { timeout }, ); } catch (err) { const snapshot = await page.evaluate((sel) => { const el = document.querySelector(sel); const debugState = window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null; const mount = document.querySelector('[data-impeccable-component-mount]'); return { found: Boolean(el), fontWeight: el ? getComputedStyle(el).fontWeight : null, outerHTML: el?.outerHTML?.slice(0, 400) || null, mountHTML: mount?.outerHTML?.slice(0, 400) || null, barText: debugState?.barText || null, state: debugState?.state || null, visibleVariant: debugState?.visibleVariant ?? null, mountedSvelteVariant: debugState?.mountedSvelteVariant ?? null, }; }, selector).catch((snapErr) => ({ error: snapErr.message })); throw new Error( `expected computed font-weight ${expected} on ${selector}; snapshot: ${JSON.stringify(snapshot)} (${err.message})`, ); } } export async function readComputedFontWeight(page, selector) { return page.evaluate((sel) => { const query = window.__impeccableLiveQuery || ((s) => document.querySelector(s)); const el = query(sel) || document.querySelector(sel); return el ? getComputedStyle(el).fontWeight : null; }, selector); } /** * Click Accept — sends accept event with current variantId + paramValues. * The bar transitions to a "Saving..." spinner, then a green confirmed row. */ export async function clickAccept(page, { expectedVariant } = {}) { if (expectedVariant != null) { await ensureVisibleVariant(page, expectedVariant); } if (await dispatchBarButton(page, /Accept/)) return; await clickBarButton(page, /Accept/); } async function ensureVisibleVariant(page, expectedVariant) { for (let attempt = 0; attempt < 5; attempt++) { const current = await getVisibleVariant(page); if (current === expectedVariant) return; if (current == null) { await page.waitForTimeout(300); continue; } await clickBarButton(page, current < expectedVariant ? '→' : '←'); await page.waitForTimeout(300); } const current = await getVisibleVariant(page); if (current !== expectedVariant) { throw new Error(`expected visible variant ${expectedVariant} before accept, got ${current}`); } } /** * Click Discard — sends discard event. live-accept.mjs unwinds the wrapper * and restores the original. */ export async function clickDiscard(page) { // The discard button has just a "✕" glyph as text content. if (await dispatchBarButton(page, '✕')) return; await clickBarButton(page, '✕'); } export async function clickEditCopy(page) { await clickEditBadgeButton(page, 'Edit copy'); await page.waitForFunction( () => window.__impeccableLiveQuery('[data-impeccable-editable="true"]')?.isContentEditable === true, { timeout: 5_000 }, ); } export async function editTextLeaf(page, leafSelector, newText) { const leaf = page.locator(leafSelector).first(); await leaf.waitFor({ state: 'visible', timeout: 5_000 }); const editable = await resolveEditableLeaf(page, leafSelector); await editable.click({ timeout: 5_000 }); await editable.fill(newText, { timeout: 5_000 }); } async function resolveEditableLeaf(page, leafSelector) { const direct = page.locator(`${leafSelector}[contenteditable="true"]`).first(); if (await direct.count()) return direct; const nested = page.locator(leafSelector).first().locator('[contenteditable="true"]').first(); if (await nested.count()) return nested; return page.locator(leafSelector).first(); } export async function clickSaveEdit(page) { await clickEditBadgeButton(page, 'Save'); await page.waitForFunction( () => !window.__impeccableLiveQuery('[data-impeccable-editable="true"]'), { timeout: 5_000 }, ); } async function clickEditBadgeButton(page, label) { const proxyRect = await page.evaluate((text) => { const proxies = [...document.querySelectorAll('[data-impeccable-edit-badge-proxy="true"]')]; const proxy = proxies.find((candidate) => (candidate.title || candidate.getAttribute('aria-label') || '').includes(text) ); if (!proxy) return null; const rect = proxy.getBoundingClientRect(); if (rect.width < 1 || rect.height < 1) return null; return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; }, label).catch(() => null); if (proxyRect) { await page.mouse.click(proxyRect.x, proxyRect.y); return; } const button = page.locator(`${EDIT_BADGE_ID} button`, { hasText: label }); try { await button.click({ timeout: 5_000 }); return; } catch (err) { const clicked = await page.evaluate(({ badgeSel, text }) => { const badge = window.__impeccableLiveQuery(badgeSel); const btn = [...(badge?.querySelectorAll('button') || [])].find((candidate) => (candidate.textContent || candidate.getAttribute('aria-label') || candidate.title || '').includes(text) ); if (!btn) return false; btn.click(); return true; }, { badgeSel: EDIT_BADGE_ID, text: label }); if (!clicked) throw err; } } export async function assertApplyDockVisible(page, expectedCount, { timeout = 5_000 } = {}) { await page.waitForFunction( ({ dockSel, expected }) => { const dock = window.__impeccableLiveQuery(dockSel); if (!dock || dock.style.display === 'none') return false; const pill = [...dock.querySelectorAll('button')].find((btn) => /Apply copy edit/.test(btn.textContent || '') ); if (!pill || pill.style.display === 'none') return false; if (expected == null) return true; return parseInt(pill.dataset.count || '0', 10) === expected; }, { dockSel: PENDING_DOCK_ID, expected: expectedCount }, { timeout }, ); } export async function waitForApplyDockHidden(page, { timeout = 10_000 } = {}) { await page.waitForFunction( (dockSel) => { const dock = window.__impeccableLiveQuery(dockSel); if (!dock || dock.style.display === 'none') return true; const pill = [...dock.querySelectorAll('button')].find((btn) => /Apply copy edit/.test(btn.textContent || '') ); return !pill || pill.style.display === 'none' || parseInt(pill.dataset.count || '0', 10) === 0; }, PENDING_DOCK_ID, { timeout }, ); } export async function assertApplyDockLoading(page, { timeout = 5_000 } = {}) { await page.waitForFunction( (dockSel) => { const dock = window.__impeccableLiveQuery(dockSel); if (!dock || dock.style.display === 'none') return false; const pill = [...dock.querySelectorAll('button')].find((btn) => /Apply copy edit|Applying|Verifying|Fixing apply issue/.test(btn.textContent || '') ); if (!pill) return false; const spinner = dock.querySelector('[aria-hidden="true"]'); return pill.disabled === true || pill.getAttribute('aria-busy') === 'true' || /Applying|Verifying|Fixing apply issue/.test(pill.textContent || '') || spinner?.style?.display === 'inline-block'; }, PENDING_DOCK_ID, { timeout }, ); } export async function clickApplyEdits(page) { const dialog = page.waitForEvent('dialog', { timeout: 5_000 }) .then((d) => d.accept()) .catch(() => {}); await page.locator(`${PENDING_DOCK_ID} button`, { hasText: /Apply copy edit/ }).click({ timeout: 5_000 }); await dialog; } export function assertSourceApplied(tmp, file, originalText, newText) { const body = readFileSync(join(tmp, file), 'utf-8'); if (!body.includes(newText)) { throw new Error(`expected ${file} to include ${JSON.stringify(newText)}`); } if (originalText && !String(newText).includes(originalText) && body.includes(originalText)) { throw new Error(`expected ${file} not to include ${JSON.stringify(originalText)}`); } } /** * Wait for the bar to go away (after accept/discard the bar hides on confirm). */ export async function waitForBarHidden(page, { timeout = 10_000 } = {}) { await installLiveQueryHelpers(page); await page.waitForFunction( (barSel) => { const bar = window.__impeccableLiveQuery(barSel); return !bar || bar.style.display === 'none'; }, BAR_ID, { timeout }, ); } /** * Dismiss dev-tool overlays that intercept clicks on the live bar (Astro, etc.). * @param {import('playwright').Page} page */ export async function preparePageForBarInteraction(page) { await page.evaluate(() => { for (const el of window.__impeccableLiveQueryAll('astro-dev-toolbar')) { el.style.setProperty('display', 'none', 'important'); el.style.setProperty('pointer-events', 'none', 'important'); } }); } export async function waitForSteerInputFocused(page, { timeout = 5_000 } = {}) { await page.waitForFunction( (inputSel) => { const input = window.__impeccableLiveQuery(inputSel); const active = window.__IMPECCABLE_LIVE_CHROME_CORE__?.activeElementDeep?.() || input?.getRootNode?.()?.activeElement || document.activeElement; return Boolean(input && active === input && input.style.pointerEvents !== 'none' && input.style.opacity !== '0'); }, STEER_INPUT_ID, { timeout }, ); } export async function waitForSteerInputValue(page, value, { timeout = 5_000 } = {}) { await page.waitForFunction( ({ inputSel, value: expected }) => window.__impeccableLiveQuery(inputSel)?.value === expected, { inputSel: STEER_INPUT_ID, value }, { timeout }, ); } /** * Expand the Steer pill, type a message, and submit with Enter. * Uses a normal click when possible; falls back to direct focus when overlays * (e.g. Astro dev toolbar) intercept pointer events — same outcome as keyboard focus. */ export async function submitSteer(page, message) { await installLiveQueryHelpers(page); await preparePageForBarInteraction(page); const chat = page.locator(STEER_CHAT_ID); await chat.waitFor({ state: 'visible', timeout: 5_000 }); try { await chat.click({ timeout: 2_500 }); } catch { await chat.click({ force: true, timeout: 2_500 }); } await waitForSteerInputFocused(page); const input = page.locator(STEER_INPUT_ID); await input.type(message, { timeout: 5_000 }); await waitForSteerInputValue(page, message); await input.press('Enter'); } /** * Poll until a marked hero is visible. Uses Playwright's visible check so * elements inside closed modals/tabs do not satisfy the assertion. */ export async function waitForSteerDomMarker(page, selector, { timeout = 20_000 } = {}) { const loc = page.locator(selector).first(); await loc.waitFor({ state: 'visible', timeout }); } /** * Steer bar enters processing mode after submit (handing off / working). */ export async function waitForSteerLocked(page, { timeout = 5_000 } = {}) { await page.waitForFunction( (sel) => window.__impeccableLiveQuery(sel)?.dataset.processing === 'true', STEER_CHAT_ID, { timeout }, ); } /** * Steer bar unlocks after the agent replies steer_done over SSE. */ export async function waitForSteerUnlocked(page, { timeout = 15_000 } = {}) { await page.waitForFunction( (sel) => { const chat = window.__impeccableLiveQuery(sel); const input = window.__impeccableLiveQuery('#impeccable-live-page-chat-input'); return chat?.dataset.processing !== 'true' && input && !input.disabled; }, STEER_CHAT_ID, { timeout }, ); } async function ensureToggleActive(page, selector, shouldBeActive) { await installLiveQueryHelpers(page); const isActive = await page.locator(selector).evaluate((el) => el?.dataset.active === 'true'); if (isActive === shouldBeActive) return; await page.locator(selector).click({ timeout: 5_000 }); await page.waitForFunction( ({ sel, active }) => window.__impeccableLiveQuery(sel)?.dataset.active === (active ? 'true' : 'false'), { sel: selector, active: shouldBeActive }, { timeout: 5_000 }, ); } /** Turn on Pick mode (and off Insert — they are mutually exclusive). */ export async function enablePickMode(page) { await ensureToggleActive(page, PICK_TOGGLE, true); } /** Turn on Insert mode (and off Pick — they are mutually exclusive). */ export async function enableInsertMode(page) { await ensureToggleActive(page, INSERT_TOGGLE, true); } /** * Insert flow: hover an anchor at before/after edge, click to place the * resizable placeholder, describe the new element, and click Create. */ export async function runInsertFlow(page, { anchorSelector, position = 'after', prompt = 'Add a testimonial strip', } = {}) { await enableInsertMode(page); const anchor = await page.waitForSelector(anchorSelector, { timeout: 5_000 }); const box = await anchor.boundingBox(); if (!box) throw new Error(`anchor ${anchorSelector} has no layout box`); const x = box.x + box.width / 2; const y = position === 'before' ? box.y + 4 : box.y + box.height - 4; await page.mouse.move(x, y); await page.waitForFunction(() => { const line = window.__impeccableLiveQuery('#impeccable-live-insert-line'); return line && line.style.display !== 'none'; }, { timeout: 5_000 }); await page.mouse.click(x, y); await installLiveQueryHelpers(page); await page.waitForFunction( ({ inputSel, barSel }) => { const input = window.__impeccableLiveQuery(inputSel); const bar = window.__impeccableLiveQuery(barSel); if (!input || !bar) return false; const rect = input.getBoundingClientRect(); return rect.width > 0 && rect.height > 0 && bar.style.display !== 'none'; }, { inputSel: INSERT_INPUT_ID, barSel: BAR_ID }, { timeout: 5_000 }, ); const focused = await page.evaluate((sel) => { const el = window.__impeccableLiveQuery(sel); if (!el) return false; try { el.focus({ preventScroll: true }); } catch { el.focus(); } let active = document.activeElement; while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement; return active === el; }, INSERT_INPUT_ID); if (!focused) throw new Error('Insert prompt input did not receive focus'); await page.keyboard.type(prompt); await page.waitForFunction( ({ sel, value }) => window.__impeccableLiveQuery(sel)?.value === value, { sel: INSERT_INPUT_ID, value: prompt }, { timeout: 5_000 }, ); await page.waitForFunction( (sel) => { const btn = window.__impeccableLiveQuery(sel); return btn && !btn.disabled; }, INSERT_CREATE_ID, { timeout: 5_000 }, ); const clicked = await page.evaluate((sel) => { const btn = window.__impeccableLiveQuery(sel); if (!btn || btn.disabled) return false; btn.click(); return true; }, INSERT_CREATE_ID); if (!clicked) { await page.locator(INSERT_CREATE_ID).click({ force: true, timeout: 5_000 }); } }