Skip to content

Commit 28729bb

Browse files
committed
5d save progress
1 parent febd6f4 commit 28729bb

File tree

2 files changed

+383
-289
lines changed

2 files changed

+383
-289
lines changed

src/content/5/en/part5d.md

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,27 @@ Some tests might pass one time and fail another, even if the code does not chang
2424

2525
Perhaps the two easiest libraries for End to End testing at the moment are [Cypress](https://www.cypress.io/) and [Playwright](https://playwright.dev/).
2626

27-
From the statistics on [npmtrends.com](https://npmtrends.com/cypress-vs-playwright) we see that Cypress, which dominated the market for the last five years, is still the clear number one, but Playwright is on a rapid rise:
27+
From the statistics on [npmtrends.com](https://npmtrends.com/cypress-vs-playwright) we see that Cypress, which dominated the market for the last five years, is still clearly the number one, but Playwright is on a rapid rise:
2828

2929
![cypress vs playwright in npm trends](../../images/5/cvsp.png)
3030

3131
This course has been using Cypress for years. Now Playwright is a new addition. You can choose whether to complete the E2E testing part of the course with Cypress or Playwright. The operating principles of both libraries are very similar, so your choice is not very important. However, Playwright is now the preferred E2E library for the course.
3232

3333
If your choice is Playwright, please proceed. If you end up using Cypress, go [here](/en/part5/end_to_end_testing_cypress).
3434

35-
3635
### Playwright
3736

3837
So [Playwright](https://playwright.dev/) is a newcomer to the End to End tests, which started to explode in popularity towards the end of 2023. Playwright is roughly on a par with Cypress in terms of ease of use. The libraries are slightly different in terms of how they work. Cypress is radically different from most libraries suitable for E2E testing, as Cypress tests are run entirely within the browser. Playwright's tests, on the other hand, are executed in the Node process, which is connected to the browser via programming interfaces.
3938

4039
Many blogs have been written about library comparisons, e.g. [this](https://www.lambdatest.com/blog/cypress-vs-playwright/) and [this](https://www.browserstack.com/guide/playwright-vs-cypress).
4140

42-
It is difficult to say which library is better. One advantage of Playwright is its browser support, Playwright supports Chrome, Firefox and Webkit-based browsers like Safari. Currently Cypress includes support for all these browsers, although Webkit support is experimental and does not support all Cypress features. At the time of writing (1.3.2024), my personal preference leans slightly towards Playwright.
41+
It is difficult to say which library is better. One advantage of Playwright is its browser support; Playwright supports Chrome, Firefox and Webkit-based browsers like Safari. Currently, Cypress includes support for all these browsers, although Webkit support is experimental and does not support all of Cypress features. At the time of writing (1.3.2024), my personal preference leans slightly towards Playwright.
4342

4443
Now let's explore Playwright.
4544

4645
### Initializing tests
4746

48-
Unlike the backend tests or unit tests done on the React front-end, End to End tests do not need to be located in the same npm project where the code is. Let's make a completely separate project for the E2E tests with the _npm init_ command. Then install Playwright by running in the new project directory the command
47+
Unlike the backend tests or unit tests done on the React front-end, End to End tests do not need to be located in the same npm project where the code is. Let's make a completely separate project for the E2E tests with the _npm init_ command. Then install Playwright by running in the new project directory the command:
4948

5049
```js
5150
npm init playwright@latest
@@ -68,7 +67,7 @@ Let's define an npm script for running tests and test reports in _package.json_:
6867
}
6968
```
7069

71-
During installation, the following is printed to the console
70+
During installation, the following is printed to the console:
7271

7372
```
7473
And check out the following files:
@@ -77,7 +76,7 @@ And check out the following files:
7776
- ./playwright.config.js - Playwright Test configuration
7877
```
7978

80-
that is, the installation created a few example tests for the project.
79+
that is, the location of a few example tests for the project that the installation has created.
8180

8281
Let's run the tests:
8382

@@ -102,7 +101,7 @@ The tests pass. A more detailed test report can be opened either with the comman
102101
npm run test:report
103102
```
104103

105-
Tests can also be run via the graphical UI with a command
104+
Tests can also be run via the graphical UI with the command:
106105

107106
```
108107
npm run test -- --ui
@@ -131,13 +130,13 @@ test('get started link', async ({ page }) => {
131130
});
132131
```
133132

134-
The first line of the test function says that the tests are testing the page at https://playwright.dev/.
133+
The first line of the test functions says that the tests are testing the page at https://playwright.dev/.
135134

136-
### Testing own code
135+
### Testing our own code
137136

138137
Now let's remove the sample tests and start testing our own application.
139138

140-
Playwright tests assume that the system under test is running when the tests are executed, i.e. unlike e.g. backend integration tests, Playwright tests <i>do not start</i> the system under test during testing.
139+
Playwright tests assume that the system under test is running when the tests are executed. Unlike, for example, backend integration tests, Playwright tests <i>do not start</i> the system under test during testing.
141140

142141
Let's make an npm script for the <i>backend</i>, which will enable it to be started in testing mode, i.e. so that <i>NODE\_ENV</i> gets the value <i>test</i>.
143142

@@ -173,7 +172,7 @@ test('front page can be opened', async ({ page }) => {
173172
})
174173
```
175174

176-
First, the test opens the application with the method [page.goto](https://playwright.dev/docs/writing-tests#navigation). After this, the test uses the [page.getByText](https://playwright.dev/docs/api/class-page#page-get-by-text) to get a [locator](https://playwright.dev/docs/locators) that corresponds to the element where the text <i>Notes</i> is found.
175+
First, the test opens the application with the method [page.goto](https://playwright.dev/docs/writing-tests#navigation). After this, it uses the [page.getByText](https://playwright.dev/docs/api/class-page#page-get-by-text) to get a [locator](https://playwright.dev/docs/locators) that corresponds to the element where the text <i>Notes</i> is found.
177176

178177
The method [toBeVisible](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-visible) ensures that the element corresponding to the locator is visible at the page.
179178

@@ -193,28 +192,28 @@ test('front page can be opened', async ({ page }) => {
193192
})
194193
```
195194

196-
As expected, the test fails. Playwright opens the test report in the browser and it becomes clear that Playwright has actually performed the tests with three different browsers: Chrome, one Firefox and Webkit, i.e. the browser engine used by Safari:
195+
As expected, the test fails. Playwright opens the test report in the browser and it becomes clear that Playwright has actually performed the tests with three different browsers: Chrome, Firefox and Webkit, i.e. the browser engine used by Safari:
197196

198-
![](../../images/5/play2.png)
197+
![test report showing the test failing in three different browsers](../../images/5/play2.png)
199198

200199
By clicking on the report of one of the browsers, we can see a more detailed error message:
201200

202-
![](../../images/5/play3a.png)
201+
![test error message](../../images/5/play3a.png)
203202

204-
In the big picture, it is of course a very good thing that the testing takes place with all three commonly used browser engines, but this is slow, and when developing the tests it is probably best to carry out the tests mainly with only one browser. You can define the browser engine to be used with the command line parameter:
203+
In the big picture, it is of course a very good thing that the testing takes place with all three commonly used browser engines, but this is slow, and when developing the tests it is probably best to carry them out mainly with only one browser. You can define the browser engine to be used with the command line parameter:
205204

206205
```js
207206
npm test -- --project chromium
208207
```
209208

210-
Now let's correct the outdated year in the frontend code that caused the error in the code.
209+
Now let's correct the outdated year in the frontend code that caused the error.
211210

212211
Before we continue, let's add a _describe_ block to the tests:
213212

214213
```js
215214
const { test, describe, expect } = require('@playwright/test')
216215

217-
describe('Note app', () => {
216+
describe('Note app', () => { // highlight-line
218217
test('front page can be opened', async ({ page }) => {
219218
await page.goto('http://localhost:5173')
220219

@@ -225,7 +224,7 @@ describe('Note app', () => {
225224
})
226225
```
227226

228-
Before we move on, let's break the tests one more time. We notice that the execution of the tests is quite fast when the tests pass, but much slower if the tests do not pass. The reason for this is that Playwright's policy is to wait for searched elements until [they are rendered and ready for action](https://playwright.dev/docs/actionability). If the element is not found, a _TimeoutError_ is raised and the test fails. Playwright waits for elements by default for 5 or 30 seconds [depending on the functions used in testing](https://playwright.dev/docs/test-timeouts#introduction).
227+
Before we move on, let's break the tests one more time. We notice that the execution of the tests is quite fast when they pass, but much slower if the they do not pass. The reason for this is that Playwright's policy is to wait for searched elements until [they are rendered and ready for action](https://playwright.dev/docs/actionability). If the element is not found, a _TimeoutError_ is raised and the test fails. Playwright waits for elements by default for 5 or 30 seconds [depending on the functions used in testing](https://playwright.dev/docs/test-timeouts#introduction).
229228

230229
When developing tests, it may be wiser to reduce the waiting time to a few seconds. According to the [documentation](https://playwright.dev/docs/test-timeouts), this can be done by changing the file _playwright.config.js_ as follows:
231230

@@ -242,7 +241,7 @@ We also made two other changes to the file, and specified that all tests [be exe
242241

243242
### Writing on the form
244243

245-
Let's expand the tests so that the test tries to log into the application. Let's assume that a user is stored in the database, with username <i>mluukkai</i> and password <i>salainen</i>.
244+
Let's write a new test that tries to log into the application. Let's assume that a user is stored in the database, with username <i>mluukkai</i> and password <i>salainen</i>.
246245

247246
Let's start by opening the login form.
248247

@@ -268,11 +267,11 @@ npm test -- --ui
268267

269268
We now see that the test finds the button
270269

271-
![](../../images/5/play4.png)
270+
![playwright UI rendering the notes app while testing it](../../images/5/play4.png)
272271

273272
After clicking, the form will appear
274273

275-
![](../../images/5/play5.png)
274+
![playwright UI rendering the login form of the notes app](../../images/5/play5.png)
276275

277276
When the form is opened, the test should look for the text fields and enter the username and password in them. Let's make the first attempt using the method [page.getByRole](https://playwright.dev/docs/api/class-page#page-get-by-role):
278277

@@ -284,7 +283,7 @@ describe('Note app', () => {
284283
await page.goto('http://localhost:5173')
285284

286285
await page.getByRole('button', { name: 'log in' }).click()
287-
await page.getByRole('textbox').fill('mluukkai')
286+
await page.getByRole('textbox').fill('mluukkai') // highlight-line
288287
})
289288
})
290289
```
@@ -307,11 +306,13 @@ describe('Note app', () => {
307306
await page.goto('http://localhost:5173')
308307

309308
await page.getByRole('button', { name: 'log in' }).click()
309+
// highlight-start
310310
await page.getByRole('textbox').first().fill('mluukkai')
311311
await page.getByRole('textbox').last().fill('salainen')
312312
await page.getByRole('button', { name: 'login' }).click()
313313

314314
await expect(page.getByText('Matti Luukkainen logged in')).toBeVisible()
315+
// highlight-end
315316
})
316317
})
317318
```
@@ -327,12 +328,13 @@ describe('Note app', () => {
327328
await page.goto('http://localhost:5173')
328329

329330
await page.getByRole('button', { name: 'log in' }).click()
331+
// highlight-start
330332
const textboxes = await page.getByRole('textbox').all()
331333

332334
await textboxes[0].fill('mluukkai')
333335
await textboxes[1].fill('salainen')
336+
// highlight-end
334337

335-
// await page.getByRole('textbox').last().fill('salainen')
336338
await page.getByRole('button', { name: 'login' }).click()
337339

338340
await expect(page.getByText('Matti Luukkainen logged in')).toBeVisible()
@@ -342,7 +344,7 @@ describe('Note app', () => {
342344

343345
Both this and the previous version of the test work. However, both are problematic to the extent that if the registration form is changed, the tests may break, as they rely on the fields to be on the page in a certain order.
344346

345-
A better solution is to define unique test id attributes for the fields, and search the fields in the tests based on them using the method [getByTestId](https://playwright.dev/docs/api/class-page#page-get-by-test-id ).
347+
A better solution is to define unique test id attributes for the fields, to search for them in the tests using the method [getByTestId](https://playwright.dev/docs/api/class-page#page-get-by-test-id ).
346348

347349
Let's expand the login form as follows
348350

@@ -378,7 +380,7 @@ const LoginForm = ({ ... }) => {
378380
}
379381
```
380382

381-
Test changes as follows
383+
Test changes as follows:
382384

383385
```js
384386
describe('Note app', () => {
@@ -400,8 +402,6 @@ describe('Note app', () => {
400402

401403
Note that passing the test at this stage requires that there is a user in the <i>test</i> database of the backend with username <i>mluukkai</i> and password <i>salainen</i>. Create a user if needed!
402404

403-
Initialization of tests
404-
405405
Since both tests start in the same way, i.e. by opening the page <i>http://localhost:5173</i>, it is recommended to isolate the common part in the <i>beforeEach</i> block that is executed before each test:
406406

407407
```js
@@ -428,7 +428,6 @@ describe('Note app', () => {
428428
await expect(page.getByText('Matti Luukkainen logged in')).toBeVisible()
429429
})
430430
})
431-
432431
```
433432

434433
### Testing note creation
@@ -457,20 +456,19 @@ describe('Note app', () => {
457456
})
458457
})
459458
})
460-
461459
```
462460

463-
The test is defined in its own _describe_ block. Creating a note requires that the user is logged in, and the login is handled in the _beforeEach_ block.
461+
The test is defined in its own _describe_ block. Creating a note requires that the user is logged in, which is handled in the _beforeEach_ block.
464462

465-
The test trusts that when creating a new note there is only one input field on the page, i.e. it searches for the field as follows
463+
The test trusts that when creating a new note, there is only one input field on the page, so it searches for it as follows:
466464

467465
```js
468466
page.getByRole('textbox')
469467
```
470468

471-
If there were more fields, the test would break. Because of this, it would be better to add a test-id to the field of the form and search for the field in the test based on the id.
469+
If there were more fields, the test would break. Because of this, it would be better to add a test-id to the form input and search for it in the test based on this id.
472470

473-
**Note:** the test will only pass the first time. The reason for this is that expectation
471+
**Note:** the test will only pass the first time. The reason for this is that its expectation
474472

475473
```js
476474
await expect(page.getByText('a note created by playwright')).toBeVisible()
@@ -510,7 +508,6 @@ describe('Note app', () => {
510508
})
511509
})
512510
})
513-
514511
```
515512

516513
Since we have prevented the tests from running in parallel, Playwright runs the tests in the order they appear in the test code. That is, first the test <i>user can log in</i>, where the user logs into the application, is performed. After this the test <i>a new note can be created</i> gets executed, which also does a log in, in the <i>beforeEach</i> block. Why is this done, isn't the user already logged in thanks to the previous test? No, because the execution of <i>each</i> test starts from the browser's "zero state", all changes made to the browser's state by the previous tests are reset.
@@ -620,6 +617,7 @@ describe('Note app', () => {
620617
describe('when logged in', () => {
621618
// ...
622619

620+
// highlight-start
623621
describe('and a note exists', () => {
624622
beforeEach(async ({ page }) => {
625623
await page.getByRole('button', { name: 'new note' }).click()
@@ -631,6 +629,7 @@ describe('Note app', () => {
631629
await page.getByRole('button', { name: 'make not important' }).click()
632630
await expect(page.getByText('make important')).toBeVisible()
633631
})
632+
// highlight-end
634633
})
635634
})
636635
})

0 commit comments

Comments
 (0)