← 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); });