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:
parent
2d2df2d4db
commit
0390ae9ee1
5 changed files with 135 additions and 3 deletions
|
@ -112,7 +112,8 @@ export default function Login({
|
||||||
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
|
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
|
||||||
})
|
})
|
||||||
.catch(() => setErrorMessage(errorMessages[ErrorCode.InternalServerError]));
|
.catch(() => setErrorMessage(errorMessages[ErrorCode.InternalServerError]));
|
||||||
}}>
|
}}
|
||||||
|
data-testid="login-form">
|
||||||
<input defaultValue={csrfToken || undefined} type="hidden" hidden {...form.register("csrfToken")} />
|
<input defaultValue={csrfToken || undefined} type="hidden" hidden {...form.register("csrfToken")} />
|
||||||
|
|
||||||
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
|
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { CheckIcon } from "@heroicons/react/outline";
|
import { CheckIcon } from "@heroicons/react/outline";
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
import { useSession, signOut } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
@ -16,6 +17,8 @@ import { ssrInit } from "@server/lib/ssr";
|
||||||
type Props = inferSSRProps<typeof getServerSideProps>;
|
type Props = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
export default function Logout(props: Props) {
|
export default function Logout(props: Props) {
|
||||||
|
const { data: session, status } = useSession();
|
||||||
|
if (status === "authenticated") signOut({ redirect: false });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.query?.survey === "true") {
|
if (props.query?.survey === "true") {
|
||||||
|
|
96
apps/web/playwright/fixtures/users.ts
Normal file
96
apps/web/playwright/fixtures/users.ts
Normal 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);
|
||||||
|
}
|
17
apps/web/playwright/lib/fixtures.ts
Normal file
17
apps/web/playwright/lib/fixtures.ts
Normal 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);
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,13 +1,28 @@
|
||||||
import { test } from "@playwright/test";
|
import { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
import { test } from "./lib/fixtures";
|
||||||
|
|
||||||
test.describe("Login tests", () => {
|
test.describe("Login tests", () => {
|
||||||
// Using logged in state from globalSteup
|
// Using logged in state from globalSteup
|
||||||
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
|
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
|
// Try to go homepage
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
// It should redirect you to the event-types page
|
// It should redirect you to the event-types page
|
||||||
await page.waitForSelector("[data-testid=event-types]");
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue