← back to Studio Zero
test/ui.spec.js
73 lines
const { chromium } = require('playwright');
(async () => {
const errors = [];
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', m => { if (m.type() === 'error') errors.push('console:' + m.text()); });
page.on('pageerror', e => errors.push('pageerror:' + e.message));
const base = process.env.BASE || 'http://localhost:4173';
const log = [];
const ok = (c, m) => log.push((c ? 'PASS ' : 'FAIL ') + m);
await page.goto(base, { waitUntil: 'networkidle' });
ok(await page.locator('.brand').isVisible(), 'brand renders');
const mode = await page.locator('#modeChip').textContent();
ok(/DEMO|LIVE/.test(mode), 'mode chip: ' + mode.trim());
ok(await page.locator('#startBtn').isVisible(), 'hero CTA visible');
await page.click('#startBtn');
await page.waitForSelector('#stepper .sdot');
const dots = await page.locator('#stepper .sdot').count();
ok(dots === 7, 'stepper has 7 nodes (' + dots + ')');
ok((await page.locator('#panel h2').textContent()).includes('Niche'), 'step 1 = Niche');
// Step 1 generate (no inputs)
await page.click('#genBtn');
await page.waitForFunction(() => { const o = document.querySelector('#mdOut'); return o && o.textContent && !/working/i.test(o.textContent); }, null, { timeout: 175000, polling: 800 });
ok((await page.locator('#mdOut').textContent()).length > 40, 'step1 deliverable rendered');
ok(await page.locator('#outBadge').isVisible(), 'output badge shown');
// table rendered from markdown?
ok((await page.locator('#mdOut').textContent()).length > 200, 'step1 deliverable substantial');
// Jump to step 3 (roadmap) via stepper dot #3
await page.locator('#stepper .sdot').nth(2).click();
ok((await page.locator('#panel h2').textContent()).includes('Roadmap'), 'step 3 = Roadmap');
// fill niche field
const nicheField = page.locator('#panel input').first();
await nicheField.fill('personal finance for freelancers');
await page.click('#genBtn');
await page.waitForFunction(() => document.querySelector('#calGrid') && document.querySelectorAll('#calGrid .vcard').length > 2, null, { timeout: 175000, polling: 800 });
const vcount = await page.locator('#calGrid .vcard').count();
ok(vcount >= 8, 'calendar grid rendered (' + vcount + ' cards, expect ~12)');
// sort control
await page.selectOption('#calSort', 'az');
ok((await page.locator('#calSort').inputValue()) === 'az', 'calendar sort switched to A→Z');
// density slider
await page.locator('#calDens').fill('2');
const cols = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--cols').trim());
ok(cols === '2', 'density slider set --cols=2 (' + cols + ')');
// export: toolbar present + plan doc content + real download
ok(await page.locator('#builderbar #pname').isVisible(), 'builder toolbar (rename) present');
const dlEnabled = !(await page.locator('#dlBtn').getAttribute('class')).includes('done0');
ok(dlEnabled, 'download button enabled after a step is done');
const planLen = await page.evaluate(() => planMarkdown().length);
const planHasTitle = await page.evaluate(() => /Step 3 · 90-Day Content Roadmap/.test(planMarkdown()));
ok(planLen > 300, 'plan markdown assembled (' + planLen + ' chars)');
ok(planHasTitle, 'plan includes generated step deliverable');
const [dl] = await Promise.all([ page.waitForEvent('download', { timeout: 10000 }), page.click('#dlBtn') ]);
ok(/^studio-zero-.*\.html$/.test(dl.suggestedFilename()), 'download fires: ' + dl.suggestedFilename());
// back home, saved card with created date+time
await page.click('#homeBtn');
await page.waitForSelector('#savedGrid .pcard');
const when = await page.locator('#savedGrid .pcard .when').first().textContent();
ok(/\d/.test(when) && /(AM|PM|:)/.test(when), 'saved card shows created date+time: ' + when.trim());
await page.screenshot({ path: '/tmp/sz-shot.png', fullPage: false });
await browser.close();
console.log(log.join('\n'));
console.log('\nconsole/page errors: ' + (errors.length ? '\n' + errors.join('\n') : 'NONE'));
process.exit(log.some(l => l.startsWith('FAIL')) || errors.length ? 1 : 0);
})().catch(e => { console.error('HARNESS ERROR', e); process.exit(2); });