Skip to content

Commit 0951eec

Browse files
committed
Add fixture for generating new users for specific tests
1 parent f677b95 commit 0951eec

File tree

9 files changed

+214
-194
lines changed

9 files changed

+214
-194
lines changed

global-setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { FullConfig } from "@playwright/test";
2-
import { createUser } from "./setup-utils";
2+
import { createUserInFile } from "./setup-utils";
33

44
export default async function globalSetup(config: FullConfig) {
55
const { baseURL } = config.projects[0]?.use || {};
66
if (!baseURL) {
77
throw new Error("baseURL is required");
88
}
99

10-
await createUser(baseURL, "api");
10+
await createUserInFile(baseURL, "api");
1111
}

global-teardown.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { FullConfig } from "@playwright/test";
2-
import { deleteUser } from "./setup-utils";
2+
import { deleteUserFromFile } from "./setup-utils";
33

44
export default async function globalTeardown(config: FullConfig) {
55
const { baseURL } = config.projects[0]?.use || {};
66
if (!baseURL) {
77
throw new Error("baseURL is required");
88
}
99

10-
await deleteUser(baseURL, "api");
10+
await deleteUserFromFile(baseURL, "api");
1111
}

mcp-user.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
import { createUser, deleteUser } from "./setup-utils";
3+
import { createUserInFile, deleteUserFromFile } from "./setup-utils";
44

55
const scriptArg = process.argv[2];
66

@@ -16,7 +16,7 @@ const baseURL = "https://endform-playwright-tutorial.vercel.app";
1616

1717
if (scriptArg === "create") {
1818
console.log("Creating MCP user", baseURL);
19-
await createUser(baseURL, "mcp");
19+
await createUserInFile(baseURL, "mcp");
2020
} else if (scriptArg === "delete") {
21-
await deleteUser(baseURL, "mcp");
21+
await deleteUserFromFile(baseURL, "mcp");
2222
}

setup-utils.ts

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,64 @@
11
import fs from "node:fs";
22

3-
export async function createUser(baseURL: string, userKindName: string) {
3+
export interface ApiUser {
4+
email: string;
5+
password: string;
6+
session: string;
7+
}
8+
9+
export async function createUser(baseURL: string): Promise<ApiUser> {
410
const email = `${Math.random().toString(36).substring(2, 15)}@example.com`;
511
const password = "testpassword123";
612

13+
const response = await fetch(`${baseURL}/api/internal/user`, {
14+
method: "POST",
15+
headers: {
16+
"content-type": "application/json",
17+
authorization: "Bearer VerySecretDummyToken",
18+
},
19+
body: JSON.stringify({ email, password }),
20+
});
21+
const data = (await response.json()) as { session: string };
22+
if (!data.session) {
23+
throw new Error("Failed to create user");
24+
}
25+
26+
return { email, password, session: data.session };
27+
}
28+
29+
export async function deleteUser(baseURL: string, email: string) {
30+
const response = await fetch(`${baseURL}/api/internal/user`, {
31+
method: "DELETE",
32+
headers: {
33+
"content-type": "application/json",
34+
authorization: "Bearer VerySecretDummyToken",
35+
},
36+
body: JSON.stringify({ email }),
37+
});
38+
const data = await response.json().catch(() => null);
39+
40+
return data;
41+
}
42+
43+
export async function createUserInFile(baseURL: string, userKindName: string) {
744
try {
8-
const response = await fetch(`${baseURL}/api/internal/user`, {
9-
method: "POST",
10-
headers: {
11-
"content-type": "application/json",
12-
authorization: "Bearer VerySecretDummyToken",
13-
},
14-
body: JSON.stringify({ email, password }),
15-
});
16-
const data = (await response.json()) as { session: string };
17-
if (!data.session) {
18-
throw new Error("Failed to create user");
19-
}
45+
const { email, session } = await createUser(baseURL);
2046

2147
writeEmailFile(email, `.auth/${userKindName}-user-email.txt`);
22-
writeSessionFile(data.session, baseURL, `.auth/${userKindName}-user.json`);
48+
writeSessionFile(session, baseURL, `.auth/${userKindName}-user.json`);
2349
} catch (error) {
2450
console.error("[setup-utils] error creating user:", error);
2551
}
2652
}
2753

28-
export async function deleteUser(baseURL: string, userKindName: string) {
54+
export async function deleteUserFromFile(
55+
baseURL: string,
56+
userKindName: string,
57+
) {
2958
const email = readEmailFile(`.auth/${userKindName}-user-email.txt`);
3059

3160
try {
32-
const response = await fetch(`${baseURL}/api/internal/user`, {
33-
method: "DELETE",
34-
headers: {
35-
"content-type": "application/json",
36-
authorization: "Bearer VerySecretDummyToken",
37-
},
38-
body: JSON.stringify({ email }),
39-
});
40-
const data = await response.json().catch(() => null);
61+
await deleteUser(baseURL, email);
4162
// console.log("[setup-utils] delete user status:", response.status);
4263
// console.log("[setup-utils] delete user body:", data);
4364
fs.unlinkSync(`.auth/${userKindName}-user-email.txt`);
@@ -47,6 +68,20 @@ export async function deleteUser(baseURL: string, userKindName: string) {
4768
}
4869
}
4970

71+
export function getCookieForSession(session: string, baseURL: string) {
72+
return {
73+
name: "session",
74+
value: session,
75+
domain: new URL(baseURL).hostname,
76+
path: "/",
77+
// Playwright will delete the cookie when the test is done
78+
expires: -1,
79+
httpOnly: true,
80+
secure: true,
81+
sameSite: "Lax" as const,
82+
};
83+
}
84+
5085
function writeEmailFile(email: string, filePath: string) {
5186
fs.writeFileSync(filePath, email);
5287
}
@@ -59,19 +94,7 @@ function writeSessionFile(session: string, baseURL: string, filePath: string) {
5994
fs.writeFileSync(
6095
filePath,
6196
JSON.stringify({
62-
cookies: [
63-
{
64-
name: "session",
65-
value: session,
66-
domain: new URL(baseURL).hostname,
67-
path: "/",
68-
// Playwright will delete the cookie when the test is done
69-
expires: -1,
70-
httpOnly: true,
71-
secure: true,
72-
sameSite: "Lax",
73-
},
74-
],
97+
cookies: [getCookieForSession(session, baseURL)],
7598
origins: [],
7699
}),
77100
);

tests/change-email.spec.ts

Lines changed: 80 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,86 @@
11
import { expect, test } from "@playwright/test";
2-
3-
// Use a clean user for this test, to not interfere with other tests
4-
test.use({ storageState: { cookies: [], origins: [] } });
2+
import { testWithNewUser } from "./test-with-new-user";
53

64
test.describe("User Email Change Flow", () => {
7-
test("should sign up new user, change email, sign out, and sign in with new email", async ({
8-
page,
9-
}) => {
10-
// Generate random emails for the test
11-
const initialEmail = `${Math.random().toString(36).substring(2, 15)}@example.com`;
12-
const newEmail = `${Math.random().toString(36).substring(2, 15)}@example.com`;
13-
const testPassword = "testpassword123";
14-
15-
await test.step("Navigate to homepage and ensure clean state", async () => {
16-
await page.goto("/");
17-
await expect(page).toHaveTitle("Playwright Tutorial");
18-
19-
// Verify we're not logged in by checking for Sign Up link
20-
await expect(page.getByRole("link", { name: "Sign Up" })).toBeVisible();
21-
});
22-
23-
await test.step("Navigate to sign up page", async () => {
24-
await page.getByRole("link", { name: "Sign Up" }).click();
25-
await expect(page).toHaveURL("/sign-up");
26-
await expect(
27-
page.getByRole("heading", { name: "Create your account" }),
28-
).toBeVisible();
29-
});
30-
31-
await test.step("Fill signup form and submit", async () => {
32-
await page.getByRole("textbox", { name: "Email" }).fill(initialEmail);
33-
await page.getByRole("textbox", { name: "Password" }).fill(testPassword);
34-
await page.getByRole("button", { name: "Sign up" }).click();
35-
});
36-
37-
await test.step("Verify successful signup redirects to dashboard", async () => {
38-
await expect(page).toHaveURL("/dashboard");
39-
await expect(
40-
page.getByRole("heading", { name: "Team Settings" }),
41-
).toBeVisible();
42-
// Verify initial email appears in team members list
5+
testWithNewUser(
6+
"should sign up new user, change email, sign out, and sign in with new email",
7+
async ({ page, newUser: { email: initialEmail, password } }) => {
8+
await page.goto("/dashboard");
439
await expect(page.getByText(initialEmail)).toBeVisible();
44-
});
45-
46-
await test.step("Navigate to general settings page", async () => {
47-
await page.getByRole("link", { name: "General" }).click();
48-
await expect(page).toHaveURL("/dashboard/general");
49-
await expect(
50-
page.getByRole("heading", { name: "General Settings" }),
51-
).toBeVisible();
52-
});
53-
54-
await test.step("Change email to new randomly generated email", async () => {
55-
const nameInput = page.getByRole("textbox", { name: "Name" });
56-
await expect(nameInput).toBeVisible();
57-
await nameInput.fill("John Doe");
58-
59-
const emailInput = page.getByRole("textbox", { name: "Email" });
60-
await expect(emailInput).toBeVisible();
61-
await expect(emailInput).toHaveValue(initialEmail);
62-
63-
await emailInput.clear();
64-
await emailInput.fill(newEmail);
65-
await page.getByRole("button", { name: "Save Changes" }).click();
66-
67-
// Wait for success message
68-
await expect(
69-
page.getByText("Account updated successfully."),
70-
).toBeVisible();
71-
});
72-
73-
await test.step("Sign out", async () => {
74-
// Click on user menu button
75-
await page.getByTestId("user-menu-trigger").click();
76-
await page.getByRole("button", { name: "Sign out" }).click();
77-
78-
// Verify we're signed out by checking for Sign Up link
79-
await expect(page.getByRole("link", { name: "Sign Up" })).toBeVisible();
80-
});
81-
82-
await test.step("Sign in with new email and test password", async () => {
83-
// If not already on sign-in page, navigate there
84-
const currentUrl = page.url();
85-
if (!currentUrl.includes("/sign-in")) {
86-
await page.goto("/sign-in");
87-
}
88-
89-
await expect(
90-
page.getByRole("heading", { name: "Sign in to your account" }),
91-
).toBeVisible();
92-
93-
await page.getByRole("textbox", { name: "Email" }).fill(newEmail);
94-
await page.getByRole("textbox", { name: "Password" }).fill(testPassword);
95-
await page.getByRole("button", { name: "Sign in" }).click();
96-
});
97-
98-
await test.step("Verify successful sign in and redirect to team settings page", async () => {
99-
await expect(page).toHaveURL("/dashboard");
100-
await expect(
101-
page.getByRole("heading", { name: "Team Settings" }),
102-
).toBeVisible();
103-
});
104-
105-
await test.step("Verify new email is displayed in general settings page", async () => {
106-
await page.getByRole("link", { name: "General" }).click();
107-
await expect(page).toHaveURL("/dashboard/general");
108-
await expect(
109-
page.getByRole("heading", { name: "General Settings" }),
110-
).toBeVisible();
11110

112-
// Verify the new email is displayed in the email field
113-
const emailInput = page.getByRole("textbox", { name: "Email" });
114-
await expect(emailInput).toBeVisible();
115-
await expect(emailInput).toHaveValue(newEmail);
116-
});
117-
});
11+
await test.step("Navigate to general settings page", async () => {
12+
await page.getByRole("link", { name: "General" }).click();
13+
await expect(page).toHaveURL("/dashboard/general");
14+
await expect(
15+
page.getByRole("heading", { name: "General Settings" }),
16+
).toBeVisible();
17+
});
18+
19+
const newEmail = `${Math.random().toString(36).substring(2, 15)}@example.com`;
20+
21+
await test.step("Change email to new randomly generated email", async () => {
22+
const nameInput = page.getByRole("textbox", { name: "Name" });
23+
await expect(nameInput).toBeVisible();
24+
await nameInput.fill("John Doe");
25+
26+
const emailInput = page.getByRole("textbox", { name: "Email" });
27+
await expect(emailInput).toBeVisible();
28+
await expect(emailInput).toHaveValue(initialEmail);
29+
30+
await emailInput.clear();
31+
await emailInput.fill(newEmail);
32+
await page.getByRole("button", { name: "Save Changes" }).click();
33+
34+
// Wait for success message
35+
await expect(
36+
page.getByText("Account updated successfully."),
37+
).toBeVisible();
38+
});
39+
40+
await test.step("Sign out", async () => {
41+
// Click on user menu button
42+
await page.getByTestId("user-menu-trigger").click();
43+
await page.getByRole("button", { name: "Sign out" }).click();
44+
45+
// Verify we're signed out by checking for Sign Up link
46+
await expect(page.getByRole("link", { name: "Sign Up" })).toBeVisible();
47+
});
48+
49+
await test.step("Sign in with new email and test password", async () => {
50+
// If not already on sign-in page, navigate there
51+
const currentUrl = page.url();
52+
if (!currentUrl.includes("/sign-in")) {
53+
await page.goto("/sign-in");
54+
}
55+
56+
await expect(
57+
page.getByRole("heading", { name: "Sign in to your account" }),
58+
).toBeVisible();
59+
60+
await page.getByRole("textbox", { name: "Email" }).fill(newEmail);
61+
await page.getByRole("textbox", { name: "Password" }).fill(password);
62+
await page.getByRole("button", { name: "Sign in" }).click();
63+
});
64+
65+
await test.step("Verify successful sign in and redirect to team settings page", async () => {
66+
await expect(page).toHaveURL("/dashboard");
67+
await expect(
68+
page.getByRole("heading", { name: "Team Settings" }),
69+
).toBeVisible();
70+
});
71+
72+
await test.step("Verify new email is displayed in general settings page", async () => {
73+
await page.getByRole("link", { name: "General" }).click();
74+
await expect(page).toHaveURL("/dashboard/general");
75+
await expect(
76+
page.getByRole("heading", { name: "General Settings" }),
77+
).toBeVisible();
78+
79+
// Verify the new email is displayed in the email field
80+
const emailInput = page.getByRole("textbox", { name: "Email" });
81+
await expect(emailInput).toBeVisible();
82+
await expect(emailInput).toHaveValue(newEmail);
83+
});
84+
},
85+
);
11886
});

0 commit comments

Comments
 (0)