import { chromium } from 'playwright'; import assert from 'node:assert/strict'; const browser = await chromium.launch({ headless: true }); const viewports = [ { name: '390x844', width: 390, height: 844 }, { name: 'iPhone 15 class', width: 393, height: 852 }, ]; for (const viewport of viewports) { const context = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, deviceScaleFactor: 2, serviceWorkers: 'block', }); const page = await context.newPage(); let analysisRequests = 0; await page.route('**/api/vision-status', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ enabled: true, profile: 'selfhost', providerReady: true, model: 'synthetic-test-model' }), })); await page.route('**/api/analyze', route => { analysisRequests += 1; return route.abort(); }); await page.goto('http://127.0.0.1:4173', { waitUntil: 'networkidle' }); await page.locator('[data-scan]').click(); const camera = page.locator('#camera-photo'); const gallery = page.locator('#gallery-photo'); assert.equal(await camera.getAttribute('capture'), 'environment', `${viewport.name}: camera input uses the rear camera`); assert.equal(await gallery.getAttribute('capture'), null, `${viewport.name}: gallery input does not force camera capture`); assert.equal(await page.getByText('Take photo', { exact: true }).isVisible(), true); assert.equal(await page.getByText('Choose from gallery', { exact: true }).isVisible(), true); await camera.dispatchEvent('cancel'); assert.match(await page.locator('[role="status"]').innerText(), /camera.*closed|permission.*denied/i); assert.equal(await page.getByText('Continue without AI', { exact: true }).isVisible(), true); assert.equal(analysisRequests, 0, `${viewport.name}: cancellation never uploads`); await page.locator('#gallery-photo').setInputFiles({ name: 'corrupt-synthetic.jpg', mimeType: 'image/jpeg', buffer: Buffer.from('not an image'), }); assert.match(await page.locator('.scan-result').innerText(), /could not be read/i); await page.getByText('Try another photo', { exact: true }).click(); await page.locator('#gallery-photo').setInputFiles('tests/fixtures/synthetic-type4.jpg'); assert.equal(await page.locator('#analyze-photo').isDisabled(), true); assert.equal(await page.locator('#retake-photo').isVisible(), true); assert.equal(analysisRequests, 0, `${viewport.name}: corrupt and unconsented photos never upload`); await context.close(); } await browser.close(); console.log('PASS camera/gallery paths recover from cancellation at 390x844 and iPhone-class viewport without upload');