Skip to content

Commit 698239f

Browse files
committed
add solution summaries to exercises
1 parent 186871b commit 698239f

File tree

29 files changed

+155
-269
lines changed

29 files changed

+155
-269
lines changed
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
# Install & configure
22

3-
- Mention the explicit `workers` value in `playwright.config.ts`.
3+
## Summary
44

5-
Good job! 👏
5+
1. Install Playwright.
6+
1. Create `playwright.config.ts`.
7+
1. Specify `projects` using Chromium as the browser.
8+
1. Specify quality-of-life options like `fullyParallel` or `forbidOnly`.
9+
1. A brief mention of `workers` set to `1` on CI.
610

7-
Please wait for the others to finish so we could go through the solution to this exercise together.
11+
---
12+
13+
1. Create a simple test at `./tests/epicweb.test.ts`.
14+
1. Test block structure (`test()`).
15+
1. The `page` fixture.
16+
1. Use `page.goto()` to visit pages (external and internal).
17+
1. Promise-based `expect()` assertions (retriability).
18+
19+
---
20+
21+
1. Add the `test:e2e` script in `package.json` that runs Playwright.
22+
1. Run the test via `npm run test:e2e`. See the result.
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Running the app
22

3-
Good job! 👏
3+
## Summary
44

5-
Please wait for the others to finish so we could go through the solution to this exercise together.
5+
1. Introduce to the Epic Stack app, briefly.
6+
7+
---
8+
9+
1. Changes in `playwright.config.ts`, mainly:
10+
1. `PORT` at which the app will be running. `use.baseURL` to we can use relative URLs in our tests.
11+
1. The `webServer` option to spawn our app and wait at the given `PORT`.
12+
1. `reuseExistingServer` allows us to use an already running app locally for faster tests.
13+
1. `testDir` set to `./tests/e2e` since our app now also has other types of tests.
14+
15+
---
16+
17+
1. New test at `tests/e2e/homepage.test.ts`. We need to test our app, not running `epicweb.dev`.
18+
1. The basic structure of the test remains the same though:
19+
1. Visit the homepage `/` (since we enabled relative URLs).
20+
1. Assert that the heading text is visible to the user.
21+
1. Verify the test via `npm run test:e2e`.

exercises/01.fundamentals/03.solution.custom-fixtures/README.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Custom fixtures
22

3-
Good job! 👏
3+
## Summary
44

5-
Please wait for the others to finish so we could go through the solution to this exercise together.
5+
1. Implement the new `navigate` fixture in `tests/text-extend.ts`. Use generated type definitions from React Router for type-safe navigation in tests. Build the app to generate the type definitions.
6+
1. Change the existing test at `tests/e2e/homepage.test.ts` to use the new `navigate` fixture instead of `page`. Notice the route suggestions! Neat.
67

78
## Related materials
89

exercises/02.authentication/01.problem.basic/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@
145145
"jsdom": "^25.0.1",
146146
"msw": "^2.7.6",
147147
"npm-run-all": "^4.1.5",
148-
"playwright-persona": "^0.2.8",
149148
"prettier": "^3.5.3",
150149
"prettier-plugin-sql": "^0.19.0",
151150
"prettier-plugin-tailwindcss": "^0.6.11",
Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,18 @@
11
import { test as testBase, expect } from '@playwright/test'
2-
import {
3-
definePersona,
4-
combinePersonas,
5-
type AuthenticateFunction,
6-
} from 'playwright-persona'
72
import { href, type Register } from 'react-router'
8-
import { getPasswordHash } from '#app/utils/auth.server.ts'
9-
import { prisma } from '#app/utils/db.server.ts'
10-
import { generateUserInfo } from '#tests/db-utils'
113

124
interface Fixtures {
135
navigate: <T extends keyof Register['pages']>(
146
...args: Parameters<typeof href<T>>
157
) => Promise<void>
16-
authenticate: AuthenticateFunction<[typeof user]>
178
}
189

19-
const user = definePersona('user', {
20-
async createSession({ page }) {
21-
const user = await prisma.user.create({
22-
data: {
23-
...generateUserInfo(),
24-
roles: { connect: { name: 'user' } },
25-
password: { create: { hash: await getPasswordHash('supersecret') } },
26-
},
27-
})
28-
29-
await page.goto('/login')
30-
await page.getByLabel('Username').fill(user.username)
31-
await page.getByLabel('Password').fill('supersecret')
32-
await page.getByRole('button', { name: 'Log in' }).click()
33-
await page.getByText(user.name!).waitFor({ state: 'visible' })
34-
35-
return { user }
36-
},
37-
async verifySession({ page, session }) {
38-
await page.goto('/')
39-
await expect(page.getByText(session.user.name!)).toBeVisible({
40-
timeout: 100,
41-
})
42-
},
43-
async destroySession({ session }) {
44-
await prisma.user.deleteMany({ where: { id: session.user.id } })
45-
},
46-
})
47-
4810
export const test = testBase.extend<Fixtures>({
4911
async navigate({ page }, use) {
5012
await use(async (...args) => {
5113
await page.goto(href(...args))
5214
})
5315
},
54-
authenticate: combinePersonas(user),
5516
})
5617

5718
export { expect }

exercises/02.authentication/01.solution.basic/README.mdx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22

33
- Testing a basic (email+password) authentication.
44

5-
## Solution
5+
## Summary
66

7-
- Explain why `timeout` here is necessary:
7+
1. Create new test at `tests/e2e/authentication-basic.test.ts`.
8+
1. Create a new test case for successful authentication using email and password.
9+
1. In the test, use the existing `createUser()` utility, which generates a new user and creates it in the database.
10+
1. Log in by filling the log in form as the user would.
11+
1. Talk about _locators_ in Playwright. They are promises to values.
12+
1. Assert that the authentication is successful based on the user profile link being visible on the screen.
13+
1. `npm run test:e2e:run`.
814

9-
```ts
10-
async verifySession({ page, session }) {
11-
await page.goto('/')
12-
await expect(page.getByText(session.user.name!)).toBeVisible({
13-
timeout: 100,
14-
})
15-
},
16-
```
15+
---
1716

18-
Without it, the default Playwright timeout of 5s will make auth extremely slow.
17+
1. Another test case in the same file focused on displaying an error when authentication fails.
18+
1. Similar steps but _no setup_ so the user doesn't exist.
19+
1. Assert the error message being shown (correct _role_ and text).
20+
1. `npm run test:e2e:run`.

exercises/02.authentication/01.solution.basic/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@
145145
"jsdom": "^25.0.1",
146146
"msw": "^2.7.6",
147147
"npm-run-all": "^4.1.5",
148-
"playwright-persona": "^0.2.8",
149148
"prettier": "^3.5.3",
150149
"prettier-plugin-sql": "^0.19.0",
151150
"prettier-plugin-tailwindcss": "^0.6.11",
Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,18 @@
11
import { test as testBase, expect } from '@playwright/test'
2-
import {
3-
definePersona,
4-
combinePersonas,
5-
type AuthenticateFunction,
6-
} from 'playwright-persona'
72
import { href, type Register } from 'react-router'
8-
import { getPasswordHash } from '#app/utils/auth.server.ts'
9-
import { prisma } from '#app/utils/db.server.ts'
10-
import { generateUserInfo } from '#tests/db-utils'
113

124
interface Fixtures {
135
navigate: <T extends keyof Register['pages']>(
146
...args: Parameters<typeof href<T>>
157
) => Promise<void>
16-
authenticate: AuthenticateFunction<[typeof user]>
178
}
189

19-
const user = definePersona('user', {
20-
async createSession({ page }) {
21-
const user = await prisma.user.create({
22-
data: {
23-
...generateUserInfo(),
24-
roles: { connect: { name: 'user' } },
25-
password: { create: { hash: await getPasswordHash('supersecret') } },
26-
},
27-
})
28-
29-
await page.goto('/login')
30-
await page.getByLabel('Username').fill(user.username)
31-
await page.getByLabel('Password').fill('supersecret')
32-
await page.getByRole('button', { name: 'Log in' }).click()
33-
await page.getByText(user.name!).waitFor({ state: 'visible' })
34-
35-
return { user }
36-
},
37-
async verifySession({ page, session }) {
38-
await page.goto('/')
39-
await expect(page.getByText(session.user.name!)).toBeVisible({
40-
timeout: 100,
41-
})
42-
},
43-
async destroySession({ session }) {
44-
await prisma.user.deleteMany({ where: { id: session.user.id } })
45-
},
46-
})
47-
4810
export const test = testBase.extend<Fixtures>({
4911
async navigate({ page }, use) {
5012
await use(async (...args) => {
5113
await page.goto(href(...args))
5214
})
5315
},
54-
authenticate: combinePersonas(user),
5516
})
5617

5718
export { expect }

exercises/02.authentication/02.problem.2fa/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@
145145
"jsdom": "^25.0.1",
146146
"msw": "^2.7.6",
147147
"npm-run-all": "^4.1.5",
148-
"playwright-persona": "^0.2.8",
149148
"prettier": "^3.5.3",
150149
"prettier-plugin-sql": "^0.19.0",
151150
"prettier-plugin-tailwindcss": "^0.6.11",
Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,18 @@
11
import { test as testBase, expect } from '@playwright/test'
2-
import {
3-
definePersona,
4-
combinePersonas,
5-
type AuthenticateFunction,
6-
} from 'playwright-persona'
72
import { href, type Register } from 'react-router'
8-
import { getPasswordHash } from '#app/utils/auth.server.ts'
9-
import { prisma } from '#app/utils/db.server.ts'
10-
import { generateUserInfo } from '#tests/db-utils'
113

124
interface Fixtures {
135
navigate: <T extends keyof Register['pages']>(
146
...args: Parameters<typeof href<T>>
157
) => Promise<void>
16-
authenticate: AuthenticateFunction<[typeof user]>
178
}
189

19-
const user = definePersona('user', {
20-
async createSession({ page }) {
21-
const user = await prisma.user.create({
22-
data: {
23-
...generateUserInfo(),
24-
roles: { connect: { name: 'user' } },
25-
password: { create: { hash: await getPasswordHash('supersecret') } },
26-
},
27-
})
28-
29-
await page.goto('/login')
30-
await page.getByLabel('Username').fill(user.username)
31-
await page.getByLabel('Password').fill('supersecret')
32-
await page.getByRole('button', { name: 'Log in' }).click()
33-
await page.getByText(user.name!).waitFor({ state: 'visible' })
34-
35-
return { user }
36-
},
37-
async verifySession({ page, session }) {
38-
await page.goto('/')
39-
await expect(page.getByText(session.user.name!)).toBeVisible({
40-
timeout: 100,
41-
})
42-
},
43-
async destroySession({ session }) {
44-
await prisma.user.deleteMany({ where: { id: session.user.id } })
45-
},
46-
})
47-
4810
export const test = testBase.extend<Fixtures>({
4911
async navigate({ page }, use) {
5012
await use(async (...args) => {
5113
await page.goto(href(...args))
5214
})
5315
},
54-
authenticate: combinePersonas(user),
5516
})
5617

5718
export { expect }

0 commit comments

Comments
 (0)