Introducing Playwright Fixtures - Users Factory (#2293)

* Fix not able to logout using the logout path

* Add users fixture for e2e tests

* typo

Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Demian Caldelas 2022-03-28 17:06:41 -03:00 committed by GitHub
parent 2d2df2d4db
commit 0390ae9ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 3 deletions

View File

@ -112,7 +112,8 @@ export default function Login({
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
})
.catch(() => setErrorMessage(errorMessages[ErrorCode.InternalServerError]));
}}>
}}
data-testid="login-form">
<input defaultValue={csrfToken || undefined} type="hidden" hidden {...form.register("csrfToken")} />
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>

View File

@ -1,5 +1,6 @@
import { CheckIcon } from "@heroicons/react/outline";
import { GetServerSidePropsContext } from "next";
import { useSession, signOut } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
@ -16,6 +17,8 @@ import { ssrInit } from "@server/lib/ssr";
type Props = inferSSRProps<typeof getServerSideProps>;
export default function Logout(props: Props) {
const { data: session, status } = useSession();
if (status === "authenticated") signOut({ redirect: false });
const router = useRouter();
useEffect(() => {
if (props.query?.survey === "true") {

View File

@ -0,0 +1,96 @@
import type { Page } from "@playwright/test";
import { UserPlan } from "@prisma/client";
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>;
}
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",
}
// creates a user fixture instance and stores the collection
export const createUsersFixture = (page: Page): UsersFixture => {
let store = { users: [], page } as { users: UserFixture[]; page: typeof page };
return {
create: async (opts) => {
const user = await prisma.user.create({
data: await createUser(opts),
});
const userFixture = createUserFixture(user, store.page!);
store.users.push(userFixture);
return userFixture;
},
get: () => store.users,
logout: async () => {
await page.goto("/auth/logout");
},
};
};
// creates the single user fixture
const createUserFixture = (user: Prisma.User, page: Page): UserFixture => {
const store = { user, page };
// self is a reflective method that return the Prisma object that references this fixture.
const self = async () => (await prisma.user.findUnique({ where: { id: store.user.id } }))!;
return {
id: user.id,
self,
login: async () => login(await self(), store.page),
// ths is for developemnt only aimed to inject debugging messages in the metadata field of the user
debug: async (message) => {
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 };
// creates the actual user in the db.
const createUser = async (opts?: CustomUserOpts) => {
// build a unique name for our user
const uname =
(opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()) + "-" + Date.now();
return {
username: uname,
name: (opts?.username ?? opts?.plan ?? UserPlan.PRO).toUpperCase(),
plan: opts?.plan ?? UserPlan.PRO,
email: `${uname}@example.com`,
password: await hashPassword(uname),
emailVerified: new Date(),
completedOnboarding: opts?.completedOnboarding ?? true,
timeZone: opts?.timeZone ?? TimeZoneE.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");
// 2 seconds of delay before returning to help the session loading well
await page.waitForTimeout(2000);
}

View File

@ -0,0 +1,17 @@
import { test as base } from "@playwright/test";
import { createUsersFixture } from "../fixtures/users";
import type { UsersFixture } from "../fixtures/users";
interface Fixtures {
users: UsersFixture;
}
export const test = base.extend<Fixtures>({
users: async ({ page }, use, testInfo) => {
// instantiate the fixture
const usersFixture = createUsersFixture(page);
// use the fixture within the test
await use(usersFixture);
},
});

View File

@ -1,13 +1,28 @@
import { test } from "@playwright/test";
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
test.describe("Login tests", () => {
// Using logged in state from globalSteup
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("login with pro@example.com", async ({ page }) => {
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]");
});
test("Should logout using the logout path", async ({ page, users }) => {
// creates a user and login
const pro = await users.create();
await pro.login();
// users.logout() action uses the logout route "/auth/logout" to clear the session
await users.logout();
// check if we are at the login page
await page.goto("/");
await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
});
});