#!/usr/bin/env node /* global document, innerWidth, innerHeight, getComputedStyle, requestAnimationFrame */ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { expect } from '@playwright/test'; import { createNativeSession } from './session.mjs'; import { verifyNativeMenuCommands } from './menu-commands.mjs'; import { fileDigest, evidenceFailures, geometryFailures, contentSizes, modes, packageDigest, routes, schema, settingsSections, states, seededCases, } from './contract.mjs'; import { installPreviewFixture } from './preview-fixture.mjs'; import { measurePageHeader, pageHeaderFailures } from './layout-contract.mjs'; import { performance } from 'node:perf_hooks'; const root = path.resolve(import.meta.dirname, '../..'); const [packageArgument, outputArgument] = process.argv.slice(2); assert( packageArgument?.endsWith('.app') && outputArgument, 'Usage: node scripts/native-ui/run.mjs ' ); assert.equal(process.platform, 'darwin', 'This gate must run on macOS'); const packagePath = await realpath(packageArgument); const output = path.resolve(outputArgument); await mkdir(output); // Never overwrite a previous run or its failure artifacts. const git = (...args) => { const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' }); assert.equal(result.status, 0, result.stderr); return result.stdout.trim(); }; const commit = git('rev-parse', 'HEAD'); const version = JSON.parse(await readFile(path.join(root, 'desktop/package.json'), 'utf8')).version; const report = { schema, boundary: 'packaged-macos', status: 'running', commit, dirty: !!git('status', '--porcelain'), version, packagePath, packageDigest: await packageDigest(packagePath), startedAt: new Date().toISOString(), entries: [], httpFailures: [], }; const persist = () => writeFile(path.join(output, 'evidence.json'), JSON.stringify(report, null, 2) + '\n'); await persist(); const session = await createNativeSession({ packagePath, commit, version }); const { origin } = session; let app; let page; let fixtureTask; let activeState = 'startup'; async function launch() { const launched = await session.launch(); ({ app, page } = launched); report.identity = launched.identity; page.on('response', (response) => { if (response.status() >= 400) { report.httpFailures.push({ state: activeState, path: new URL(response.url()).pathname, method: response.request().method(), status: response.status(), }); } }); } async function resize(width, height) { await app.evaluate( ({ BrowserWindow }, size) => { const window = BrowserWindow.getAllWindows().find((w) => w.isVisible()); window.webContents.setZoomFactor(1); window.setContentSize(size.width, size.height); }, { width, height } ); await expect.poll(() => page.evaluate(() => [innerWidth, innerHeight])).toEqual([width, height]); } async function configure(mode) { await resize(mode.width, mode.height); if (await page.getByRole('dialog').count()) await page.goto(origin); if ((await page.locator('html').getAttribute('data-mantine-color-scheme')) !== mode.theme) await button('Toggle theme').click(); if (new URL(page.url()).pathname !== '/') await page .getByRole('complementary', { name: 'Desktop navigation' }) .getByRole('button', { name: 'Board', exact: true }) .click(); await expect(page.locator('html')).toHaveAttribute('data-mantine-color-scheme', mode.theme); await expect(page.getByRole('button', { name: 'Settings', exact: true })).toBeVisible(); } async function metrics() { const geometry = await page.evaluate(() => { const shell = document.querySelector('.desktop-app-shell'); if (!shell) throw new Error('Missing production desktop shell'); const rect = (el) => { const b = el.getBoundingClientRect(); return { x: b.x, y: b.y, width: b.width, height: b.height, right: b.right, bottom: b.bottom }; }; const paintedOpacity = (el) => { let opacity = 1; for (let node = el; node; node = node.parentElement) { const style = getComputedStyle(node); if (style.visibility !== 'visible' || style.display === 'none') return 0; opacity *= Number(style.opacity); } return opacity; }; return { width: innerWidth, height: innerHeight, rem: parseFloat(getComputedStyle(document.documentElement).fontSize), scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, shell: rect(shell), overlays: [ ...document.querySelectorAll( '[data-overlay-active="true"] .vk-overlay-content, .vk-overlay-content[data-overlay-active="true"], [data-testid="task-detail-panel"][data-overlay-active="true"]' ), ].map((el) => ({ ...rect(el), compound: !!el.closest('[data-overlay-compound]'), opacity: paintedOpacity(el), overflow: el.scrollWidth - el.clientWidth, parts: [ ...el.querySelectorAll( '.vk-overlay-header, .vk-overlay-body, .vk-overlay-footer, .vk-overlay-scroll, .vk-task-workspace-header, .mantine-Drawer-body' ), ].map((part) => { const style = getComputedStyle(part); const b = rect(part); return { insetKind: part.matches('.vk-task-workspace-header') ? 'task-header' : part.matches('.mantine-Drawer-body') ? 'task-body' : part.matches('.vk-overlay-scroll') ? 'scroll' : part.matches('.vk-overlay-header') ? 'header' : part.matches('.vk-overlay-footer') ? 'footer' : 'body', name: part.matches('.vk-overlay-header, .vk-task-workspace-header') ? 'header' : part.matches('.vk-overlay-footer') ? 'footer' : 'body', ...b, visible: b.width > 0 && b.height > 0 && b.x >= 0 && b.y >= 0 && b.right <= innerWidth + 1 && b.bottom <= innerHeight + 1, padding: [ style.paddingTop, style.paddingRight, style.paddingBottom, style.paddingLeft, ].map(parseFloat), }; }), })), }; }); geometry.primaryHeader = await page.evaluate(measurePageHeader); return geometry; } async function capture(entry) { // Native capturePage does not wait for CSS transitions as Playwright screenshots do. // Wait for the real fade to complete, then allow the compositor to paint it. await expect .poll(async () => (await metrics()).overlays.every((overlay) => overlay.opacity >= 0.999)) .toBe(true); await page.evaluate( () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))) ); entry.route = new URL(page.url()).pathname; entry.theme = await page.locator('html').getAttribute('data-mantine-color-scheme'); entry.geometry = await metrics(); const native = await app.evaluate(async ({ BrowserWindow, screen }) => { const window = BrowserWindow.getAllWindows().find((w) => w.isVisible()); return { bounds: window.getBounds(), contentBounds: window.getContentBounds(), minimumSize: window.getMinimumSize(), scaleFactor: screen.getDisplayMatching(window.getBounds()).scaleFactor, png: (await window.capturePage()).toPNG().toString('base64'), }; }); const name = `${entry.id.replaceAll('/', '--')}.png`; await writeFile(path.join(output, name), Buffer.from(native.png, 'base64')); delete native.png; entry.nativeWindow = native; entry.screenshot = { path: name, sha256: await fileDigest(path.join(output, name)) }; assert.deepEqual(geometryFailures(entry.geometry), [], entry.id); const route = routes.find(([name]) => entry.id.endsWith(`/route-${name}`)); if (route) assert.deepEqual( pageHeaderFailures( entry.geometry.primaryHeader, entry.geometry.rem, route[2], entry.geometry ), [], entry.id ); } const button = (name) => page.getByRole('button', { name, exact: true }); async function dismiss(dialog, opener) { await page.keyboard.press('Escape'); await expect(dialog).toBeHidden(); if (opener) await expect(opener).toBeFocused(); } async function createTask(title) { await button('New Task').click(); const dialog = page.getByRole('dialog', { name: 'Create New Task', exact: true }); await dialog.getByRole('textbox', { name: 'Title', exact: true }).fill(title); await dialog .getByLabel('Description', { exact: true }) .fill('Public-safe native UI acceptance fixture.'); const response = page.waitForResponse( (r) => new URL(r.url()).pathname === '/api/tasks' && r.request().method() === 'POST' ); await dialog.getByRole('button', { name: 'Create Task', exact: true }).click(); const result = await response; assert(result.ok(), 'Synthetic task creation failed'); await expect(dialog).toBeHidden(); const body = await result.json(); return body.data ?? body; } async function openTask() { if (!fixtureTask) fixtureTask = await createTask('Native public-safe fixture'); await page.locator(`[data-task-id="${fixtureTask.id}"]`).click(); const panel = page.getByTestId('task-detail-panel'); await expect(panel).toBeVisible(); return panel; } async function exercise(state, mode, shot) { if (state === 'board') { await expect(page.locator('html')).toHaveAttribute('data-client', 'desktop'); await shot(); } else if (state.startsWith('route-')) { const [, route, title] = routes.find(([id]) => state === `route-${id}`); // Use the actual navigation button so route transition and Back are exercised. const navLabel = { 'Task Templates': 'Templates', 'Behavioral Drift Monitor': 'Drift Monitor', 'Agent Output Scoring': 'Scoring', 'Agent Policies': 'Policies', }[title] ?? title; await page .getByRole('complementary', { name: 'Desktop navigation' }) .getByRole('button', { name: navLabel, exact: true }) .click(); await expect(page).toHaveURL(`${origin}${route}`); const shell = page.locator('[data-page-shell="primary"]'); await expect(shell.getByRole('heading', { name: title, level: 1, exact: true })).toBeFocused(); await expect(page.getByRole('heading', { level: 1 })).toHaveCount(1); await expect(button('Expand right sidebar').or(button('Collapse right sidebar'))).toHaveCount( 0 ); const back = shell.getByRole('button', { name: 'Back', exact: true }); await expect(back).toHaveText(''); await shot(); await back.click(); await expect(page).toHaveURL(`${origin}/`); } else if (state.startsWith('settings-')) { const tab = settingsSections.find((label) => state === `settings-${label.toLowerCase()}`); const opener = button('Settings'); await opener.click(); const dialog = page.locator('.settings-dialog-content'); await dialog.getByRole('tab', { name: tab, exact: true }).click(); await expect(dialog.getByRole('tab', { name: tab, exact: true })).toHaveAttribute( 'aria-selected', 'true' ); await expect(dialog.getByRole('heading', { name: tab, exact: true })).toBeVisible(); await shot(); await dismiss(dialog, opener); } else if (state === 'left-rail' || state === 'right-rail') { const side = state === 'left-rail' ? 'left' : 'right'; const expand = button(`Expand ${side} sidebar`); if (await expand.isVisible()) await expand.click(); const collapse = button(`Collapse ${side} sidebar`); await expect(collapse).toHaveAttribute('aria-expanded', 'true'); const controlled = page.locator(`#${await collapse.getAttribute('aria-controls')}`); await expect(controlled).toBeVisible(); await shot(); await collapse.click(); await expect(expand).toHaveAttribute('aria-expanded', 'false'); } else if (state === 'board-chat' || state === 'squad-chat') { const channel = state === 'board-chat' ? 'Board Chat' : 'Squad Chat'; await button(`Open ${channel}`).click(); const dock = page.getByRole('region', { name: 'Workbench right dock' }); await expect(dock).toBeVisible(); if (state === 'squad-chat') { const sender = dock.getByRole('combobox', { name: 'Sending as' }); await sender.click(); await expect(page.getByRole('listbox', { name: 'Sending as' })).toBeInViewport(); await sender.press('Escape'); await expect(sender).toBeFocused(); } await shot(); await button(`Close ${channel}`).click(); await expect(dock).toBeHidden(); } else if (['task-drawer', 'task-expanded', 'task-chat'].includes(state)) { const panel = await openTask(); if (state === 'task-expanded') await panel.getByRole('button', { name: 'Expand task workspace', exact: true }).click(); if (state === 'task-chat') { await panel.getByRole('button', { name: 'Chat', exact: true }).first().click(); const chat = panel.getByRole('region', { name: /^Task Chat:/ }); await chat .getByRole('textbox', { name: 'Message Task Chat' }) .fill('Unsent public-safe draft'); await shot(); await chat.getByRole('button', { name: 'Close Task Chat panel', exact: true }).click(); await expect(chat).toBeHidden(); } else await shot(); await panel.getByRole('button', { name: 'Close task workspace', exact: true }).click(); await expect(panel).toBeHidden(); } else if (state === 'preview') { if (!fixtureTask) fixtureTask = await createTask('Native public-safe fixture'); const removeFixture = await installPreviewFixture(page, fixtureTask.id); try { const panel = await openTask(); await panel.getByRole('button', { name: 'Results', exact: true }).click(); const opener = panel.getByRole('button', { name: 'Preview acceptance.txt', exact: true }); await opener.click(); const dialog = page.getByRole('dialog', { name: 'Preview: Native acceptance artifact', exact: true, }); await expect(dialog).toContainText('Native text evidence'); await expect(dialog.getByRole('button', { name: 'Download', exact: true })).toBeInViewport({ ratio: 1, }); await shot({ fixtureBoundary: 'read-only text artifact responses' }); await dismiss(dialog, opener); await panel.getByRole('button', { name: 'Close task workspace', exact: true }).click(); } finally { await removeFixture(); } } else if (state === 'create-template' || state === 'edit-template') { await page.goto(`${origin}/templates`); const name = `Native template ${mode.id}`; if (state === 'edit-template') { await page.getByPlaceholder('Search templates...').fill(name); await button('Edit').click(); } else await button('New Template').click(); const dialog = page.getByRole('dialog', { name: state === 'edit-template' ? 'Edit Template' : 'Create New Template', exact: true, }); await expect(dialog.getByRole('textbox', { name: 'Template Name', exact: true })).toBeFocused(); await dialog.getByRole('textbox', { name: 'Template Name', exact: true }).fill(name); const text = `# Native acceptance\n\n${'Public-safe description.\n'.repeat(40)}${state}`; await dialog.getByLabel('Description Template', { exact: true }).fill(text); const scroll = dialog.getByTestId('template-editor-scroll-region'); await scroll.hover(); await page.mouse.wheel(0, 900); const save = dialog.getByRole('button', { name: state === 'edit-template' ? 'Update Template' : 'Create Template', exact: true, }); await expect(save).toBeInViewport({ ratio: 1 }); await shot(); await save.click(); await expect(dialog).toBeHidden(); await page.reload(); await page.getByPlaceholder('Search templates...').fill(name); await button('Edit').click(); const edited = page.getByRole('dialog', { name: 'Edit Template', exact: true }); await expect(edited.getByLabel('Description Template', { exact: true })).toHaveValue(text); await edited.getByRole('button', { name: 'Cancel', exact: true }).click(); await expect(edited).toBeHidden(); } else if (state === 'header-controls') { const header = page.getByRole('navigation', { name: 'Main navigation', exact: true }); const controls = await header .locator('button:visible, input[role="combobox"]:visible') .evaluateAll((elements) => elements.map((el) => ({ name: el.getAttribute('aria-label') || el.textContent.trim(), role: el.getAttribute('role'), })) ); const exercised = []; for (const { name, role } of controls) { const control = role === 'combobox' ? header.getByRole('combobox', { name, exact: true }) : header.getByRole('button', { name, exact: true }); if (name === 'Refresh page') { await Promise.all([page.waitForEvent('load'), control.click()]); await expect(button('Settings')).toBeVisible(); } else if (/^(Expand|Collapse) (left|right) sidebar$/.test(name)) { const before = await control.getAttribute('aria-expanded'); await control.click(); const side = name.includes('left') ? 'left' : 'right'; await expect( button(`${before === 'true' ? 'Expand' : 'Collapse'} ${side} sidebar`) ).toHaveAttribute('aria-expanded', before === 'true' ? 'false' : 'true'); } else if ( name === 'New Task' || name === 'Settings' || name === 'Search' || name === 'Command palette' ) { await control.click(); const dialog = page.getByRole('dialog', { name: { 'New Task': 'Create New Task', Settings: /^Settings(?: Board Only)?$/, Search: 'Universal Search', 'Command palette': 'Command palette', }[name], exact: true, }); await expect(dialog).toBeVisible(); await dismiss(dialog); } else if (/^(Open|Close|Switch to) (Board|Squad) Chat$/.test(name)) { await control.click(); const dock = page.getByRole('region', { name: 'Workbench right dock' }); await expect(dock).toBeVisible(); await button(name.includes('Squad') ? 'Close Squad Chat' : 'Close Board Chat').click(); await expect(dock).toBeHidden(); } else if (name === 'Toggle theme') { await control.click(); await expect(page.locator('html')).toHaveAttribute( 'data-mantine-color-scheme', mode.theme === 'light' ? 'dark' : 'light' ); await control.click(); } else if (name === 'Workspace') { await control.click(); await expect(page.getByRole('listbox', { name: 'Workspace' })).toBeVisible(); await control.press('Escape'); } else if (name === 'More views') { await control.click(); await expect(page.getByRole('menu')).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.getByRole('menu')).toBeHidden(); } else if (name === 'Session menu') { await control.click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.getByRole('dialog')).toBeHidden(); } else if (/^WebSocket /.test(name)) { await control.click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.keyboard.press('Escape'); } else throw new Error(`Visible header control has no behavioral assertion: ${name}`); exercised.push(name); } assert.equal(exercised.length, controls.length); await shot({ headerControls: exercised }); } else if (state === 'create-task') { const opener = button('New Task'); await opener.click(); const dialog = page.getByRole('dialog', { name: 'Create New Task', exact: true }); await expect(dialog.getByRole('textbox', { name: 'Title', exact: true })).toBeFocused(); await dialog .getByRole('textbox', { name: 'Title', exact: true }) .fill(`Native acceptance ${mode.id}`); await dialog .getByLabel('Description', { exact: true }) .fill('Public-safe native UI acceptance task.'); await shot(); await dialog.getByRole('button', { name: 'Create Task', exact: true }).click(); await expect(dialog).toBeHidden(); await expect( page.getByRole('heading', { name: `Native acceptance ${mode.id}`, exact: true }) ).toBeVisible(); } else if (state === 'confirmation') { await button('Settings').click(); const settings = page.locator('.settings-dialog-content'); const reset = settings.getByRole('button', { name: 'Reset All', exact: true }); await reset.click(); const dialog = page.getByRole('dialog', { name: 'Reset all settings?', exact: true }); await expect(dialog.getByRole('button', { name: 'Cancel', exact: true })).toBeFocused(); await shot(); await dismiss(dialog, reset); await dismiss(settings, button('Settings')); } else if (state === 'search' || state === 'command-palette') { const palette = state === 'command-palette'; if (palette) await page.keyboard.press('Meta+k'); else await button('Search').click(); const dialog = page.getByRole('dialog', { name: palette ? 'Command palette' : 'Universal Search', exact: true, }); await expect( dialog.getByRole('textbox', { name: palette ? 'Search commands' : 'Search Veritas', exact: true, }) ).toBeFocused(); await shot(); await dismiss(dialog); } else if (state === 'responsive-collapse') { await resize(contentSizes.normal.width, contentSizes.normal.height); if (await button('Expand left sidebar').isVisible()) await button('Expand left sidebar').click(); if (await button('Expand right sidebar').isVisible()) await button('Expand right sidebar').click(); await button('Open Board Chat').click(); await resize(contentSizes.minimum.width, contentSizes.minimum.height); await expect(page.getByRole('region', { name: 'Workbench right dock' })).toBeHidden(); await expect(button('Expand left sidebar')).toBeVisible(); await expect(button('Expand right sidebar')).toBeVisible(); await resize(mode.width, mode.height); await shot(); } else if (state === 'relaunch') { await app.close(); app = undefined; await launch(); // The hosted macOS window manager may restore a constrained prior width // after a process relaunch. Re-establish this matrix entry's content size // before checking the renderer state that the relaunch is meant to prove. await resize(mode.width, mode.height); await expect(page.locator('html')).toHaveAttribute('data-mantine-color-scheme', mode.theme); await expect( page.getByRole('heading', { name: `Native acceptance ${mode.id}`, exact: true }) ).toBeVisible(); await shot(); } else { throw new Error(`Required scenario has not been implemented: ${state}`); } } async function checkSeededRendererFailures() { report.seededFailures = []; for (const [id, expectedFailure] of seededCases) { const entry = { id: `seed/${id}`, status: 'running' }; activeState = entry.id; report.seededFailures.push(entry); try { await configure(modes[0]); if (id === 'heading-offset' || id === 'context-invalid-header') { await page .getByRole('complementary', { name: 'Desktop navigation' }) .getByRole('button', { name: 'Activity', exact: true }) .click(); await expect(page.getByRole('heading', { name: 'Activity', level: 1 })).toBeFocused(); } if (id === 'clipped-modal' || id === 'overlay-padding') { await button('New Task').click(); await expect(page.getByRole('dialog', { name: 'Create New Task' })).toBeVisible(); } await page.evaluate((fault) => { if (fault === 'shell-blank') document .querySelector('.desktop-app-shell') .style.setProperty('height', `${innerHeight - 100}px`, 'important'); else if (fault === 'heading-offset') document.querySelector('[data-page-shell="primary"] h1').style.transform = 'translateY(16px)'; else if (fault === 'context-invalid-header') { const button = document.createElement('button'); button.setAttribute('aria-label', 'Expand right sidebar'); button.textContent = 'Invalid board control'; document.querySelector('[aria-label="Main navigation"]').append(button); } else if (fault === 'clipped-modal') document .querySelector('[data-overlay-active="true"] .vk-overlay-content') .style.setProperty('transform', `translateX(${innerWidth}px)`, 'important'); else if (fault === 'overlay-padding') document .querySelector('[data-overlay-active="true"] .vk-overlay-header') .style.setProperty('padding', '0px', 'important'); else if (fault === 'dead-header') { const button = [ ...document.querySelectorAll('[aria-label="Main navigation"] button'), ].find((element) => element.textContent.trim() === 'New Task'); button.replaceWith(button.cloneNode(true)); // Remove React's target handler, retain visible UI. } }, id); entry.observedFailures = []; if (id === 'dead-header') { entry.behavior = { control: 'New Task', visibleBefore: await button('New Task').isVisible(), enabledBefore: await button('New Task').isEnabled(), dialogVisibleBefore: await page .getByRole('dialog', { name: 'Create New Task' }) .isVisible(), }; await button('New Task').click(); entry.behavior.clickCompleted = true; const started = performance.now(); try { await expect(page.getByRole('dialog', { name: 'Create New Task' })).toBeVisible({ timeout: 1000, }); } catch (error) { if (!error.message.includes('toBeVisible')) throw error; entry.observedFailures.push('dead header control'); } entry.behavior.waitedMs = performance.now() - started; entry.behavior.dialogVisibleAfter = await page .getByRole('dialog', { name: 'Create New Task' }) .isVisible(); } try { await capture(entry); } catch (error) { // Geometry faults must retain their native capture, then be identified by the same validator. if (!entry.screenshot) throw error; entry.captureAssertion = error.message; } entry.observedFailures.push(...geometryFailures(entry.geometry)); if (id === 'heading-offset' || id === 'context-invalid-header') entry.observedFailures.push( ...pageHeaderFailures( entry.geometry.primaryHeader, entry.geometry.rem, 'Activity', entry.geometry ) ); assert( entry.observedFailures.includes(expectedFailure), `Injected fault was not detected: ${id}` ); entry.status = 'detected'; } catch (error) { entry.status = 'failed'; entry.error = error.message; } finally { // Faults only affect this disposable renderer; reload removes all injected DOM/CSS. await page.goto(origin); } await persist(); console.log(`${entry.status}: ${entry.id}`); } } try { await launch(); report.menuCommands = await verifyNativeMenuCommands(app, page); await persist(); for (const mode of modes) { for (const state of states) { const entry = { id: `${mode.id}/${state}`, status: 'running' }; activeState = entry.id; report.entries.push(entry); let captured = false; try { await configure(mode); await exercise(state, mode, async (details) => { Object.assign(entry, details); await capture(entry); captured = true; assert.deepEqual( [entry.theme, entry.geometry.width, entry.geometry.height], [mode.theme, mode.width, mode.height], `${entry.id}: capture mode changed during interaction` ); }); assert(captured, 'Scenario did not capture native evidence'); entry.completionGeometry = await metrics(); assert.deepEqual( [entry.completionGeometry.width, entry.completionGeometry.height], [mode.width, mode.height], `${entry.id}: completion viewport changed during interaction` ); assert.deepEqual( geometryFailures(entry.completionGeometry), [], `${entry.id}: after interaction` ); assert.equal( (report.httpFailures ?? []).filter( (failure) => failure.state === entry.id && (failure.status === 429 || failure.status >= 500) ).length, 0, 'Native state encountered rate limiting or a server failure' ); entry.status = 'passed'; } catch (error) { entry.status = 'failed'; entry.error = error.message; if (captured) { entry.failure = { id: `${entry.id}-failure` }; try { await capture(entry.failure); } catch (captureError) { entry.failure.captureError = captureError.message; } } else { try { await capture(entry); } catch (captureError) { entry.captureError = captureError.message; } } } console.log( `${entry.status}: ${entry.id}${entry.error ? `: ${entry.error.split('\n')[0]}` : ''}` ); await persist(); } } await checkSeededRendererFailures(); report.status = report.entries.every((entry) => entry.status === 'passed') ? 'passed' : 'failed'; } catch (error) { report.status = 'failed'; report.error = error.message; } finally { activeState = 'teardown'; if (app) await app.close(); report.completedAt = new Date().toISOString(); report.validationErrors = evidenceFailures(report, { commit, version, packageDigest: report.packageDigest, }); if (report.validationErrors.length) report.status = 'failed'; await persist(); } console.log(`Native evidence: ${path.join(output, 'evidence.json')} (${report.status})`); process.exitCode = report.status === 'passed' ? 0 : 1;