diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx
index 0fbe55ea..51ee2e60 100644
--- a/apps/web/pages/auth/login.tsx
+++ b/apps/web/pages/auth/login.tsx
@@ -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">
diff --git a/apps/web/pages/auth/logout.tsx b/apps/web/pages/auth/logout.tsx
index eb7b326a..a57a02c6 100644
--- a/apps/web/pages/auth/logout.tsx
+++ b/apps/web/pages/auth/logout.tsx
@@ -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
;
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") {
diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts
new file mode 100644
index 00000000..dd8e83aa
--- /dev/null
+++ b/apps/web/playwright/fixtures/users.ts
@@ -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;
+ get: () => UserFixture[];
+ logout: () => Promise;
+}
+
+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",
+}
+
+// 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> & { 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);
+}
diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts
new file mode 100644
index 00000000..5fb1a6cc
--- /dev/null
+++ b/apps/web/playwright/lib/fixtures.ts
@@ -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({
+ users: async ({ page }, use, testInfo) => {
+ // instantiate the fixture
+ const usersFixture = createUsersFixture(page);
+ // use the fixture within the test
+ await use(usersFixture);
+ },
+});
diff --git a/apps/web/playwright/login.test.ts b/apps/web/playwright/login.test.ts
index f89f2f64..9523932a 100644
--- a/apps/web/playwright/login.test.ts
+++ b/apps/web/playwright/login.test.ts
@@ -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();
+ });
});