Skip to content

Commit 5c1744c

Browse files
committed
add 01/01 exercise text
1 parent 665a2be commit 5c1744c

6 files changed

Lines changed: 280 additions & 23 deletions

File tree

exercises/01.fundamentals/01.problem.install-and-configure/README.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ npm i @playwright/test --save-dev
1313
🐨 Then, install the binaries of the browsers you want to use for automated tests. In this workshop, we will be using Chromium for browser automation. Install it using this command:
1414

1515
```sh
16-
npx playwright install --with-deps chromium
16+
npx playwright install chromium --with-deps
1717
```
1818

1919
🐨 Next, create `playwright.config.ts` file at the root of your project. This is Playwright's configuration file. You will use it to set up Playwright in the way that makes sense for your project.
2020

21-
🐨 In `package.json`, create a new script called `test`. You will use this script to run your end-to-end tests at the end of this exercise. Use the `npx playwright test` command as the value for that script.
21+
🐨 In `package.json`, create a new script called `test:e2e`. You will use this script to run your end-to-end tests at the end of this exercise. Use the `npx playwright test` command as the value for that script.
2222

2323
🐨 Create a directory called `tests`. In that directory, create a new test file called `epicweb.test.ts`.
2424

@@ -45,4 +45,4 @@ To do that, your test would have to perform a series of steps:
4545
4646
3. Assert that the heading element is visible on the page via `await expect(element).toBeVisible()`.
4747

48-
🐨 Finally, run this test via `npm test` in the terminal. You should see it passing.
48+
🐨 Finally, run this test via `npm run test:e2e` in the terminal. You should see it passing.
Lines changed: 238 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,245 @@
11
# Install & configure
22

3-
## Summary
3+
## Installation
44

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.
5+
### Choosing the right package
106

11-
---
7+
There are actually _two_ different packages shipped by Playwright:
128

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).
9+
- `playwright`, which is a library to help you automate browsers;
10+
- `@playwright/test`, which is a testing framework build on top of the aforementioned library.
1811

19-
---
12+
Today, you would need the latter so let's get it installed:
2013

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.
14+
```sh
15+
npm i @playwright/test --save-dev
16+
```
17+
18+
> I'm installing this package as a development dependency (`--save-dev`), but this is not required. The distinction between save/dev dependencies is irrelevant in the context of enterprise applications.
19+
20+
### Installing browser binaries
21+
22+
You can think of Playwright as a wrapped around the browser. And as such, it doesn't actually ship any browsers with it. You have to install the exact browser binaries you need to test your application.
23+
24+
Luckily, Playwright comes with a CLI to help you do that (**don't run it just yet**):
25+
26+
```sh
27+
npx playwright install
28+
```
29+
30+
Without any arguments, this command will install _all binaries for all supported browsers_. This includes Chromium, Firefox, WebKit... That's quite a lot! Especially if you aren't using all of those browsers to test your app.
31+
32+
In this workshop, you will be using Chromium exclusively so it makes sense to install just that binary:
33+
34+
```sh
35+
npx playwright install chromium --with-deps
36+
```
37+
38+
> Notice the `--with-deps` flag here, which makes sure that all the system dependencies required by this browser binary are also installed. This is less relevant for local use but is extremely handy when running Playwright on CI.
39+
40+
## Configuration
41+
42+
Okay, so you've got Playwright installed. Now it's time to configure it.
43+
44+
Create a file called `playwright.config.ts` at the root of your project and fill it with a minimal configuration options:
45+
46+
```ts filename=playwright.config.ts
47+
import { defineConfig, devices } from '@playwright/test'
48+
49+
export default defineConfig({
50+
projects: [
51+
{
52+
name: 'chromium',
53+
use: {
54+
...devices['Desktop Chrome'],
55+
},
56+
},
57+
],
58+
fullyParallel: true,
59+
forbidOnly: !!process.env.CI,
60+
workers: process.env.CI ? 1 : undefined,
61+
})
62+
```
63+
64+
Let's go through every option and see what it does one-by-one.
65+
66+
### Projects
67+
68+
Similar to Vitest, you can have multiple test projects in Playwright. This allows you to decouple the test suites from the orchestration that runs it. In practice, this means you can:
69+
70+
- Have the same tests run in multiple browsers;
71+
- Have unique test suites that run only in some browsers;
72+
- Have browsers configured a certain way to make test cases possible (e.g. specific viewport sizes, enabling `prefers-reduced-motion`, using different locales or timezones).
73+
74+
```ts filename=playwright.config.ts highlight=4-11
75+
// ...
76+
77+
export default defineConfig({
78+
projects: [
79+
{
80+
name: 'chromium',
81+
use: {
82+
...devices['Desktop Chrome'],
83+
},
84+
},
85+
],
86+
// ...
87+
})
88+
```
89+
90+
In our case, we have a single project that uses `Desktop Chrome` as the browser.
91+
92+
### Full parallelization
93+
94+
Similar to Vitest, Playwright has the same behaviors when it comes to concurrency and parallelization:
95+
96+
- All test _files_ run in parallel in isolated workers;
97+
- All test _cases_ in those files run sequentially.
98+
99+
This is a good default, but we are going to opt out from it and make all our test cases run concurrently by setting `fullyParallel` option to `true`:
100+
101+
```ts filename=playwright.config.ts highlight=5
102+
// ...
103+
104+
export default defineConfig({
105+
// ...
106+
fullyParallel: true,
107+
// ...
108+
})
109+
```
110+
111+
### Forbidding `test.only`
112+
113+
Isolating invididual test cases while writing or debugging tests can be really handy. Playwright supports that via `test.only()`. The problem is, it's extremely easy to forget about a test case you've isolated and get your CI running just that single test 😬
114+
115+
There's an option to guard you from that mistake by making Playwright throw an error if it detects `test.only`. Naturally, let's set that option to `true` only for remote, CI runs:
116+
117+
```ts filename=playwright.config.ts highlight=5
118+
// ...
119+
120+
export default defineConfig({
121+
// ...
122+
forbidOnly: !!process.env.CI,
123+
// ...
124+
})
125+
```
126+
127+
> The `process.env.CI` environment variable is commonly set by CI providers, like GitHub Actions. Make sure to fine-tune this condition if your provider marks the environment differently.
128+
129+
### Worker count
130+
131+
The last thing we are going to configure is the `workers` option that controls the number of parallel workers spawned for each test run. By default, Playwright will use _half_ of your available CPU cores for that, but that can cause trouble in CI runs.
132+
133+
It's generally recommended to use a _single_ worker for your end-to-end tests on CI.
134+
135+
```ts filename=playwright.config.ts highlight=5
136+
// ...
137+
138+
export default defineConfig({
139+
// ...
140+
workers: process.env.CI ? 1 : undefined,
141+
// ...
142+
})
143+
```
144+
145+
This means _slower_ but more _reliable_ test results on machines that are normally nowhere near as powerful as those you use for development.
146+
147+
> Note that you should configure Playwright to suit the specifics of your CI context. Today, we are going through the safe defaults that make sense in the majority of projects. The beauty is, **you can opt out from them!** And I strongly encourage you to experiment with your environment and find the optimal options for your end-to-end tests.
148+
149+
## Writing a test
150+
151+
End-to-end tests follow the same test anatomy you already know.
152+
153+
```ts
154+
import { test, expect } from '@playwright/test'
155+
156+
test('test title', testFunction)
157+
```
158+
159+
> Note that Playwright does _not_ expose its functions, like `test` or `expect` globally, and those have to be imported.
160+
161+
The test case we have at hand today is the one that makes sure a correct heading is displayed on the Epic Web website. So let's give this test a descriptive name and navigate to `https://epicweb.dev/` for starters.
162+
163+
```ts add=2
164+
test('displays the page heading', async ({ page }) => {
165+
await page.goto('https://epicweb.dev/')
166+
})
167+
```
168+
169+
In Playwright, you use the `page` object from the test context argument to interact with the underlying browser page. For example, calling `page.goto()` will trigger a navigation to the given URL!
170+
171+
<callout-warning>
172+
173+
The test context **MUST** be destructured in Playwright. The framework analyzes which context properties each test uses to implement lazy fixtures.
174+
175+
```ts remove=1-3 add=5-7
176+
test('displays the page heading', async (context) => {
177+
await context.page.goto(url)
178+
})
179+
180+
test('displays the page heading', async ({ page }) => {
181+
await page.goto(url)
182+
})
183+
```
184+
185+
</callout-warning>
186+
187+
### Locating elements
188+
189+
Next, let's find the `<h1>` element on the page that we want to assert on. You can use many of the `page.getBy*` utilities to locate elements by their accessible role, associated label text, text content, etc.
190+
191+
Let's use `page.getByRole()` to locate the heading we need by its role (`heading`) and its accessible name, which in case of the heading elements equals to its text content.
192+
193+
```ts add=6-8
194+
import { test, expect } from '@playwright/test'
195+
196+
test('displays the page heading', async ({ page }) => {
197+
await page.goto('https://epicweb.dev/')
198+
199+
page.getByRole('heading', {
200+
name: 'Full Stack Workshop Training for Professional Web Developers',
201+
})
202+
})
203+
```
204+
205+
When you find elements on the page, you don't get a direct reference to their DOM nodes straight away. Instead, Playwright gives you a _locator_. Think of it as a promise to that element that will be resolved only when the element is needed. For example, when you assert on it.
206+
207+
### Assertions
208+
209+
Finally, let's express some expectations, shall we?
210+
211+
We've already located the heading element we need, and all that remains is to make sure that it is visible on the page. To do that, use the `expect()` function, provide it the locator of the element you want to assert on, and use one of the many available matchers, like `.toBeVisible()`, for example.
212+
213+
```ts add=6-10
214+
import { test, expect } from '@playwright/test'
215+
216+
test('displays the page heading', async ({ page }) => {
217+
await page.goto('https://epicweb.dev/')
218+
219+
await expect(
220+
page.getByRole('heading', {
221+
name: 'Full Stack Workshop Training for Professional Web Developers',
222+
}),
223+
).toBeVisible()
224+
})
225+
```
226+
227+
> Note that matchers in Playwright _return a promise_ that you have to await. That is because every assertion is retryable by default to deal with asynchronicity and race conditions—two common problems that result in flakiness if left unchecked.
228+
229+
## Running tests
230+
231+
Now that the test is ready, let's run it, using the npm script we've prepared earlier:
232+
233+
```
234+
npm run test:e2e
235+
```
236+
237+
```
238+
Running 1 test using 1 worker
239+
240+
✓ 1 [chromium] › tests/epicweb.test.ts:3:5 › displays the page heading (935ms)
241+
242+
1 passed (2.8s)
243+
```
244+
245+
Sweet! :tada:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Fundamentals
22

3-
Congratulations, you've completed this exercise block! 🥳 Let's have a short rest and continue.
3+
Congratulations, you've completed this exercise block! 🥳 Have a short break and let's continue.
44

55
<callout-success>Meanwhile, I would be grateful if you _filled out the feedback form_ on your right. Your feedback helps me polish the exercises and improve the workshop before recording it. Thank you!</callout-success>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# Fundamentals
2+
3+
First, let's get on the same page when it comes to what an end-to-end test is and what it's meant for. Join me to learn how end-to-end tests complete your testing strategy without repeating everything you have tested before; how these tests manifest themselves in other mediums of software; what contributes to the infamous reputation of end-to-end tests being slow and flaky; what modern end-to-end testing frameworks you can choose from today, and which one you will be using in this workshop.
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Authentication
22

3-
- Authentication is both a feature to test and a prerequisite to test other features.
4-
- A login page example.
5-
- Users log in to _do_ something. Those actions treat authentication as a _given_.
6-
- In this block, you will learn how to test different authentication methods as well as how to provision authentication state as a part of your test setup.
3+
Authentication is an inseparable part of end-to-end tests just like it is an inseparable part of your applications. But there's a catch. Authentication encompases _two_ distinct areas from the testing perspective:
4+
5+
- **Authentication as a feature**. These are the authentication options you provide to your users (e.g. email+password, 2FA, passkeys, third-party providers);
6+
- **Authentication as a dependency**. The functionality behind authentication (e.g. managing resources, performing authorized operations).
7+
8+
In this block, you will learn about the difference between testing authentication as a feature and testing features that are behind authentication with Playwright.

exercises/README.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,31 @@
11
# End-to-end React Testing with Playwright
2+
3+
Hi :wave: Artem's here.
4+
5+
Today, you and I are going to explore end-to-end tests. You know the type—expensive, slow, and unreliable. Only you won't be writing any of those. Instead, I will teach you how to use Playwright to test your web applications in a meaningful and performant way.
6+
7+
## Exercises
8+
9+
This workshop consists of four exercise blocks.
10+
11+
### Fundamentals
12+
13+
You will start by installing Playwright and covering the essential configuration options for a great testing experience both locally and on CI. You will get familiar with the test structure, using the `page` object, and why it gives you element _locators_ instead of direct DOM references. And you will wrap up by writing your own custom fixture for type-safe page navigation in your end-to-end tests.
14+
15+
### Authentication
16+
17+
In this one, you will learn the differnce between testing authentication as a feature and testing features that are _behind_ authentication (yes, those require entirely different approaches). You will write tests for the most common authentication methods in web applications:
18+
19+
- **Basic**, where you will create a disposable `createUser()` utility that will create a test user in the database and delete it when the test is done;
20+
- **Two-factor Authentication**, where you will create test OTP tokens and store their verification records in the database for your test user;
21+
- **Passkeys**, where you will leverage a Chrome Developer Protocol session to create a web autnentication client with the passkey you provide and use it in your test.
22+
23+
After that, you will explore how to create and manage authentication in your test setup using [Personas](https://github.com/kettanaito/playwright-persona)—reproducible snapshots of different authentication states that bridge the DX gap of `setStorageState` in Playwright.
24+
25+
### Guides
26+
27+
Next, you will dive straight into practical tips for writing end-to-end tests: from provisioning mock databases and test data to mocking client-side requests. As a bonus, I will show you how to write tests by recording your interactions in the browser using Playwright's extension for Visual Studio Code.
28+
29+
### Debugging
30+
31+
It's become somewhat of a tradition to include debugging techniques in my workshops, and this one will be no different. In the final section, you will master the UI mode to preview and debug your tests _visually_ and after that, learn how to debug failed CI tests with the Trace Viewer (this is, seriously, such a time-saver).

0 commit comments

Comments
 (0)