Refactor login tests (#2337)

Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Demian Caldelas 2022-04-06 12:13:09 -03:00 committed by GitHub
parent 7fd65ceb8a
commit 551892fa30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 61 deletions

View File

@ -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">
&copy; {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>

View File

@ -0,0 +1,4 @@
export enum TimeZoneEnum {
USA = "America/Phoenix",
UK = "Europe/London",
}

View File

@ -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);
}

View File

@ -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);
},
});

View File

@ -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";
};
}

View File

@ -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();

View File

@ -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");
});