import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { _electron as electron, expect, test } from '@playwright/test' const here = dirname(fileURLToPath(import.meta.url)) const MAIN = join(here, '..', 'out', 'main', 'index.js') // Opt-in only (GITEA_LIVE=1). Launches WITHOUT COMMITEA_E2E so the main process // reconciles the real repo via .env.local, and asserts real data renders. test.describe('live backlog', () => { test('The pot + Focus render real gitea data', async () => { test.skip(!process.env.GITEA_LIVE, 'GITEA_LIVE not set — opt-in live test') const app = await electron.launch({ args: [MAIN], env: { ...process.env } }) const win = await app.firstWindow() await win.waitForLoadState('domcontentloaded') const rail = win.getByRole('navigation', { name: 'Primary' }) // The pot — waiting here also lets the reconcile (issues + deps + timelines) complete await rail.getByRole('button', { name: 'The pot' }).click() await expect(win.getByText('Gitea read client behind an injected fetch')).toBeVisible({ timeout: 20000 }) // Lifecycle inference (#5): columns come from the real event stream — merged // work lands in Done, so that column is non-empty (proves timelines drove it, // not the three-column fallback which would still show closed issues in Done). await expect(win.getByText('Done', { exact: true })).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-board.png'), fullPage: true, animations: 'disabled' }) // Back to Focus — the deterministic scheduler's real Now/Next/Later. These // rationale phrases are emitted only by the scheduler, never by the demo fixture. await rail.getByRole('button', { name: 'Morning service' }).click() await expect(win.getByText(/on the critical path|unblocks #|waits on #|· ready/).first()).toBeVisible() // Real Monte Carlo cone — this headline is emitted only for real forecasts. await expect(win.getByText(/80% of the open backlog lands by/)).toBeVisible() await expect(win.getByText(/Cold-start priors/)).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-focus.png'), fullPage: true, animations: 'disabled' }) // Runway → calibration surface, fitted from real closed-issue actuals (#1). // With <20 estimated closes the repo is honestly cold-start; the note proves // the fit ran on real data, not the fixture's "calibrated on 27". await rail.getByRole('button', { name: 'Runway' }).click() await expect( win.getByText(/cold-start priors · \d+\/20 closed issues estimated|calibrated on \d+ closed/), ).toBeVisible() // Real per-milestone forecasts — these milestone names come from gitea, not the // fixture (which lists Beta / Pilot-ready / v1.0). Only milestones with open // scope appear (P2 dropped off once all its work shipped — correct). await expect(win.getByText(/P5 — Dogfood/)).toBeVisible() // Real capacity config from pm-state (christian/stephen), not the fixture (Stephen/Ana K.) await expect(win.getByText('christian', { exact: true })).toBeVisible() await expect(win.getByText(/pd\/day/).first()).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-runway.png'), fullPage: true, animations: 'disabled' }) // Milestone drill-in — clicking a real milestone opens its real detail await win.getByText(/P5 — Dogfood/).click() await expect(win.getByRole('heading', { name: 'P5 — Dogfood + polish' })).toBeVisible() await expect(win.getByText(/\d+ issues · est \d+d/)).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-milestone.png'), fullPage: true, animations: 'disabled' }) await rail.getByRole('button', { name: 'Runway' }).click() await win.getByRole('button', { name: 'Full report' }).click() await expect(win.getByText(/cold-start · \d+\/20|curve active · n ≥ 20/)).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-calibration.png'), fullPage: true, animations: 'disabled' }) // Write path (apply_changes): open the top scheduled issue, propose an // estimate change, and confirm the propose-approve diff renders — then CANCEL // so the live run never mutates the real repo (the PUT is unit-tested; a // one-off change→revert verified it against gitea manually). await rail.getByRole('button', { name: 'Morning service' }).click() await win.getByRole('main').getByRole('link').first().click() await expect(win.getByText(/· stephen\/commitea/)).toBeVisible() await win.getByRole('button', { name: 'Adjust' }).click() await expect(win.getByText('Adjust estimate & priority')).toBeVisible() await win.getByRole('combobox').first().selectOption('est/8d') await expect(win.getByText('Proposed label change')).toBeVisible() await expect(win.getByText(/est\/8d/).last()).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-apply-change.png'), fullPage: true, animations: 'disabled' }) await win.getByRole('button', { name: 'Cancel' }).click() // no mutation await app.close() }) })