Refactor login tests (#2337)
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
parent
7fd65ceb8a
commit
551892fa30
7 changed files with 128 additions and 61 deletions
|
@ -288,7 +288,9 @@ export default function Shell(props: {
|
|||
</nav>
|
||||
</div>
|
||||
<TrialBanner />
|
||||
<div className="rounded-sm pb-2 pl-3 pt-2 pr-2 hover:bg-gray-100 lg:mx-2 lg:pl-2">
|
||||
<div
|
||||
className="rounded-sm pb-2 pl-3 pt-2 pr-2 hover:bg-gray-100 lg:mx-2 lg:pl-2"
|
||||
data-testid="user-dropdown-trigger">
|
||||
<span className="hidden lg:inline">
|
||||
<UserDropdown />
|
||||
</span>
|
||||
|
@ -299,7 +301,9 @@ export default function Shell(props: {
|
|||
<small style={{ fontSize: "0.5rem" }} className="mx-3 mt-1 mb-2 hidden opacity-50 lg:block">
|
||||
© {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"}
|
||||
{process.env.NEXT_PUBLIC_WEBSITE_URL === "https://cal.com" ? "h" : "sh"}
|
||||
<span className="lowercase">-{user && user.plan}</span>
|
||||
<span className="lowercase" data-testid={`plan-${user?.plan.toLowerCase()}`}>
|
||||
-{user && user.plan}
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
4
apps/web/playwright/fixtures/types.ts
Normal file
4
apps/web/playwright/fixtures/types.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum TimeZoneEnum {
|
||||
USA = "America/Phoenix",
|
||||
UK = "Europe/London",
|
||||
}
|
|
@ -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<UserFixture>;
|
||||
get: () => UserFixture[];
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
import { TimeZoneEnum } from "./types";
|
||||
|
||||
interface UserFixture {
|
||||
id: number;
|
||||
self: () => Promise<Prisma.User>;
|
||||
login: () => Promise<void>;
|
||||
debug: (message: Record<string, any>) => Promise<void>;
|
||||
}
|
||||
|
||||
// An alias for the hard to remember timezones strings
|
||||
export enum TimeZoneE {
|
||||
USA = "America/Phoenix",
|
||||
UK = "Europe/London",
|
||||
}
|
||||
type UserFixture = ReturnType<typeof createUserFixture>;
|
||||
|
||||
// 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<JSONValue>;
|
||||
|
||||
// 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<string, JSONValue>) => {
|
||||
await prisma.user.update({ where: { id: store.user.id }, data: { metadata: { debug: message } } });
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type CustomUserOptsKeys = "username" | "plan" | "completedOnboarding" | "locale";
|
||||
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneE };
|
||||
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale";
|
||||
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { 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<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<typeof createUsersFixture>;
|
||||
}
|
||||
|
||||
export const test = base.extend<Fixtures>({
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue