diff --git a/apps/web/components/Shell.tsx b/apps/web/components/Shell.tsx
index 26240406..85e702e6 100644
--- a/apps/web/components/Shell.tsx
+++ b/apps/web/components/Shell.tsx
@@ -288,7 +288,9 @@ export default function Shell(props: {
-
+
@@ -299,7 +301,9 @@ export default function Shell(props: {
© {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"}
{process.env.NEXT_PUBLIC_WEBSITE_URL === "https://cal.com" ? "h" : "sh"}
- -{user && user.plan}
+
+ -{user && user.plan}
+
diff --git a/apps/web/playwright/fixtures/types.ts b/apps/web/playwright/fixtures/types.ts
new file mode 100644
index 00000000..5631999d
--- /dev/null
+++ b/apps/web/playwright/fixtures/types.ts
@@ -0,0 +1,4 @@
+export enum TimeZoneEnum {
+ USA = "America/Phoenix",
+ UK = "Europe/London",
+}
diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts
index dd8e83aa..796d1016 100644
--- a/apps/web/playwright/fixtures/users.ts
+++ b/apps/web/playwright/fixtures/users.ts
@@ -5,30 +5,15 @@ import type Prisma from "@prisma/client";
import { hashPassword } from "@calcom/lib/auth";
import { prisma } from "@calcom/prisma";
-export interface UsersFixture {
- create: (opts?: CustomUserOpts) => Promise;
- get: () => UserFixture[];
- logout: () => Promise;
-}
+import { TimeZoneEnum } from "./types";
-interface UserFixture {
- id: number;
- self: () => Promise;
- login: () => Promise;
- debug: (message: Record) => Promise;
-}
-
-// An alias for the hard to remember timezones strings
-export enum TimeZoneE {
- USA = "America/Phoenix",
- UK = "Europe/London",
-}
+type UserFixture = ReturnType;
// creates a user fixture instance and stores the collection
-export const createUsersFixture = (page: Page): UsersFixture => {
+export const createUsersFixture = (page: Page) => {
let store = { users: [], page } as { users: UserFixture[]; page: typeof page };
return {
- create: async (opts) => {
+ create: async (opts?: CustomUserOpts) => {
const user = await prisma.user.create({
data: await createUser(opts),
});
@@ -43,8 +28,10 @@ export const createUsersFixture = (page: Page): UsersFixture => {
};
};
+type JSONValue = string | number | boolean | { [x: string]: JSONValue } | Array;
+
// creates the single user fixture
-const createUserFixture = (user: Prisma.User, page: Page): UserFixture => {
+const createUserFixture = (user: Prisma.User, page: Page) => {
const store = { user, page };
// self is a reflective method that return the Prisma object that references this fixture.
@@ -52,16 +39,16 @@ const createUserFixture = (user: Prisma.User, page: Page): UserFixture => {
return {
id: user.id,
self,
- login: async () => login(await self(), store.page),
+ login: async () => login({ ...(await self()), password: user.username }, store.page),
// ths is for developemnt only aimed to inject debugging messages in the metadata field of the user
- debug: async (message) => {
+ debug: async (message: string | Record) => {
await prisma.user.update({ where: { id: store.user.id }, data: { metadata: { debug: message } } });
},
};
};
-type CustomUserOptsKeys = "username" | "plan" | "completedOnboarding" | "locale";
-type CustomUserOpts = Partial> & { timeZone?: TimeZoneE };
+type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale";
+type CustomUserOpts = Partial> & { timeZone?: TimeZoneEnum };
// creates the actual user in the db.
const createUser = async (opts?: CustomUserOpts) => {
@@ -76,21 +63,28 @@ const createUser = async (opts?: CustomUserOpts) => {
password: await hashPassword(uname),
emailVerified: new Date(),
completedOnboarding: opts?.completedOnboarding ?? true,
- timeZone: opts?.timeZone ?? TimeZoneE.UK,
+ timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
locale: opts?.locale ?? "en",
};
};
// login using a replay of an E2E routine.
-async function login(user: Prisma.User, page: Page) {
- await page.goto("/auth/logout");
- await page.goto("/");
- await page.click('input[name="email"]');
- await page.fill('input[name="email"]', user.email);
- await page.press('input[name="email"]', "Tab");
- await page.fill('input[name="password"]', user.username!);
- await page.press('input[name="password"]', "Enter");
+export async function login(
+ user: Pick & Partial>,
+ page: Page
+) {
+ // get locators
+ const loginLocator = await page.locator("[data-testid=login-form]");
+ const emailLocator = await loginLocator.locator("#email");
+ const passwordLocator = await loginLocator.locator("#password");
+ const signInLocator = await loginLocator.locator('[type="submit"]');
- // 2 seconds of delay before returning to help the session loading well
+ //login
+ await page.goto("/");
+ await emailLocator.fill(user.email ?? `${user.username}@example.com`);
+ await passwordLocator.fill(user.password ?? user.username!);
+ await signInLocator.click();
+
+ // 2 seconds of delay to give the session enough time for a clean load
await page.waitForTimeout(2000);
}
diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts
index 5fb1a6cc..615347a9 100644
--- a/apps/web/playwright/lib/fixtures.ts
+++ b/apps/web/playwright/lib/fixtures.ts
@@ -1,17 +1,14 @@
import { test as base } from "@playwright/test";
import { createUsersFixture } from "../fixtures/users";
-import type { UsersFixture } from "../fixtures/users";
interface Fixtures {
- users: UsersFixture;
+ users: ReturnType;
}
export const test = base.extend({
- users: async ({ page }, use, testInfo) => {
- // instantiate the fixture
+ users: async ({ page }, use) => {
const usersFixture = createUsersFixture(page);
- // use the fixture within the test
await use(usersFixture);
},
});
diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts
index 2e9f8e2f..d651264d 100644
--- a/apps/web/playwright/lib/testUtils.ts
+++ b/apps/web/playwright/lib/testUtils.ts
@@ -86,3 +86,13 @@ export async function selectSecondAvailableTimeSlotNextMonth(page: Page) {
await page.click('[data-testid="day"][data-disabled="false"]');
await page.locator('[data-testid="time"]').nth(1).click();
}
+
+// Provide an standalone localize utility not managed by next-i18n
+export async function localize(locale: string) {
+ const localeModule = `../../public/static/locales/${locale}/common.json`;
+ const localeMap = await import(localeModule);
+ return (message: string) => {
+ if (message in localeMap) return localeMap[message];
+ throw "No locale found for the given entry message";
+ };
+}
diff --git a/apps/web/playwright/login.test.ts b/apps/web/playwright/login.test.ts
index 9523932a..8674b9c0 100644
--- a/apps/web/playwright/login.test.ts
+++ b/apps/web/playwright/login.test.ts
@@ -1,19 +1,85 @@
import { expect } from "@playwright/test";
+import { UserPlan } from "@prisma/client";
+import { ErrorCode } from "@lib/auth";
+
+import { login } from "./fixtures/users";
import { test } from "./lib/fixtures";
+import { localize } from "./lib/testUtils";
-test.describe("Login tests", () => {
- // Using logged in state from globalSteup
- test.use({ storageState: "playwright/artifacts/proStorageState.json" });
+test.describe("Login and logout tests", () => {
+ // Test login with all plans
+ const plans = [UserPlan.PRO, UserPlan.FREE, UserPlan.TRIAL];
+ plans.forEach((plan) => {
+ test(`Should login with a ${plan} account`, async ({ page, users }) => {
+ // Create user and login
+ const pro = await users.create({ plan });
+ await pro.login();
- test("Login with pro@example.com", async ({ page }) => {
- // Try to go homepage
- await page.goto("/");
- // It should redirect you to the event-types page
- await page.waitForSelector("[data-testid=event-types]");
+ const shellLocator = await page.locator(`[data-testid=dashboard-shell]`);
+
+ // expects the home page for an authorized user
+ await page.goto("/");
+ await expect(shellLocator).toBeVisible();
+
+ // Asserts to read the tested plan
+ const planLocator = shellLocator.locator(`[data-testid=plan-${plan.toLowerCase()}]`);
+ await expect(planLocator).toBeVisible();
+ await await expect(planLocator).toHaveText;
+
+ // When TRIAL check if the TRIAL banner is visible
+ if (plan === UserPlan.TRIAL) {
+ await expect(page.locator(`[data-testid=trial-banner]`)).toBeVisible();
+ }
+ });
});
- test("Should logout using the logout path", async ({ page, users }) => {
+ test("Should warn when user does not exist", async ({ page, users }) => {
+ const alertMessage = (await localize("en"))("no_account_exists");
+
+ // Login with a non-existent user
+ const never = "never";
+ await login({ username: never }, page);
+
+ // assert for the visibility of the localized alert message
+ await expect(page.locator(`text=${alertMessage}`)).toBeVisible();
+ });
+
+ test("Should warn when password is incorrect", async ({ page, users }) => {
+ const alertMessage = (await localize("en"))("incorrect_password");
+ // by default password===username with the users fixture
+ const pro = await users.create({ username: "pro" });
+
+ // login with a wrong password
+ await login({ username: "pro", password: "wrong" }, page);
+
+ // assert for the visibility of the localized alert message
+ await expect(page.locator(`text=${alertMessage}`)).toBeVisible();
+ });
+
+ test("Should logout", async ({ page, users }) => {
+ const signOutLabel = (await localize("en"))("sign_out");
+ const userDropdownDisclose = async () =>
+ (await page.locator("[data-testid=user-dropdown-trigger]")).click();
+
+ // creates a user and login
+ const pro = await users.create();
+ await pro.login();
+
+ // disclose and click the sign out button from the user dropdown
+ await userDropdownDisclose();
+ const signOutBtn = await page.locator(`text=${signOutLabel}`);
+ await signOutBtn.click();
+
+ // 2s of delay to assure the session is cleared
+ await page.waitForTimeout(2000);
+
+ // Reroute to the home page to check if the login form shows up
+ await page.goto("/");
+ await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
+ });
+
+ test("Should logout using the logout route", async ({ page, users }) => {
// creates a user and login
const pro = await users.create();
await pro.login();
diff --git a/apps/web/playwright/trial.test.ts b/apps/web/playwright/trial.test.ts
index c5099391..ea54ae99 100644
--- a/apps/web/playwright/trial.test.ts
+++ b/apps/web/playwright/trial.test.ts
@@ -1,15 +1,7 @@
import { expect, test } from "@playwright/test";
+import { todo } from "./lib/testUtils";
+
test.describe("Trial account tests", () => {
- // Using logged in state from globalSteup
- test.use({ storageState: "playwright/artifacts/trialStorageState.json" });
-
- test("Trial banner should be visible to TRIAL users", async ({ page }) => {
- // Try to go homepage
- await page.goto("/");
- // It should redirect you to the event-types page
- await page.waitForSelector("[data-testid=event-types]");
-
- await expect(page.locator(`[data-testid=trial-banner]`)).toBeVisible();
- });
+ todo("Add tests with a TRIAL account");
});