import type { Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import type { CoachRhythm, CoachToday, Goal, Habit, Routine } from "@rakazo/contracts"; import { captureScreenshot, completeOnboarding, openAgentOverview, openMainBotThread, rpc, signup, } from "./helpers"; /** The nav button reads "Today" or "Today, N need you". */ function openHeute(page: Page) { return page.getByRole("button", { name: /^Today(,|$)/ }).click(); } function shiftDate(date: string, days: number) { return new Date(Date.parse(`${date}T00:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10); } test("Heute shows native brief instead of Mission Control iframe", async ({ page }, testInfo) => { const stamp = Date.now(); await signup(page, `heute-${stamp}@rakazo.test`, "password12", "Heute"); await completeOnboarding(page); await openHeute(page); await expect(page).toHaveURL(/\/app\/heute$/); await page.reload(); await expect(page.getByTestId("heute-brief")).toBeVisible(); await expect(page).toHaveURL(/\/app\/heute$/); const brief = page.getByTestId("heute-brief"); await expect(brief).toBeVisible(); await expect(page.locator('iframe[src="http://127.0.0.1:3088/"]')).toHaveCount(0); await expect(page.getByText("evOo Mission Control")).toHaveCount(0); await expect(page.getByTestId("heute-mission-control-link")).toHaveCount(0); await expect(page.getByTestId("heute-status")).toBeVisible(); // Nothing tracked yet: no coach sections. await expect(page.getByTestId("heute-coach-brief")).toHaveCount(0); await expect(page.getByTestId("heute-habits")).toHaveCount(0); await captureScreenshot(page, testInfo, "heute-native-brief"); }); test("Heute shows the coach's brief, habits and goals, checks habits off and sets check-ins", async ({ page, }, testInfo) => { const stamp = Date.now(); await signup(page, `heute-coach-${stamp}@rakazo.test`, "password12", "Heute Coach"); await completeOnboarding(page); // The main bot writes today's brief through its tool. const composer = page.getByRole("combobox", { name: "Message" }); await composer.fill("write today's brief: Two calm hours on the beta, then a walk."); await page.keyboard.press("Enter"); await expect(page.getByText("today's brief is ready.").first()).toBeVisible({ timeout: 30_000 }); const { date } = await rpc(page, "coach/today", {}); const goal = await rpc(page, "coach/goals/create", { title: "Ship the beta", targetDate: shiftDate(date, 30), }); const habit = await rpc(page, "coach/habits/create", { title: "Walk 20 minutes" }); for (const day of [1, 2]) { await rpc(page, "coach/checkIns/log", { kind: "habit", habitId: habit.id, date: shiftDate(date, -day), done: true, }); } await openHeute(page); await expect(page.getByTestId("heute-coach-brief")).toHaveText( "Two calm hours on the beta, then a walk.", ); await expect(page.getByTestId(`heute-goal-${goal.id}`)).toContainText("Ship the beta"); const habitRow = page.getByTestId(`heute-habit-${habit.id}`); await expect(habitRow).toContainText("Walk 20 minutes"); await expect(page.getByTestId(`heute-habit-streak-${habit.id}`)).toHaveText("2 days"); const checkbox = habitRow.getByRole("checkbox"); await expect(checkbox).not.toBeChecked(); const logged = page.waitForResponse( (response) => response.url().includes("/rpc/coach/checkIns/log") && response.ok(), ); await checkbox.click(); await logged; await expect(checkbox).toBeChecked(); await expect(page.getByTestId(`heute-habit-streak-${habit.id}`)).toHaveText("3 days"); await captureScreenshot(page, testInfo, "heute-coach"); await page.reload({ waitUntil: "domcontentloaded" }); await expect(page).toHaveURL(/\/app\/heute$/); await expect(page.getByTestId("heute-brief")).toBeVisible(); await expect(page.getByTestId(`heute-habit-${habit.id}`).getByRole("checkbox")).toBeChecked(); // Check-in times live behind one compact control. const rhythm = page.getByTestId("heute-rhythm"); await expect(page.getByTestId("heute-coach-brief")).toBeVisible(); await rhythm.scrollIntoViewIfNeeded(); await expect(rhythm).toBeVisible({ timeout: 30_000 }); const rhythmLoaded = page.waitForResponse( (response) => response.url().includes("/rpc/coach/rhythm/get") && response.ok(), ); await rhythm.click(); await rhythmLoaded; const panel = page.getByTestId("heute-rhythm-panel"); await expect(panel).toBeVisible(); const morning = panel.getByTestId("heute-rhythm-morning"); await expect(morning.getByRole("switch")).not.toBeChecked(); const enabled = page.waitForResponse( (response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(), ); await morning.getByRole("switch").click(); await enabled; const time = morning.locator('input[type="time"]'); await expect(time).toHaveValue("08:00"); const moved = page.waitForResponse( (response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(), ); await time.fill("07:30"); await moved; await expect(time).toHaveValue("07:30"); const weekly = panel.getByTestId("heute-rhythm-weekly"); const weeklyOn = page.waitForResponse( (response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(), ); await weekly.getByRole("switch").click(); await weeklyOn; await expect(weekly.getByRole("combobox")).toHaveValue("0"); await captureScreenshot(page, testInfo, "heute-rhythm"); expect(await rpc(page, "coach/rhythm/get", {})).toEqual({ morning: "07:30", evening: null, weekly: { day: 0, time: "18:00" }, }); const main = await rpc<{ id: string }>(page, "bots/ensureMain", {}); const routines = await rpc(page, "routines/list", { botId: main.id }); expect(routines.map(({ name, coachKey }) => ({ name, coachKey }))).toEqual( expect.arrayContaining([ { name: "Morning brief", coachKey: "morning" }, { name: "Weekly review", coachKey: "weekly" }, ]), ); expect(routines).toHaveLength(2); // The main bot's routine list names the check-ins by their key, in the UI language. await openMainBotThread(page); await openAgentOverview(page); await expect(page.getByText("Morning brief", { exact: true })).toBeVisible(); await expect(page.getByText("Weekly review", { exact: true })).toBeVisible(); });