import { it, mock } from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { prepareBrowser, imageOutput } from './skill-workflow/browser.mjs'; import { chromium } from 'playwright'; it('fails browser preflight with an actionable error before starting a workflow', async () => { const launch = mock.method(chromium, 'launch', async () => { throw new Error('browser missing'); }); try { await assert.rejects(prepareBrowser('/unused'), /playwright install chromium/); } finally { launch.mock.restore(); } }); it('prepares real browser captures, interactions, and multimodal image results offline', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-workflow-browser-')); let browser; try { fs.writeFileSync(path.join(root, 'index.html'), 'Fixture'); browser = await prepareBrowser(root); const trace = { toolCalls: [] }; const tools = browser.tools(trace); const capture = await tools.browser_snapshot.execute({ path: './index.html', viewport: 'desktop', click: 'button' }); assert.equal(capture.target, 'index.html'); assert.match(capture.text, /Done/); assert.ok(fs.existsSync(path.join(root, capture.screenshot))); assert.equal(capture.viewport, 'desktop'); assert.equal(trace.toolCalls[0].name, 'browser_snapshot'); const output = imageOutput({ output: capture }); assert.equal(output.type, 'content'); assert.ok(output.value.some((part) => part.mediaType === 'image/png')); const viewed = await tools.view_image.execute({ path: capture.screenshot }); assert.equal(viewed.image, capture.image); const captures = await Promise.all(['desktop', 'mobile'].map((viewport) => tools.browser_snapshot.execute({ path: 'index.html', viewport }))); assert.deepEqual(captures.map((result) => result.viewport), ['desktop', 'mobile']); assert.notEqual(captures[0].image, captures[1].image, 'parallel viewports must not share mutable page state'); assert.ok(browser.blockedRequests.some((url) => url.includes('example.invalid'))); await assert.rejects(tools.browser_snapshot.execute({ path: '../outside.html', viewport: 'mobile' }), /workspace/); await assert.rejects(tools.view_image.execute({ path: 'index.html' }), /PNG/); fs.symlinkSync(os.tmpdir(), path.join(root, 'escape')); const response = await fetch(`${browser.origin}/escape/`); assert.equal(response.status, 403); } finally { await browser?.close(); fs.rmSync(root, { recursive: true, force: true }); } });