Rebased onto main with Expo SDK 57 typecheck fixes. Voice options expose stable test ids; Shell keeps /app/heute across bootstrap and redirects legacy /app/heute bot URLs. Heute coach E2E asserts URL after reload. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
149 lines
6.3 KiB
TypeScript
149 lines
6.3 KiB
TypeScript
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, 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<CoachToday>(page, "coach/today", {});
|
|
const goal = await rpc<Goal>(page, "coach/goals/create", {
|
|
title: "Ship the beta",
|
|
targetDate: shiftDate(date, 30),
|
|
});
|
|
const habit = await rpc<Habit>(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<CoachRhythm>(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<Routine[]>(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 page.getByTitle("Agent computer").click();
|
|
await expect(page.getByText("Morning brief", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("Weekly review", { exact: true })).toBeVisible();
|
|
});
|