- 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
120 lines
5.6 KiB
TypeScript
120 lines
5.6 KiB
TypeScript
import { expect, type Page, test } from "@playwright/test";
|
||
import { captureScreenshot, completeOnboarding, rpc, signup } from "./helpers";
|
||
|
||
function slackCard(page: Page) {
|
||
return page.getByRole("group", { name: "Slack connection" });
|
||
}
|
||
|
||
test("focus choice suggests apps and preserves a completed connection", async ({
|
||
page,
|
||
}, testInfo) => {
|
||
const stamp = Date.now();
|
||
await signup(page, `onboarding-${stamp}@rakazo.test`, "password12", "Robin");
|
||
// The main bot greets the user in the main conversation, where onboarding lands.
|
||
await completeOnboarding(page, undefined, { mainConversation: true });
|
||
|
||
await expect(
|
||
page.getByText("Hey Robin. Fresh start on my side, so I’ll keep this short."),
|
||
).toHaveCount(0);
|
||
await expect(page.getByText("What do you want me on first?", { exact: true })).toBeVisible();
|
||
await expect(page.getByTestId("focus-choice-day")).toBeVisible();
|
||
await expect(page.getByTestId("focus-choice-day")).toContainText("Day-to-day work");
|
||
await expect(page.getByTestId("focus-choice-research")).toContainText("Research & writing");
|
||
await expect(page.getByTestId("focus-choice-crew")).toContainText("Specialist crew");
|
||
// Onboarding questions offer a quiet way out instead of a dismiss button.
|
||
await expect(page.getByTestId("focus-choice-skip")).toBeVisible();
|
||
await expect(page.getByRole("button", { name: "Dismiss" })).toHaveCount(0);
|
||
await page.mouse.move(1, 1);
|
||
await captureScreenshot(page, testInfo, "01-focus-choice");
|
||
await captureScreenshot(page, testInfo, "choice-card-onboarding");
|
||
|
||
await page.getByTestId("focus-choice-day").click();
|
||
// The focus step suggests apps but must not rename the bot: the name the
|
||
// user chose during creation ("Chief") is preserved.
|
||
await expect(
|
||
page.getByTestId("bot-settings-trigger").getByText("Chief", { exact: true }),
|
||
).toBeVisible();
|
||
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Chief");
|
||
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
|
||
await expect(
|
||
page.getByText("Got it. Slack, Gmail, and Google Calendar are a good place to start."),
|
||
).toBeVisible();
|
||
await expect(page.getByText("Slack", { exact: true })).toBeVisible();
|
||
await expect(page.getByText("Gmail", { exact: true })).toBeVisible();
|
||
const connectionCards = page.getByRole("group", { name: / connection$/ });
|
||
await expect(connectionCards).toHaveCount(3);
|
||
const cardBoxes = await connectionCards.evaluateAll((cards) =>
|
||
cards.map((card) => {
|
||
const { bottom, top } = card.getBoundingClientRect();
|
||
return { bottom, top };
|
||
}),
|
||
);
|
||
expect(cardBoxes[1].top - cardBoxes[0].bottom).toBeGreaterThanOrEqual(8);
|
||
expect(cardBoxes[2].top - cardBoxes[1].bottom).toBeGreaterThanOrEqual(8);
|
||
// The next onboarding question follows the suggestions.
|
||
const goal = page.locator('[data-testid="choice-card"][data-step="goal"]');
|
||
await expect(goal).toContainText("What’s one thing you want to get done in the next few weeks?");
|
||
await slackCard(page).scrollIntoViewIfNeeded();
|
||
await page.mouse.move(1, 1);
|
||
await captureScreenshot(page, testInfo, "02-app-suggestions");
|
||
const authorizeButton = slackCard(page).getByRole("button", { name: "Authorize" });
|
||
const restingBackground = await authorizeButton.evaluate(
|
||
(button) => getComputedStyle(button).backgroundColor,
|
||
);
|
||
const accentBackground = await authorizeButton.evaluate((button) => {
|
||
const probe = document.createElement("span");
|
||
probe.style.backgroundColor = "var(--accent)";
|
||
button.append(probe);
|
||
const backgroundColor = getComputedStyle(probe).backgroundColor;
|
||
probe.remove();
|
||
return backgroundColor;
|
||
});
|
||
expect(accentBackground).not.toBe(restingBackground);
|
||
await authorizeButton.hover();
|
||
await expect
|
||
.poll(() => authorizeButton.evaluate((button) => getComputedStyle(button).backgroundColor))
|
||
.toBe(accentBackground);
|
||
await captureScreenshot(page, testInfo, "02-app-suggestions-authorize-hover");
|
||
|
||
await authorizeButton.click();
|
||
await expect(slackCard(page).getByText("Connected", { exact: true })).toBeVisible();
|
||
await expect(slackCard(page).getByText("Connected", { exact: true })).toHaveCSS("opacity", "1");
|
||
await expect
|
||
.poll(async () => {
|
||
const connections = await rpc<Array<{ provider: string; status: string }>>(
|
||
page,
|
||
"connections/list",
|
||
{},
|
||
);
|
||
return connections.some(
|
||
(connection) => connection.provider === "SLACK" && connection.status === "connected",
|
||
);
|
||
})
|
||
.toBe(true);
|
||
await page.mouse.move(1, 1);
|
||
await captureScreenshot(page, testInfo, "03-slack-connected");
|
||
|
||
await page.reload();
|
||
await expect(slackCard(page).getByText("Connected", { exact: true })).toBeVisible();
|
||
await page.mouse.move(1, 1);
|
||
await captureScreenshot(page, testInfo, "04-connected-after-reload");
|
||
});
|
||
|
||
test("choice refresh failures leave options available for retry", async ({ page }) => {
|
||
await signup(page, `choice-refresh-${Date.now()}@rakazo.test`, "password12", "Choice Retry");
|
||
await completeOnboarding(page, undefined, { mainConversation: true });
|
||
const choice = page.getByTestId("focus-choice-day");
|
||
await expect(choice).toBeEnabled();
|
||
// Keep the existing choice rendered while its save succeeds and navigation refresh fails.
|
||
await page.route("**/rpc/onboarding/choose", (route) =>
|
||
route.fulfill({
|
||
status: 200,
|
||
contentType: "application/json",
|
||
body: JSON.stringify({ json: { ok: true } }),
|
||
}),
|
||
);
|
||
await page.route("**/rpc/spaces/list", (route) => route.abort());
|
||
await Promise.all([page.waitForRequest("**/rpc/spaces/list"), choice.click()]);
|
||
await expect(choice).toBeEnabled();
|
||
});
|