/** * Cycle 12 deep-flow smoke — walks Hub → Manual Entry → submit, * Hub → History. Captures screenshots along the way and asserts * each screen renders the expected layout. */ import { chromium } from "playwright"; import { mkdirSync } from "node:fs"; const BASE = process.env.PLAYWRIGHT_URL ?? "http://localhost:4000"; const OUT = "tests/_artifacts/cycle12"; mkdirSync(OUT, { recursive: true }); // Use worker_id 5a83c1a3-... (Worker 1) but spoof the brand so we // hit the Tuxedo brandId the running server expects. const COOKIE_VALUE = [ "5a83c1a3-8eb5-44c4-bb66-323e72d38c91", "smoke-" + Math.random().toString(36).slice(2, 10), new Date(Date.now() + 12 * 3600 * 1000).toISOString(), "Worker 1", "time_admin", "en", // Brand 64294306 is the one the server logs show loading from — // matches `TUXEDO_BRAND_ID` constant. "11111111-1111-1111-1111-111111111111", ].join("|"); async function snap(page, name) { await page.screenshot({ path: `${OUT}/${name}.png`, fullPage: false }); } async function expectText(page, regex, label) { const text = await page.locator("body").innerText(); if (!regex.test(text)) { console.error(`✗ ${label}: didn't match ${regex}`); console.error("body snapshot:\n" + text.slice(0, 1500)); process.exit(1); } console.log(`✓ ${label}`); } async function main() { const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ viewport: { width: 412, height: 900 }, deviceScaleFactor: 2, }); await ctx.addCookies([ { name: "time_tracking_session", value: COOKIE_VALUE, url: BASE, httpOnly: false, secure: false, sameSite: "Lax", }, ]); const page = await ctx.newPage(); page.on("pageerror", (e) => console.error("PAGE ERROR:", e.message)); page.on("console", (m) => { if (m.type() === "error") console.error("CONSOLE ERR:", m.text()); }); // ── 1) Hub await page.goto(`${BASE}/tuxedo/time-clock`, { waitUntil: "domcontentloaded" }); await page.waitForTimeout(2000); await snap(page, "01_hub"); await expectText(page, /Recent Activity/i, "Hub renders 'Recent activity' tile"); await expectText(page, /Manual entry/i, "Hub renders 'Manual entry' tile"); // ── 2) Manual entry await page.locator('button:has-text("Manual entry")').first().click(); await page.waitForTimeout(1000); await snap(page, "02_manual_entry"); await expectText(page, /Manual entry/i, "Manual entry screen title"); await expectText(page, /WHEN/i, "Manual entry 'WHEN' section"); await expectText(page, /WHY/i, "Manual entry 'WHY' section"); // Try to submit empty — should show reason error // Reason field is the second text input on the page (first is Task) const reasonInput = page.locator('input[placeholder*="Required"]').first(); await reasonInput.fill("Forgot to clock in this morning"); await snap(page, "03_manual_filled"); await expectText(page, /Submit entry/i, "Submit button visible"); // ── 3) Recent activity await page.locator('button[aria-label*="Atr" i], button[aria-label*="Back"]').first().click().catch(() => {}); await page.waitForTimeout(800); // We may not always find the back button — fall back to going to hub via reload await page.goto(`${BASE}/tuxedo/time-clock`, { waitUntil: "domcontentloaded" }); await page.waitForTimeout(2000); await page.locator('button:has-text("Recent activity")').first().click(); await page.waitForTimeout(1500); await snap(page, "04_history"); await expectText(page, /Recent Activity|Actividad reciente/i, "History title visible"); await browser.close(); console.log("\n✓ Cycle 12 deep smoke passed."); } main().catch((e) => { console.error(e); process.exit(1); });