evOo-local/apps/web/e2e/user-profile.spec.ts
Claude 97210805be
test(onboarding): walk the onboarding questions end to end; mobile reads cards as text
- onboarding-intake.spec walks focus, goal (after typing into the composer, which still
  works), check-ins, quiet hours and the notifications card when the browser offers it,
  with the onboarding-intake-goal, -rhythm and -done screenshots.
- completeOnboarding and the wizard specs drop the preferences step; the scan step is
  optional (skipped when it asks, otherwise it advances by itself).
- The user-profile spec now checks the browser-filled profile and the focus fact; the
  conversation spec checks the shorter app suggestion and the following goal question.
- The hover spec measures a reply that fills its row (a bot row's rail sits at the row's
  end, so the old long onboarding line was what made it pass) and scopes Send to the
  composer, since an open goal card has its own.
- Mobile shows a choice card as its question and answer instead of an empty bubble.
- The focus options drop their unused summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 14:09:56 +00:00

105 lines
4.6 KiB
TypeScript

/**
* E2E: onboarding fills the user profile without asking (time zone, locale and language from
* the browser) and records the focus answer as a fact; Settings → About you shows every fact
* with its origin, confirms a pending one, and forgets one or all of them.
*/
import { expect, test } from "@playwright/test";
import { captureScreenshot, openUserSettings, rpc, signup, waitForChatComposer } from "./helpers";
test.use({ timezoneId: "Europe/Vienna" });
test.beforeEach(async ({ page }) => {
// Walk the full emoji-first flow.
await page.route("**/rpc/integrationSetup/get", (route) =>
route.fulfill({
json: {
json: {
canConfigure: true,
needsSetup: false,
webUrl: "https://example.test/integrations/setup",
providers: [{ id: "composio", configured: true }],
},
},
}),
);
});
test("onboarding feeds the profile; facts can be confirmed and forgotten", async ({
page,
}, testInfo) => {
const stamp = Date.now();
await signup(page, `profile-${stamp}@rakazo.test`, "password12", "Profile Tester");
await expect(page.getByRole("heading", { name: /Design your Oo/i })).toBeVisible({
timeout: 30_000,
});
await page.getByRole("button", { name: "Continue", exact: true }).click();
await expect(page.getByRole("heading", { name: /Choose your language/i })).toBeVisible();
await page.getByRole("button", { name: "English", exact: true }).click();
await expect(page.getByRole("heading", { name: /Pick a voice/i })).toBeVisible();
await page.getByRole("button", { name: "Skip", exact: true }).click();
await page.waitForURL(/\/app\/g\/[^/]+/, { timeout: 30_000 });
await waitForChatComposer(page);
// The main bot asks the rest in the main conversation; the focus answer becomes a fact.
await page.getByTestId("focus-choice-day").click();
await expect(page.locator('[data-testid="choice-card"][data-step="goal"]')).toBeVisible();
// The answers no longer overwrite a bot's instructions.
const bots = await rpc<Array<{ instructions: string }>>(page, "bots/list", null);
expect(bots.length).toBeGreaterThan(0);
for (const bot of bots) expect(bot.instructions).not.toContain("User Context");
// The account name is used as is; onboarding no longer asks for it again.
expect((await rpc<{ name: string }>(page, "me", null)).name).toBe("Profile Tester");
// Sensitive facts from anywhere but the user wait for confirmation.
await rpc(page, "profile/update", {
addFacts: [{ category: "health", value: "Allergic to peanuts", source: "onboarding" }],
});
const settings = await openUserSettings(page, "profile");
await expect(settings).toHaveAttribute("data-settings-section", "profile");
const profile = settings.getByTestId("user-profile-settings");
await expect(profile.getByTestId("profile-field-timeZone")).toContainText("Europe/Vienna");
await expect(profile.getByTestId("profile-field-language")).toContainText("English");
await expect(profile.getByTestId("profile-field-locale")).toContainText("United States");
const focus = profile
.getByTestId("user-fact")
.filter({ hasText: "Wants help with day-to-day work first" });
await expect(focus).toContainText("Onboarding");
await expect(profile.getByTestId("user-facts-goals")).toContainText(
"Wants help with day-to-day work first",
);
const allergy = profile.getByTestId("user-fact").filter({ hasText: "Allergic to peanuts" });
await expect(allergy).toHaveAttribute("data-status", "pending");
await captureScreenshot(page, testInfo, "user-profile-pending");
await allergy.getByRole("button", { name: "Confirm" }).click();
await expect(allergy).toHaveAttribute("data-status", "active");
await expect(allergy.getByRole("button", { name: "Confirm" })).toHaveCount(0);
await focus.getByRole("button", { name: "Delete" }).click();
await expect(focus).toHaveCount(0);
await captureScreenshot(page, testInfo, "user-profile-settings");
const afterDelete = await rpc<{ facts: Array<{ value: string; status: string }> }>(
page,
"profile/get",
null,
);
expect(afterDelete.facts).toEqual(
expect.not.arrayContaining([
expect.objectContaining({ value: "Wants help with day-to-day work first" }),
]),
);
expect(afterDelete.facts).toContainEqual(
expect.objectContaining({ value: "Allergic to peanuts", status: "active" }),
);
await profile.getByRole("button", { name: "Forget everything" }).click();
await page.getByRole("alertdialog").getByRole("button", { name: "Forget", exact: true }).click();
await expect(profile).toContainText("Nothing yet");
expect(await rpc<{ facts: unknown[] }>(page, "profile/get", null)).toMatchObject({ facts: [] });
});