feat: enable onboarding-v3 globally and update e2e tests (#27922)
* feat: enable onboarding-v3 globally and update e2e tests - Add migration to set onboarding-v3 feature flag enabled=true - Rewrite onboarding.e2e.ts for v3 flow (Plan Selection → Personal Settings → Calendar) - Update URL assertions in signup, team-invitation, org-invitation, ab-tests-redirect, and auth tests to accept both old and new onboarding paths Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: use pathname-only matching in waitForURL to prevent false matches on callbackUrl query param Co-Authored-By: sean@cal.com <Sean@brydon.io> * use test IDs --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
9 files changed
+61
-88
No files matched your search
@@ -151,6 +151,7 @@ export const OnboardingView = ({ userEmail }: OnboardingViewProps) => {
|
||||
footer={
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button
|
||||
data-testid="onboarding-continue-btn"
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
|
||||
@@ -117,6 +117,7 @@ export const PersonalCalendarView = ({ userEmail }: PersonalCalendarViewProps) =
|
||||
{t("onboarding_skip_for_now")}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="onboarding-continue-btn"
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
@@ -77,12 +76,12 @@ test.describe("apps/ A/B tests", () => {
|
||||
await expect(locator).toBeDefined();
|
||||
});
|
||||
|
||||
test("should render the /getting-started", async ({ page, users }) => {
|
||||
test("should render the /onboarding/personal/calendar", async ({ page, users }) => {
|
||||
const user = await users.create({ completedOnboarding: false, name: null });
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
await page.goto("/getting-started/connected-calendar");
|
||||
await page.goto("/onboarding/personal/calendar");
|
||||
|
||||
const locator = page.getByText("Apple Calendar");
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "../lib/fixtures";
|
||||
import { submitAndWaitForResponse } from "../lib/testUtils";
|
||||
|
||||
@@ -69,8 +68,7 @@ test.describe("Can signup from a team invite", async () => {
|
||||
await newPage.fill('input[name="password"]', testUser.password);
|
||||
await submitAndWaitForResponse(newPage, "/api/auth/signup", { expectedStatusCode: 201 });
|
||||
// Since it's a new user, it should be redirected to the onboarding
|
||||
await newPage.locator('text="Welcome to Cal.com!"').waitFor();
|
||||
await expect(newPage.locator('text="Welcome to Cal.com!"')).toBeVisible();
|
||||
await newPage.waitForURL(/\/(getting-started|onboarding\/(getting-started|personal\/settings))/);
|
||||
// We don't need the new browser anymore
|
||||
await newPage.close();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
@@ -10,80 +8,39 @@ test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
test.describe("Onboarding", () => {
|
||||
const testOnboarding = (identityProvider: IdentityProvider) => {
|
||||
test(`Onboarding Flow - ${identityProvider} user`, async ({ page, users }) => {
|
||||
test(`Onboarding Flow (v3) - ${identityProvider} user`, async ({ page, users }) => {
|
||||
const user = await users.create({
|
||||
completedOnboarding: false,
|
||||
name: null,
|
||||
identityProvider,
|
||||
});
|
||||
await user.apiLogin();
|
||||
await page.goto("/getting-started");
|
||||
// tests whether the user makes it to /getting-started
|
||||
// after login with completedOnboarding false
|
||||
await page.waitForURL("/getting-started");
|
||||
await expect(page.locator('text="Connect your calendar"').first()).toBeVisible(); // Fix race condition
|
||||
await page.goto("/onboarding/getting-started");
|
||||
await page.waitForURL("/onboarding/getting-started");
|
||||
|
||||
await test.step("step 1 - User Settings", async () => {
|
||||
const onboarding = page.getByTestId("onboarding");
|
||||
const form = onboarding.locator("form").first();
|
||||
const submitButton = form.getByTestId("connect-calendar-button");
|
||||
await test.step("step 1 - Plan Selection", async () => {
|
||||
await expect(page.getByTestId("onboarding-continue-btn")).toBeVisible();
|
||||
await page.getByTestId("onboarding-continue-btn").click();
|
||||
await page.waitForURL(/.*\/onboarding\/personal\/settings/);
|
||||
});
|
||||
|
||||
// Check required fields
|
||||
await submitButton.click();
|
||||
await expect(page.locator("data-testid=required")).toBeVisible();
|
||||
|
||||
// happy path
|
||||
await form.locator("input[name=username]").fill("new user onboarding");
|
||||
await form.getByLabel("Full name").fill("new user 2");
|
||||
await form.locator("input[role=combobox]").click();
|
||||
await page
|
||||
.locator("*")
|
||||
.filter({ hasText: /^Europe\/London/ })
|
||||
.first()
|
||||
.click();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(page).toHaveURL(/.*connected-calendar/);
|
||||
await test.step("step 2 - Personal Settings", async () => {
|
||||
const nameInput = page.locator('input[name="name"]');
|
||||
await nameInput.fill("new user 2");
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await page.waitForURL(/.*\/onboarding\/personal\/calendar/);
|
||||
|
||||
const userComplete = await user.self();
|
||||
expect(userComplete.name).toBe("new user 2");
|
||||
});
|
||||
|
||||
await test.step("step 2 - Connected Calendar", async () => {
|
||||
const isDisabled = await page.locator("button[data-testid=save-calendar-button]").isDisabled();
|
||||
await expect(isDisabled).toBe(true);
|
||||
// tests skip button, we don't want to test entire flow.
|
||||
await page.locator("button[data-testid=skip-step]").click();
|
||||
await expect(page).toHaveURL(/.*connected-video/);
|
||||
});
|
||||
|
||||
await test.step("step 3 - Connected Video", async () => {
|
||||
const isDisabled = await page.locator("button[data-testid=save-video-button]").isDisabled();
|
||||
await expect(isDisabled).toBe(true);
|
||||
// tests skip button, we don't want to test entire flow.
|
||||
await page.locator("button[data-testid=skip-step]").click();
|
||||
await expect(page).toHaveURL(/.*setup-availability/);
|
||||
});
|
||||
|
||||
await test.step("step 4 - Setup Availability", async () => {
|
||||
const isDisabled = await page.locator("button[data-testid=save-availability]").isDisabled();
|
||||
await expect(isDisabled).toBe(false);
|
||||
// same here, skip this step.
|
||||
|
||||
await page.locator("button[data-testid=save-availability]").click();
|
||||
await expect(page).toHaveURL(/.*user-profile/);
|
||||
});
|
||||
|
||||
await test.step("step 5- User Profile", async () => {
|
||||
const onboarding = page.getByTestId("onboarding");
|
||||
const form = onboarding.locator("form").first();
|
||||
const submitButton = form.getByRole("button", { name: "Finish setup and get started" });
|
||||
await submitButton.click();
|
||||
// should redirect to /event-types after onboarding
|
||||
await page.waitForURL("/event-types");
|
||||
await test.step("step 3 - Calendar Connection", async () => {
|
||||
await expect(page.getByTestId("onboarding-continue-btn")).toBeVisible();
|
||||
await page.getByTestId("onboarding-continue-btn").click();
|
||||
await page.waitForURL("/event-types**");
|
||||
|
||||
const userComplete = await user.self();
|
||||
expect(userComplete.bio?.replace("<p><br></p>", "").length).toBe(0);
|
||||
expect(userComplete.completedOnboarding).toBe(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import prisma from "@calcom/prisma";
|
||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { Browser, Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { test } from "../lib/fixtures";
|
||||
import { moveUserToOrg } from "../lib/orgMigration";
|
||||
import { bookTeamEvent, doOnOrgDomain, expectPageToBeNotFound, getInviteLink } from "../lib/testUtils";
|
||||
@@ -505,7 +502,10 @@ async function signupFromInviteLink({
|
||||
await inviteLinkPage.locator("input[name=email]").fill(email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await inviteLinkPage.waitForURL("/getting-started");
|
||||
await inviteLinkPage.waitForURL((url) => {
|
||||
const path = url.pathname;
|
||||
return /\/(getting-started|onboarding\/(getting-started|personal\/settings))/.test(path);
|
||||
});
|
||||
return { email };
|
||||
}
|
||||
|
||||
@@ -540,7 +540,10 @@ export async function signupFromEmailInviteLink({
|
||||
// Check required fields
|
||||
await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await signupPage.locator("button[type=submit]").click();
|
||||
await signupPage.waitForURL("/getting-started?from=signup");
|
||||
await signupPage.waitForURL((url) => {
|
||||
const path = url.pathname;
|
||||
return /\/(getting-started|onboarding\/(getting-started|personal\/settings))/.test(path);
|
||||
});
|
||||
await context.close();
|
||||
await signupPage.close();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import process from "node:process";
|
||||
import { APP_NAME, IS_MAILHOG_ENABLED, IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Browser, Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { hashSync } from "bcryptjs";
|
||||
import { randomBytes } from "node:crypto";
|
||||
|
||||
import { APP_NAME, IS_PREMIUM_USERNAME_ENABLED, IS_MAILHOG_ENABLED } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { localize } from "./lib/localize";
|
||||
import { getEmailsReceivedByUser, getInviteLink } from "./lib/testUtils";
|
||||
@@ -188,7 +187,10 @@ test.describe("Email Signup Flow Test", async () => {
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
// Should successfully login with original password
|
||||
await expect(page).toHaveURL(/\/(getting-started|event-types|teams)/, { timeout: 8000 });
|
||||
await expect(page).toHaveURL(
|
||||
/\/(getting-started|onboarding\/getting-started|onboarding\/personal\/settings|event-types|teams)/,
|
||||
{ timeout: 8000 }
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
await prisma.verificationToken.deleteMany({ where: { token } });
|
||||
@@ -393,7 +395,10 @@ test.describe("Email Signup Flow Test", async () => {
|
||||
// Check required fields
|
||||
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await newPage.locator("button[type=submit]").click();
|
||||
await newPage.waitForURL("/getting-started?from=signup");
|
||||
await newPage.waitForURL((url) => {
|
||||
const path = url.pathname;
|
||||
return /\/(getting-started|onboarding\/(getting-started|personal\/settings))/.test(path);
|
||||
});
|
||||
await newPage.close();
|
||||
await context.close();
|
||||
});
|
||||
@@ -524,7 +529,9 @@ test.describe("Email Signup Flow Test", async () => {
|
||||
await page.locator('input[name="password"]').fill("Password99!");
|
||||
await page.getByTestId("signup-submit-button").click();
|
||||
|
||||
await expect(page).toHaveURL(/\/getting-started|\/auth\/verify-email/);
|
||||
await expect(page).toHaveURL(
|
||||
/\/(getting-started|onboarding\/(getting-started|personal\/settings))|\/auth\/verify-email/
|
||||
);
|
||||
|
||||
const createdUser = await prisma.user.findUnique({
|
||||
where: { email: userToCreate.email },
|
||||
@@ -633,6 +640,9 @@ async function signupFromInviteLink({
|
||||
await inviteLinkPage.locator("input[name=email]").fill(email);
|
||||
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await inviteLinkPage.locator("button[type=submit]").click();
|
||||
await inviteLinkPage.waitForURL("/getting-started");
|
||||
await inviteLinkPage.waitForURL((url) => {
|
||||
const path = url.pathname;
|
||||
return /\/(getting-started|onboarding\/(getting-started|personal\/settings))/.test(path);
|
||||
});
|
||||
await context.close();
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "../lib/fixtures";
|
||||
import { localize } from "../lib/localize";
|
||||
import { getInviteLink } from "../lib/testUtils";
|
||||
@@ -65,7 +63,10 @@ test.describe("Team", () => {
|
||||
// Check required fields
|
||||
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
|
||||
await newPage.locator("button[type=submit]").click();
|
||||
await newPage.waitForURL("/getting-started?from=signup");
|
||||
await newPage.waitForURL((url) => {
|
||||
const path = url.pathname;
|
||||
return /\/(getting-started|onboarding\/(getting-started|personal\/settings))/.test(path);
|
||||
});
|
||||
await newPage.close();
|
||||
await context.close();
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE "Feature"
|
||||
SET "enabled" = true, "updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE "slug" = 'onboarding-v3';
|
||||
Reference in new issue
Block a user