diff --git a/frontend/src/components/Common/UserMenu.tsx b/frontend/src/components/Common/UserMenu.tsx
index 324dd92..e3d54ac 100644
--- a/frontend/src/components/Common/UserMenu.tsx
+++ b/frontend/src/components/Common/UserMenu.tsx
@@ -35,6 +35,7 @@ const UserMenu = () => {
icon={}
bg="ui.main"
isRound
+ data-testid="user-menu"
/>
} as={Link} to="settings">
diff --git a/frontend/tests/example.spec.ts b/frontend/tests/example.spec.ts
deleted file mode 100644
index f5dc2e7..0000000
--- a/frontend/tests/example.spec.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test('has title', async ({ page }) => {
- await page.goto('https://playwright.dev/');
-
- // Expect a title "to contain" a substring.
- await expect(page).toHaveTitle(/Playwright/);
-});
diff --git a/frontend/tests/login.spec.ts b/frontend/tests/login.spec.ts
new file mode 100644
index 0000000..eb7d5e4
--- /dev/null
+++ b/frontend/tests/login.spec.ts
@@ -0,0 +1,114 @@
+import { type Page, expect, test } from "@playwright/test"
+
+test.use({ storageState: { cookies: [], origins: [] } })
+
+type OptionsType = {
+ exact?: boolean
+}
+
+const fillForm = async (page: Page, email: string, password: string) => {
+ await page.getByPlaceholder("Email").fill(email)
+ await page.getByPlaceholder("Password", { exact: true }).fill(password)
+}
+
+const verifyInput = async (
+ page: Page,
+ placeholder: string,
+ options?: OptionsType,
+) => {
+ const input = page.getByPlaceholder(placeholder, options)
+ await expect(input).toBeVisible()
+ await expect(input).toHaveText("")
+ await expect(input).toBeEditable()
+}
+
+test("Inputs are visible, empty and editable", async ({ page }) => {
+ await page.goto("/login")
+
+ await verifyInput(page, "Email")
+ await verifyInput(page, "Password", { exact: true })
+})
+
+test("Log In button is visible", async ({ page }) => {
+ await page.goto("/login")
+
+ await expect(page.getByRole("button", { name: "Log In" })).toBeVisible()
+})
+
+test("Forgot Password link is visible", async ({ page }) => {
+ await page.goto("/login")
+
+ await expect(
+ page.getByRole("link", { name: "Forgot password?" }),
+ ).toBeVisible()
+})
+
+test("Log in with valid email and password ", async ({ page }) => {
+ await page.goto("/login")
+
+ await fillForm(page, "admin@example.com", "changethis")
+ await page.getByRole("button", { name: "Log In" }).click()
+
+ await page.waitForURL("/")
+
+ await expect(
+ page.getByText("Welcome back, nice to see you again!"),
+ ).toBeVisible()
+})
+
+test("Log in with invalid email", async ({ page }) => {
+ await page.goto("/login")
+
+ await fillForm(page, "invalidemail", "changethis")
+ await page.getByRole("button", { name: "Log In" }).click()
+
+ await expect(page.getByText("Invalid email address")).toBeVisible()
+})
+
+test("Log in with invalid password", async ({ page }) => {
+ await page.goto("/login")
+
+ await fillForm(page, "admin@example.com", "changethat")
+ await page.getByRole("button", { name: "Log In" }).click()
+
+ await expect(page.getByText("Incorrect email or password")).toBeVisible()
+})
+
+// Log out
+
+test("Successful log out", async ({ page }) => {
+ await page.goto("/login")
+
+ await fillForm(page, "admin@example.com", "changethis")
+ await page.getByRole("button", { name: "Log In" }).click()
+
+ await page.waitForURL("/")
+
+ await expect(
+ page.getByText("Welcome back, nice to see you again!"),
+ ).toBeVisible()
+
+ await page.getByTestId("user-menu").click()
+ await page.getByRole("menuitem", { name: "Log out" }).click()
+ await page.waitForURL("/login")
+})
+
+test("Logged-out user cannot access protected routes", async ({ page }) => {
+ await page.goto("/login")
+
+ await fillForm(page, "admin@example.com", "changethis")
+ await page.getByRole("button", { name: "Log In" }).click()
+
+ await page.waitForURL("/")
+
+ await expect(
+ page.getByText("Welcome back, nice to see you again!"),
+ ).toBeVisible()
+
+ await page.getByTestId("user-menu").click()
+ await page.getByRole("menuitem", { name: "Log out" }).click()
+ await page.waitForURL("/login")
+
+ await page.goto("/settings")
+ await page.waitForURL("/login")
+})