Local work in progress, not reviewed and not part of any PR. Committed only so the nightly release can build from a clean main without discarding it.
62 lines
3.4 KiB
JavaScript
62 lines
3.4 KiB
JavaScript
import { chromium } from 'playwright';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdir } from 'node:fs/promises';
|
|
|
|
await mkdir('artifacts', { recursive: true });
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, serviceWorkers: 'block' });
|
|
const page = await context.newPage();
|
|
const errors = [];
|
|
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
|
|
page.on('pageerror', error => errors.push(error.message));
|
|
await page.goto('http://127.0.0.1:4173', { waitUntil: 'networkidle' });
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload({ waitUntil: 'networkidle' });
|
|
|
|
assert.match(await page.locator('h1').innerText(), /Log it/i);
|
|
assert.equal(await page.locator('.bottom-nav .nav-btn').count(), 3);
|
|
assert.equal(await page.locator('[data-scan]').first().isVisible(), true);
|
|
assert.equal(await page.locator('[data-log]').first().isVisible(), true);
|
|
await page.screenshot({ path: 'artifacts/home-mobile.png', fullPage: false });
|
|
|
|
await page.locator('[data-log]').first().click();
|
|
await page.locator('[data-type="6"]').click();
|
|
await page.locator('#next').click();
|
|
await page.locator('#color').selectOption('brown');
|
|
await page.locator('#urgency').fill('3');
|
|
await page.locator('#note').fill('After lunch');
|
|
await page.locator('#next').click();
|
|
await page.locator('[data-symptom="blood"]').check();
|
|
assert.equal(await page.getByText('Pause and get medical help.').isVisible(), true);
|
|
await page.screenshot({ path: 'artifacts/red-flag-mobile.png', fullPage: true });
|
|
await page.locator('#save').click();
|
|
assert.equal(await page.getByText('1', { exact: true }).first().isVisible(), true);
|
|
await page.waitForTimeout(2600);
|
|
|
|
await page.locator('[data-view="timmy"]').last().click();
|
|
await page.locator('#chat-message').fill('Can I eat Taco Bell?');
|
|
await page.locator('#send-chat').click();
|
|
const chatText = await page.locator('#chat').innerText();
|
|
assert.match(chatText, /pause and get medical help/i);
|
|
assert.doesNotMatch(chatText, /Taco Bell is safe|cannot clear a food or restaurant/i);
|
|
await page.waitForTimeout(500);
|
|
assert.equal(await page.evaluate(() => window.scrollY), 0, 'reply should not push the page header/navigation out of frame');
|
|
await page.screenshot({ path: 'artifacts/timmy-chat-mobile.png', fullPage: false });
|
|
|
|
// Issue #19: the operator counters have to be disclosed where the user already looks for privacy
|
|
// facts, and the disclosure has to name what is not collected. A settings screen that stays silent
|
|
// about a new counter is the failure this asserts against.
|
|
await page.locator('[data-view="calendar"]').first().click();
|
|
await page.locator('[data-view="privacy"]').first().click();
|
|
const disclosure = page.locator('[data-health-disclosure]');
|
|
assert.equal(await disclosure.isVisible(), true, 'the privacy screen must disclose the health counters');
|
|
const disclosureText = await disclosure.innerText();
|
|
assert.match(disclosureText, /how analysis requests went/i);
|
|
assert.match(disclosureText, /never (contain|hold|include) your notes, photos, image fingerprints, or identity/i);
|
|
assert.match(disclosureText, /reset/i);
|
|
await page.screenshot({ path: 'artifacts/privacy-health-disclosure.png', fullPage: true });
|
|
|
|
assert.deepEqual(errors, []);
|
|
await browser.close();
|
|
console.log('PASS mobile home → log → red-flag → save → Timmy boundary');
|