forked from fastapi/full-stack-fastapi-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add Reset Password e2e tests (fastapi#1270)
- Loading branch information
Showing
5 changed files
with
222 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { expect, test } from "@playwright/test" | ||
import { findLastEmail } from "./utils/mailcatcher" | ||
import { randomEmail } from "./utils/random" | ||
import { logInUser, signUpNewUser } from "./utils/user" | ||
|
||
test.use({ storageState: { cookies: [], origins: [] } }) | ||
|
||
test("Password Recovery title is visible", async ({ page }) => { | ||
await page.goto("/recover-password") | ||
|
||
await expect( | ||
page.getByRole("heading", { name: "Password Recovery" }), | ||
).toBeVisible() | ||
}) | ||
|
||
test("Input is visible, empty and editable", async ({ page }) => { | ||
await page.goto("/recover-password") | ||
|
||
await expect(page.getByPlaceholder("Email")).toBeVisible() | ||
await expect(page.getByPlaceholder("Email")).toHaveText("") | ||
await expect(page.getByPlaceholder("Email")).toBeEditable() | ||
}) | ||
|
||
test("Continue button is visible", async ({ page }) => { | ||
await page.goto("/recover-password") | ||
|
||
await expect(page.getByRole("button", { name: "Continue" })).toBeVisible() | ||
}) | ||
|
||
test("User can reset password successfully using the link", async ({ | ||
page, | ||
request, | ||
}) => { | ||
const full_name = "Test User" | ||
const email = randomEmail() | ||
const password = "changethis" | ||
const new_password = "changethat" | ||
|
||
// Sign up a new user | ||
await signUpNewUser(page, full_name, email, password) | ||
|
||
await page.goto("/recover-password") | ||
await page.getByPlaceholder("Email").fill(email) | ||
|
||
await page.getByRole("button", { name: "Continue" }).click() | ||
|
||
const emailData = await findLastEmail({ | ||
request, | ||
filter: (e) => e.recipients.includes(`<${email}>`), | ||
timeout: 5000, | ||
}) | ||
|
||
await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) | ||
|
||
const selector = 'a[href*="/reset-password?token="]' | ||
|
||
let url = await page.getAttribute(selector, "href") | ||
|
||
// TODO: update var instead of doing a replace | ||
url = url!.replace("http://localhost/", "http://localhost:5173/") | ||
|
||
// Set the new password and confirm it | ||
await page.goto(url) | ||
|
||
await page.getByLabel("Set Password").fill(new_password) | ||
await page.getByLabel("Confirm Password").fill(new_password) | ||
await page.getByRole("button", { name: "Reset Password" }).click() | ||
await expect(page.getByText("Password updated successfully")).toBeVisible() | ||
|
||
// Check if the user is able to login with the new password | ||
await logInUser(page, email, new_password) | ||
}) | ||
|
||
test("Expired or invalid reset link", async ({ page }) => { | ||
const invalidUrl = "/reset-password?token=invalidtoken" | ||
|
||
await page.goto(invalidUrl) | ||
|
||
await page.getByLabel("Set Password").fill("newpassword") | ||
await page.getByLabel("Confirm Password").fill("newpassword") | ||
await page.getByRole("button", { name: "Reset Password" }).click() | ||
|
||
await expect(page.getByText("Invalid token")).toBeVisible() | ||
}) | ||
|
||
test("Weak new password validation", async ({ page, request }) => { | ||
const full_name = "Test User" | ||
const email = randomEmail() | ||
const password = "password" | ||
|
||
// Sign up a new user | ||
await signUpNewUser(page, full_name, email, password) | ||
|
||
await page.goto("/recover-password") | ||
await page.getByPlaceholder("Email").fill(email) | ||
await page.getByRole("button", { name: "Continue" }).click() | ||
|
||
const emailData = await findLastEmail({ | ||
request, | ||
filter: (e) => e.recipients.includes(`<${email}>`), | ||
timeout: 5000, | ||
}) | ||
|
||
await page.goto(`http://localhost:1080/messages/${emailData.id}.html`) | ||
|
||
const selector = 'a[href*="/reset-password?token="]' | ||
let url = await page.getAttribute(selector, "href") | ||
url = url!.replace("http://localhost/", "http://localhost:5173/") | ||
|
||
// Set a weak new password | ||
await page.goto(url) | ||
await page.getByLabel("Set Password").fill("123") | ||
await page.getByLabel("Confirm Password").fill("123") | ||
await page.getByRole("button", { name: "Reset Password" }).click() | ||
|
||
await expect( | ||
page.getByText("Password must be at least 8 characters"), | ||
).toBeVisible() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { APIRequestContext } from "@playwright/test" | ||
|
||
type Email = { | ||
id: number | ||
recipients: string[] | ||
subject: string | ||
} | ||
|
||
async function findEmail({ | ||
request, | ||
filter, | ||
}: { request: APIRequestContext; filter?: (email: Email) => boolean }) { | ||
const response = await request.get("http://localhost:1080/messages") | ||
|
||
let emails = await response.json() | ||
|
||
if (filter) { | ||
emails = emails.filter(filter) | ||
} | ||
|
||
const email = emails[emails.length - 1] | ||
|
||
if (email) { | ||
return email as Email | ||
} | ||
|
||
return null | ||
} | ||
|
||
export function findLastEmail({ | ||
request, | ||
filter, | ||
timeout = 5000, | ||
}: { | ||
request: APIRequestContext | ||
filter?: (email: Email) => boolean | ||
timeout?: number | ||
}) { | ||
const timeoutPromise = new Promise<never>((_, reject) => | ||
setTimeout( | ||
() => reject(new Error("Timeout while trying to get latest email")), | ||
timeout, | ||
), | ||
) | ||
|
||
const checkEmails = async () => { | ||
while (true) { | ||
const emailData = await findEmail({ request, filter }) | ||
|
||
if (emailData) { | ||
return emailData | ||
} | ||
// Wait for 100ms before checking again | ||
await new Promise((resolve) => setTimeout(resolve, 100)) | ||
} | ||
} | ||
|
||
return Promise.race([timeoutPromise, checkEmails()]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { type Page, expect } from "@playwright/test" | ||
|
||
export async function signUpNewUser( | ||
page: Page, | ||
name: string, | ||
email: string, | ||
password: string, | ||
) { | ||
await page.goto("/signup") | ||
|
||
await page.getByPlaceholder("Full Name").fill(name) | ||
await page.getByPlaceholder("Email").fill(email) | ||
await page.getByPlaceholder("Password", { exact: true }).fill(password) | ||
await page.getByPlaceholder("Repeat Password").fill(password) | ||
await page.getByRole("button", { name: "Sign Up" }).click() | ||
await expect( | ||
page.getByText("Your account has been created successfully"), | ||
).toBeVisible() | ||
await page.goto("/login") | ||
} | ||
|
||
export async function logInUser(page: Page, email: string, password: string) { | ||
await page.goto("/login") | ||
|
||
await page.getByPlaceholder("Email").fill(email) | ||
await page.getByPlaceholder("Password", { exact: true }).fill(password) | ||
await page.getByRole("button", { name: "Log In" }).click() | ||
await page.waitForURL("/") | ||
await expect( | ||
page.getByText("Welcome back, nice to see you again!"), | ||
).toBeVisible() | ||
} | ||
|
||
export async function logOutUser(page: Page, name: string) { | ||
await page.getByRole("button", { name: name }).click() | ||
await page.getByRole("menuitem", { name: "Log out" }).click() | ||
await page.goto("/login") | ||
} |