The onboarding scan started on its own, discarded what it found, claimed to read email and calendars, simulated progress for everyone who could not scan, and its project creation always failed (wrong Space fields and a space lookup by title across tenants). Discovery now asks first and creates nothing without a choice: - The scan step appears only for the deployment owner when ONBOARDING_SCAN_HOME is set; everyone else skips it. The simulated scan is gone. - It shows the folder and asks. Only directory listings are read: git projects up to four levels deep, their languages and a few tools (Docker, GitHub Actions, Terraform). Symlinks are never followed and each chosen folder is resolved again before use, so nothing outside the folder is reached. Results stay with the account that scanned. - Found projects are a checklist (none preselected); found languages and tools are facts (preselected). "Add" creates one project space per chosen project with createSpaceForMember and wireProjectWorkspace in the user's own organization, reusing the user's own space already linked to that folder, and saves the chosen facts as onboarding facts under the profile lock and fact limit. - When a connection becomes connected (direct connect, authorization round trip, or catalog reconciliation), "Uses <App>" is remembered as a tools fact from the connector. Revoking keeps it. The E2E harness now points ONBOARDING_SCAN_HOME at a fixture folder and starts without a deployment owner, so owner-only onboarding steps appear only in specs that take the seat through /__e2e/deployment-owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
77 lines
3.6 KiB
TypeScript
77 lines
3.6 KiB
TypeScript
/**
|
|
* E2E: the deployment owner's onboarding asks before scanning the fixture home folder the
|
|
* harness configures, then adds only the projects and facts the user picks.
|
|
*/
|
|
import { expect, type Page, test } from "@playwright/test";
|
|
import type { SpaceNavigation, UserProfileState } from "@rakazo/contracts";
|
|
import { captureScreenshot, rpc, signup } from "./helpers";
|
|
|
|
const api = process.env.API_URL ?? "http://127.0.0.1:3110";
|
|
|
|
/** Advances whichever wizard step is showing until the scan step is reached. */
|
|
async function walkToScanStep(page: Page) {
|
|
const surface = page.locator('[data-rakazo-surface="onboarding"]');
|
|
await expect(async () => {
|
|
if ((await surface.getAttribute("data-onboarding-step")) === "scan") return;
|
|
const skip = page.getByRole("button", { name: /^Skip/ }).first();
|
|
const next = (await skip.isVisible())
|
|
? skip
|
|
: page.getByRole("button", { name: "Continue", exact: true });
|
|
await next.click({ timeout: 2_000 });
|
|
await expect(surface).toHaveAttribute("data-onboarding-step", "scan", { timeout: 1_000 });
|
|
}).toPass({ timeout: 60_000 });
|
|
}
|
|
|
|
test("asks before looking for projects and adds only what the user picks", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const email = `discovery-${Date.now()}@rakazo.test`;
|
|
await signup(page, email, "password12", "Discovery");
|
|
await page.waitForURL(/\/onboarding/, { timeout: 20_000 });
|
|
// The harness starts without a deployment owner; this spec takes the seat for its run.
|
|
const owner = await page.request.post(`${api}/__e2e/deployment-owner`, { data: { email } });
|
|
expect(owner.ok()).toBe(true);
|
|
const { previousOwnerUserId } = (await owner.json()) as { previousOwnerUserId: string | null };
|
|
try {
|
|
await page.goto("/onboarding", { waitUntil: "domcontentloaded" });
|
|
await walkToScanStep(page);
|
|
|
|
await expect(page.getByRole("heading", { name: "Look for projects here?" })).toBeVisible();
|
|
await expect(page.getByText(/rakazo-e2e-scan-home-/)).toBeVisible();
|
|
await captureScreenshot(page, testInfo, "onboarding-discovery-consent");
|
|
await page.getByRole("button", { name: "Look", exact: true }).click();
|
|
|
|
const projects = page.getByRole("group", { name: "Add as projects" });
|
|
await expect(projects.getByRole("checkbox")).toHaveCount(2);
|
|
const atlas = projects.getByRole("checkbox", { name: /atlas/ });
|
|
await expect(atlas).not.toBeChecked();
|
|
await expect(projects.getByRole("checkbox", { name: /garden/ })).not.toBeChecked();
|
|
await atlas.check();
|
|
const facts = page.getByRole("group", { name: "Add to About you" });
|
|
await expect(facts.getByRole("checkbox", { name: "TypeScript" })).toBeChecked();
|
|
await facts.getByRole("checkbox", { name: "Python" }).uncheck();
|
|
await captureScreenshot(page, testInfo, "onboarding-discovery-results");
|
|
await page.getByRole("button", { name: "Add", exact: true }).click();
|
|
await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 });
|
|
|
|
const navigation = await rpc<SpaceNavigation>(page, "spaces/list", {});
|
|
const names = navigation.spaces.map((space) => space.name);
|
|
expect(names).toContain("atlas");
|
|
expect(names).not.toContain("garden");
|
|
expect(navigation.spaces.find((space) => space.name === "atlas")?.workspace?.path).toMatch(
|
|
/code\/atlas$/,
|
|
);
|
|
const values = (await rpc<UserProfileState>(page, "profile/get", {})).facts.map(
|
|
(fact) => fact.value,
|
|
);
|
|
expect(values).toEqual(
|
|
expect.arrayContaining(["Codes in TypeScript", "Uses Docker", "Uses GitHub Actions"]),
|
|
);
|
|
expect(values).not.toContain("Codes in Python");
|
|
} finally {
|
|
await page.request.post(`${api}/__e2e/deployment-owner`, {
|
|
data: { userId: previousOwnerUserId },
|
|
});
|
|
}
|
|
});
|