Skip to content

Commit ad9dd68

Browse files
committed
moar changes 5 playwright
1 parent be8aed6 commit ad9dd68

File tree

1 file changed

+110
-112
lines changed

1 file changed

+110
-112
lines changed

src/content/5/fi/osa5e.md

Lines changed: 110 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -583,28 +583,34 @@ Testit ja frontendin tämänhetkinen koodi on kokonaisuudessaan [GitHubissa](htt
583583
584584
Tehdään nyt testi joka varmistaa, että kirjautumisyritys epäonnistuu jos salasana on väärä.
585585
586-
Cypress suorittaa oletusarvoisesti aina kaikki testit, ja testien määrän kasvaessa se alkaa olla aikaavievää. Uutta testiä kehitellessä tai rikkinäistä testiä debugatessa voidaan määritellä testi komennon <i>it</i> sijaan komennolla <i>it.only</i>, jolloin Cypress suorittaa ainoastaan sen testin. Kun testi on valmiina, voidaan <i>only</i> poistaa.
586+
Playwright suorittaa oletusarvoisesti aina kaikki testit, ja testien määrän kasvaessa se alkaa olla aikaavievää. Uutta testiä kehitellessä tai rikkinäistä testiä debugatessa voidaan määritellä testi komennon <i>test</i> sijaan komennolla <i>test.only</i>, jolloin Playwright suorittaa ainoastaan sen testin. Kun testi on valmiina, voidaan <i>only</i> poistaa.
587+
588+
Toinen vaihtoehto suorittaa yksittäinen testi, on käyttää komentoriviparametria
589+
590+
```
591+
npm test -- -g "login fails with wrong password"
592+
```
587593
588594
Testin ensimmäinen versio näyttää seuraavalta:
589595
590596
```js
591-
describe('Note app', function() {
597+
describe('Note app', () => {
592598
// ...
593599

594-
it.only('login fails with wrong password', function() {
595-
cy.contains('log in').click()
596-
cy.get('#username').type('mluukkai')
597-
cy.get('#password').type('wrong')
598-
cy.get('#login-button').click()
600+
test('login fails with wrong password', async ({ page }) => {
601+
await page.getByRole('button', { name: 'log in' }).click()
602+
await page.getByTestId('username').fill('mluukkai')
603+
await page.getByTestId('password').fill('wrong')
604+
await page.getByRole('button', { name: 'login' }).click()
599605

600-
cy.contains('wrong credentials')
606+
await expect(await page.getByText('wrong credentials')).toBeVisible()
601607
})
602608

603609
// ...
604610
)}
605611
```
606612
607-
Testi siis varmistaa komennon [cy.contains](https://docs.cypress.io/api/commands/contains.html#Syntax) avulla, että sovellus tulostaa virheilmoituksen.
613+
Testi siis varmistaa komennon [getByText](https://playwright.dev/docs/api/class-page#page-get-by-text) avulla, että sovellus tulostaa virheilmoituksen.
608614
609615
Sovellus renderöi virheilmoituksen CSS-luokan <i>error</i> sisältävään elementtiin:
610616
@@ -626,173 +632,165 @@ Voisimmekin tarkentaa testiä varmistamaan, että virheilmoitus tulostuu nimenom
626632
627633
628634
```js
629-
it('login fails with wrong password', function() {
635+
test('login fails with wrong password', async ({ page }) => {
630636
// ...
631637

632-
cy.get('.error').contains('wrong credentials') // highlight-line
638+
const errorDiv = await page.locator('.error')
639+
await expect(errorDiv).toContainText('wrong credentials')
633640
})
634641
```
635642
636-
Eli ensin etsitään komennolla [cy.get](https://docs.cypress.io/api/commands/get.html#Syntax) CSS-luokan <i>error</i> sisältävä komponentti ja sen jälkeen varmistetaan että virheilmoitus löytyy sen sisältä. Huomaa, että [luokan CSS-selektori](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) alkaa pisteellä, eli luokan <i>error</i> selektori on <i>.error</i>.
643+
Testi siis etsitään metodilla [page.locator](https://playwright.dev/docs/api/class-page#page-locator) CSS-luokan <i>error</i> sisältävän komponentin ja tallennetaan sen muuttujaan muuttujaan. Komponenttiin liittyvän teksstin oikeellisuus voidaan varmistaa ekspektaatiolla [toContainText](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-contain-text). Huomaa, että [luokan CSS-selektori](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) alkaa pisteellä, eli luokan <i>error</i> selektori on <i>.error</i>.
637644
638-
Voisimme tehdä saman myös käyttäen [should](https://docs.cypress.io/api/commands/should.html)-syntaksia:
645+
Ekspekaatiolla [toHaveCSS](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-css) on mahdollista testata tyylejä. Voimme esim. varmistaa, että virheilmoituksen väri on punainen, ja että sen ympärillä on border:
639646
640647
```js
641-
it('login fails with wrong password', function() {
648+
test('login fails with wrong password', async ({ page }) =>{
642649
// ...
643650

644-
cy.get('.error').should('contain', 'wrong credentials') // highlight-line
651+
const errorDiv = await page.locator('.error')
652+
await expect(errorDiv).toContainText('wrong credentials')
653+
await expect(errorDiv).toHaveCSS('border-style', 'solid')
654+
await expect(errorDiv).toHaveCSS('color', 'rgb(255, 0, 0)')
645655
})
646656
```
647657
648-
Shouldin käyttö on jonkin verran "hankalampaa" kuin komennon <i>contains</i>, mutta se mahdollistaa huomattavasti monipuolisemmat testit kuin pelkän tekstisisällön perusteella toimiva <i>contains</i>.
649-
650-
Lista yleisimmistä shouldin kanssa käytettävistä assertioista on [täällä](https://docs.cypress.io/guides/references/assertions.html#Common-Assertions).
651-
652-
Voimme esim. varmistaa, että virheilmoituksen väri on punainen, ja että sen ympärillä on border:
658+
Värit on määriteltävä Playwrightille [rgb](https://rgbcolorcode.com/color/red)-koodeina.
653659
654-
```js
655-
it('login fails with wrong password', function() {
656-
// ...
657-
658-
cy.get('.error').should('contain', 'wrong credentials')
659-
cy.get('.error').should('have.css', 'color', 'rgb(255, 0, 0)')
660-
cy.get('.error').should('have.css', 'border-style', 'solid')
661-
})
662-
```
663-
664-
Värit on määriteltävä Cypressille [rgb](https://rgbcolorcode.com/color/red)-koodeina.
665-
666-
Koska kaikki tarkastukset kohdistuvat samaan komennolla [cy.get](https://docs.cypress.io/api/commands/get.html#Syntax) haettuun elementtiin, ne voidaan ketjuttaa komennon [and](https://docs.cypress.io/api/commands/and.html) avulla:
667-
668-
```js
669-
it('login fails with wrong password', function() {
670-
// ...
671660
672-
cy.get('.error')
673-
.should('contain', 'wrong credentials')
674-
.and('have.css', 'color', 'rgb(255, 0, 0)')
675-
.and('have.css', 'border-style', 'solid')
676-
})
677-
```
678661
Viimeistellään testi vielä siten, että se varmistaa myös, että sovellus ei renderöi onnistunutta kirjautumista kuvaavaa tekstiä <i>'Matti Luukkainen logged in'</i>:
679662
680663
```js
681-
it('login fails with wrong password', function() {
682-
cy.contains('log in').click()
683-
cy.get('#username').type('mluukkai')
684-
cy.get('#password').type('wrong')
685-
cy.get('#login-button').click()
686-
687-
cy.get('.error')
688-
.should('contain', 'wrong credentials')
689-
.and('have.css', 'color', 'rgb(255, 0, 0)')
690-
.and('have.css', 'border-style', 'solid')
691-
692-
cy.get('html').should('not.contain', 'Matti Luukkainen logged in') // highlight-line
664+
test('login fails with wrong password', async ({ page }) =>{
665+
await page.getByRole('button', { name: 'log in' }).click()
666+
await page.getByTestId('username').fill('mluukkai')
667+
await page.getByTestId('password').fill('wrong')
668+
await page.getByRole('button', { name: 'login' }).click()
669+
670+
const errorDiv = await page.locator('.error')
671+
await expect(errorDiv).toContainText('wrong credentials')
672+
await expect(errorDiv).toHaveCSS('border-style', 'solid')
673+
await expect(errorDiv).toHaveCSS('color', 'rgb(255, 0, 0)')
674+
675+
await expect(await page.getByText('Matti Luukkainen logged in')).not.toBeVisible() // highlight-line
693676
})
694677
```
695678
696-
Komentoa <i>should</i> käytetään useimmiten ketjutettuna komennon <i>get</i> (tai muun vastaavan ketjutettavissa olevan komennon) perään. Testissä käytetty <i>cy.get('html')</i> tarkoittaa käytännössä koko sovelluksen näkyvillä olevaa sisältöä.
697-
698-
Saman asian olisi myös voinut tarkastaa ketjuttamalla komennon <i>contains</i> perään komento <i>should</i> hieman toisenlaisella parametrilla:
699-
700-
```js
701-
cy.contains('Matti Luukkainen logged in').should('not.exist')
702-
```
703-
704679
### Operaatioiden tekeminen käyttöliittymän "ohi"
705680
706681
Sovelluksemme testit näyttävät tällä hetkellä seuraavalta:
707682
708683
```js
709-
describe('Note app', function() {
710-
it('user can login', function() {
711-
cy.contains('log in').click()
712-
cy.get('#username').type('mluukkai')
713-
cy.get('#password').type('salainen')
714-
cy.get('#login-button').click()
684+
const { test, describe, expect, beforeEach } = require('@playwright/test')
715685

716-
cy.contains('Matti Luukkainen logged in')
686+
describe('Note app', () => {
687+
// ...
688+
689+
test('user can log in', async ({ page }) => {
690+
await page.getByRole('button', { name: 'log in' }).click()
691+
await page.getByTestId('username').fill('mluukkai')
692+
await page.getByTestId('password').fill('salainen')
693+
await page.getByRole('button', { name: 'login' }).click()
694+
await expect(await page.getByText('Matti Luukkainen logged in')).toBeVisible()
717695
})
718696

719-
it('login fails with wrong password', function() {
697+
test('login fails with wrong password', async ({ page }) =>{
720698
// ...
721699
})
722700

723-
describe('when logged in', function() {
724-
beforeEach(function() {
725-
cy.contains('log in').click()
726-
cy.get('#username').type('mluukkai')
727-
cy.get('#password').type('salainen')
728-
cy.get('#login-button').click()
701+
describe('when logged in', () => {
702+
beforeEach(async ({ page, request }) => {
703+
await page.getByRole('button', { name: 'log in' }).click()
704+
await page.getByTestId('username').fill('mluukkai')
705+
await page.getByTestId('password').fill('salainen')
706+
await page.getByRole('button', { name: 'login' }).click()
729707
})
730708

731-
it('a new note can be created', function() {
732-
// ...
709+
test('a new note can be created', async ({ page }) => {
710+
// ...
733711
})
734-
735-
})
712+
713+
// ...
714+
})
736715
})
716+
737717
```
738718
739719
Ensin siis testataan kirjautumistoimintoa. Tämän jälkeen omassa describe-lohkossa on joukko testejä, jotka olettavat että käyttäjä on kirjaantuneena, kirjaantuminen hoidetaan alustuksen tekevän <i>beforeEach</i>-lohkon sisällä.
740720
741721
Kuten aiemmin jo todettiin, jokainen testi suoritetaan alkutilasta, eli vaikka testi on koodissa alempana, se ei aloita samasta tilasta mihin ylempänä koodissa olevat testit ovat jääneet!
742722
743-
Cypressin dokumentaatio neuvoo meitä seuraavasti: [Fully test the login flow – but only once](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Logging-in). Eli sen sijaan että tekisimme <i>beforeEach</i>-lohkossa kirjaantumisen lomaketta käyttäen, suosittelee Cypress että kirjaantuminen tehdään [UI:n ohi](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Bypassing-your-UI), tekemällä suoraan backendiin kirjaantumista vastaava HTTP-operaatio. Syynä tälle on se, että suoraan backendiin tehtynä kirjautuminen on huomattavasti nopeampi kuin lomakkeen täyttämällä.
723+
Vanha E2E-testaajan viisausu kuuluu _Fully test the login flow – but only once_. Eli sen sijaan että tekisimme <i>beforeEach</i>-lohkossa kirjaantumisen lomaketta käyttäen, on parempi idea että kirjaantuminen tehdään UI:n ohi, tekemällä suoraan backendiin kirjaantumista vastaava HTTP-operaatio. Syynä tälle on se, että suoraan backendiin tehtynä kirjautuminen on huomattavasti nopeampi kuin lomakkeen täyttämällä.
744724
745-
Tilanteemme on hieman monimutkaisempi kuin Cypressin dokumentaation esimerkissä, sillä kirjautumisen yhteydessä sovelluksemme tallettaa kirjautuneen käyttäjän tiedot localStorageen. Sekin toki onnistuu. Koodi on seuraavassa
725+
Muutetaan _beforeEach_-lohkossa tapahtuvaa kirjautumista seuraavasti:
746726
747727
```js
748-
describe('when logged in', function() {
749-
beforeEach(function() {
750-
// highlight-start
751-
cy.request('POST', 'http://localhost:3001/api/login', {
752-
username: 'mluukkai', password: 'salainen'
753-
}).then(response => {
754-
localStorage.setItem('loggedNoteappUser', JSON.stringify(response.body))
755-
cy.visit('http://localhost:5173')
728+
describe('Note app', () => {
729+
// ...
730+
731+
describe('when logged in', () => {
732+
beforeEach(async ({ page, request }) => {
733+
// highlight-start
734+
const response = await request.post('http://localhost:3001/api/login', {
735+
data: {
736+
name: 'Matti Luukkainen',
737+
username: 'mluukkai',
738+
password: 'salainen'
739+
}
740+
})
741+
742+
await page.evaluate((body) => {
743+
localStorage.setItem('loggedNoteappUser', JSON.stringify(body));
744+
}, await response.json())
745+
page.reload()
746+
// highlight-end
747+
})
748+
749+
it('a new note can be created', function() {
750+
// ...
756751
})
757-
// highlight-end
758-
})
759752

760-
it('a new note can be created', function() {
761753
// ...
762754
})
763755

764756
// ...
765757
})
766758
```
767759
768-
Komennon [cy.request](https://docs.cypress.io/api/commands/request.html) tulokseen päästään käsiksi _then_-metodin avulla sillä sisäiseltä toteutukseltaan <i>cy.request</i> kuten muutkin Cypressin komennot ovat [eräänlaisia promiseja](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Promises). Käsittelijäfunktio tallettaa kirjautuneen käyttäjän tiedot localStorageen ja lataa sivun uudelleen. Tämän jälkeen käyttäjä on kirjautuneena sovellukseen samalla tavalla kuin jos kirjautuminen olisi tapahtunut kirjautumislomakkeen täyttämällä.
760+
API:n läpi tapahtuva kirjautuminen tehdään tuttuun tapaan parametrina saatavan olion _request_ metodilla [post](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-post). Vastauksena saatu token talletetaan localStorageen testattavaa sovellusta vastaavan olion _page_ metodilla [evaluate](https://playwright.dev/docs/api/class-page#page-evaluate). Sivu uudelleenladataan metodilla [reload](https://playwright.dev/docs/api/class-page#page-reload) tokenin tallennuksen jäleen.
769761
770-
Jos ja kun sovellukselle kirjoitetaan lisää testejä, joudutaan kirjautumisen hoitavaa koodia soveltamaan useassa paikassa. Koodi kannattaakin eristää itse määritellyksi [komennoksi](https://docs.cypress.io/api/cypress-api/custom-commands.html).
762+
Jos ja kun sovellukselle kirjoitetaan lisää testejä, joudutaan kirjautumisen hoitavaa koodia soveltamaan useassa paikassa. Koodi kannattaakin eristää apufunktioksi, joka sijoitetaan esim. tiedostoon _tests/helper.js_:
771763
772764
Komennot määritellään tiedostoon <i>cypress/support/commands.js</i>. Kirjautumisen tekevä komento näyttää seuraavalta:
773765
774766
```js
775-
Cypress.Commands.add('login', ({ username, password }) => {
776-
cy.request('POST', 'http://localhost:3001/api/login', {
777-
username, password
778-
}).then(({ body }) => {
779-
localStorage.setItem('loggedNoteappUser', JSON.stringify(body))
780-
cy.visit('http://localhost:5173')
767+
const loginWith = async (page, request, username, password) => {
768+
const response = await request.post('http://localhost:3001/api/login', {
769+
data: { username, password, }
781770
})
782-
})
771+
772+
await page.evaluate((body) => {
773+
localStorage.setItem('loggedNoteappUser', JSON.stringify(body));
774+
}, await response.json())
775+
776+
page.reload()
777+
}
778+
779+
export { loginWith }
783780
```
784781
785-
Komennon käyttö on helppoa, testi yksinkertaistuu ja selkeytyy:
782+
Testi yksinkertaistuu ja selkeytyy:
786783
787-
```js
788-
describe('when logged in', function() {
789-
beforeEach(function() {
790-
// highlight-start
791-
cy.login({ username: 'mluukkai', password: 'salainen' })
792-
// highlight-end
793-
})
784+
```js
785+
describe('Note app', () => {
786+
// ...
787+
788+
describe('when logged in', () => {
789+
beforeEach(async ({ page, request }) => {
790+
await loginWith(page, request, 'mluukkai', 'salainen')
791+
})
794792

795-
it('a new note can be created', function() {
793+
test('a new note can be created', () => {
796794
// ...
797795
})
798796

0 commit comments

Comments
 (0)