← full-stack-fastapi-template  /  frontend/tests/user-settings.spec.ts

1
import { expect, test } from "@playwright/test"
2
import { firstSuperuser, firstSuperuserPassword } from "./config.ts"
3
import { createUser } from "./utils/privateApi.ts"
4
import { randomEmail, randomPassword } from "./utils/random"
5
import { logInUser, logOutUser } from "./utils/user"
6
7
const tabs = ["My profile", "Password", "Danger zone"]
8
9
test("My profile tab is active by default", async ({ page }) => {
10
  await page.goto("/settings")
11
  await expect(page.getByRole("tab", { name: "My profile" })).toHaveAttribute(
12
    "aria-selected",
13
    "true",
14
  )
15
})
16
17
test("All tabs are visible", async ({ page }) => {
18
  await page.goto("/settings")
19
  for (const tab of tabs) {
20
    await expect(page.getByRole("tab", { name: tab })).toBeVisible()
21
  }
22
})
23
24
test.describe("Edit user profile", () => {
25
  test.use({ storageState: { cookies: [], origins: [] } })
26
  let email: string
27
  let password: string
28
29
  test.beforeAll(async () => {
30
    email = randomEmail()
31
    password = randomPassword()
32
    await createUser({ email, password })
33
  })
34
35
  test.beforeEach(async ({ page }) => {
36
    await logInUser(page, email, password)
37
    await page.goto("/settings")
38
    await page.getByRole("tab", { name: "My profile" }).click()
39
  })
40
41
  test("Edit user name with a valid name", async ({ page }) => {
42
    const updatedName = "Test User 2"
43
44
    await page.getByRole("button", { name: "Edit" }).click()
45
    await page.getByLabel("Full name").fill(updatedName)
46
    await page.getByRole("button", { name: "Save" }).click()
47
48
    await expect(page.getByText("User updated successfully")).toBeVisible()
49
    await expect(
50
      page.locator("form").getByText(updatedName, { exact: true }),
51
    ).toBeVisible()
52
  })
53
54
  test("Edit user email with an invalid email shows error", async ({
55
    page,
56
  }) => {
57
    await page.getByRole("button", { name: "Edit" }).click()
58
    await page.getByLabel("Email").fill("")
59
    await page.locator("body").click()
60
61
    await expect(page.getByText("Invalid email address")).toBeVisible()
62
  })
63
})
64
65
test.describe("Edit user email", () => {
66
  test.use({ storageState: { cookies: [], origins: [] } })
67
68
  test("Edit user email with a valid email", async ({ page }) => {
69
    const email = randomEmail()
70
    const password = randomPassword()
71
    const updatedEmail = randomEmail()
72
73
    await createUser({ email, password })
74
    await logInUser(page, email, password)
75
    await page.goto("/settings")
76
    await page.getByRole("tab", { name: "My profile" }).click()
77
78
    await page.getByRole("button", { name: "Edit" }).click()
79
    await page.getByLabel("Email").fill(updatedEmail)
80
    await page.getByRole("button", { name: "Save" }).click()
81
82
    await expect(page.getByText("User updated successfully")).toBeVisible()
83
    await expect(
84
      page.locator("form").getByText(updatedEmail, { exact: true }),
85
    ).toBeVisible()
86
  })
87
})
88
89
test.describe("Cancel edit actions", () => {
90
  test.use({ storageState: { cookies: [], origins: [] } })
91
92
  test("Cancel edit action restores original name", async ({ page }) => {
93
    const email = randomEmail()
94
    const password = randomPassword()
95
    const user = await createUser({ email, password })
96
97
    await logInUser(page, email, password)
98
    await page.goto("/settings")
99
    await page.getByRole("tab", { name: "My profile" }).click()
100
    await page.getByRole("button", { name: "Edit" }).click()
101
    await page.getByLabel("Full name").fill("Test User")
102
    await page.getByRole("button", { name: "Cancel" }).first().click()
103
104
    await expect(
105
      page.locator("form").getByText(user.full_name as string, { exact: true }),
106
    ).toBeVisible()
107
  })
108
109
  test("Cancel edit action restores original email", async ({ page }) => {
110
    const email = randomEmail()
111
    const password = randomPassword()
112
    await createUser({ email, password })
113
114
    await logInUser(page, email, password)
115
    await page.goto("/settings")
116
    await page.getByRole("tab", { name: "My profile" }).click()
117
    await page.getByRole("button", { name: "Edit" }).click()
118
    await page.getByLabel("Email").fill(randomEmail())
119
    await page.getByRole("button", { name: "Cancel" }).first().click()
120
121
    await expect(
122
      page.locator("form").getByText(email, { exact: true }),
123
    ).toBeVisible()
124
  })
125
})
126
127
test.describe("Change password", () => {
128
  test.use({ storageState: { cookies: [], origins: [] } })
129
130
  test("Update password successfully", async ({ page }) => {
131
    const email = randomEmail()
132
    const password = randomPassword()
133
    const newPassword = randomPassword()
134
135
    await createUser({ email, password })
136
    await logInUser(page, email, password)
137
138
    await page.goto("/settings")
139
    await page.getByRole("tab", { name: "Password" }).click()
140
    await page.getByTestId("current-password-input").fill(password)
141
    await page.getByTestId("new-password-input").fill(newPassword)
142
    await page.getByTestId("confirm-password-input").fill(newPassword)
143
    await page.getByRole("button", { name: "Update Password" }).click()
144
145
    await expect(page.getByText("Password updated successfully")).toBeVisible()
146
147
    await logOutUser(page)
148
    await logInUser(page, email, newPassword)
149
  })
150
})
151
152
test.describe("Change password validation", () => {
153
  test.use({ storageState: { cookies: [], origins: [] } })
154
  let email: string
155
  let password: string
156
157
  test.beforeAll(async () => {
158
    email = randomEmail()
159
    password = randomPassword()
160
    await createUser({ email, password })
161
  })
162
163
  test.beforeEach(async ({ page }) => {
164
    await logInUser(page, email, password)
165
    await page.goto("/settings")
166
    await page.getByRole("tab", { name: "Password" }).click()
167
  })
168
169
  test("Update password with weak passwords", async ({ page }) => {
170
    const weakPassword = "weak"
171
172
    await page.getByTestId("current-password-input").fill(password)
173
    await page.getByTestId("new-password-input").fill(weakPassword)
174
    await page.getByTestId("confirm-password-input").fill(weakPassword)
175
    await page.getByRole("button", { name: "Update Password" }).click()
176
177
    await expect(
178
      page.getByText("Password must be at least 8 characters"),
179
    ).toBeVisible()
180
  })
181
182
  test("New password and confirmation password do not match", async ({
183
    page,
184
  }) => {
185
    await page.getByTestId("current-password-input").fill(password)
186
    await page.getByTestId("new-password-input").fill(randomPassword())
187
    await page.getByTestId("confirm-password-input").fill(randomPassword())
188
    await page.getByRole("button", { name: "Update Password" }).click()
189
190
    await expect(page.getByText("The passwords don't match")).toBeVisible()
191
  })
192
193
  test("Current password and new password are the same", async ({ page }) => {
194
    await page.getByTestId("current-password-input").fill(password)
195
    await page.getByTestId("new-password-input").fill(password)
196
    await page.getByTestId("confirm-password-input").fill(password)
197
    await page.getByRole("button", { name: "Update Password" }).click()
198
199
    await expect(
200
      page.getByText("New password cannot be the same as the current one"),
201
    ).toBeVisible()
202
  })
203
})
204
205
test("Appearance button is visible in sidebar", async ({ page }) => {
206
  await page.goto("/settings")
207
  await expect(page.getByTestId("theme-button")).toBeVisible()
208
})
209
210
test("User can switch between theme modes", async ({ page }) => {
211
  await page.goto("/settings")
212
213
  await page.getByTestId("theme-button").click()
214
  await page.getByTestId("dark-mode").click()
215
  await expect(page.locator("html")).toHaveClass(/dark/)
216
217
  await expect(page.getByTestId("dark-mode")).not.toBeVisible()
218
219
  await page.getByTestId("theme-button").click()
220
  await page.getByTestId("light-mode").click()
221
  await expect(page.locator("html")).toHaveClass(/light/)
222
})
223
224
test("Selected mode is preserved across sessions", async ({ page }) => {
225
  await page.goto("/settings")
226
227
  await page.getByTestId("theme-button").click()
228
  if (
229
    await page.evaluate(() =>
230
      document.documentElement.classList.contains("dark"),
231
    )
232
  ) {
233
    await page.getByTestId("light-mode").click()
234
    await page.getByTestId("theme-button").click()
235
  }
236
237
  const isLightMode = await page.evaluate(() =>
238
    document.documentElement.classList.contains("light"),
239
  )
240
  expect(isLightMode).toBe(true)
241
242
  await page.getByTestId("theme-button").click()
243
  await page.getByTestId("dark-mode").click()
244
  let isDarkMode = await page.evaluate(() =>
245
    document.documentElement.classList.contains("dark"),
246
  )
247
  expect(isDarkMode).toBe(true)
248
249
  await logOutUser(page)
250
  await logInUser(page, firstSuperuser, firstSuperuserPassword)
251
252
  isDarkMode = await page.evaluate(() =>
253
    document.documentElement.classList.contains("dark"),
254
  )
255
  expect(isDarkMode).toBe(true)
256
})
257