Skip to content

Commit c19271c

Browse files
committed
Add instructions for finding input fields by label, and unify the login button name with the earlier material
1 parent f332e98 commit c19271c

File tree

2 files changed

+138
-77
lines changed

2 files changed

+138
-77
lines changed

src/content/5/en/part5e.md

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ describe('Note app', function() {
159159

160160
it('login form can be opened', function() {
161161
cy.visit('http://localhost:5173')
162-
cy.contains('login').click()
162+
cy.contains('button', 'login').click()
163163
})
164164
})
165165
```
166166

167-
The test first searches for the login button by its text and clicks the button with the command [cy.click](https://docs.cypress.io/api/commands/click.html#Syntax).
167+
The test first searches for a _button_ element with the desired text and clicks the button with the command [cy.click](https://docs.cypress.io/api/commands/click.html#Syntax).
168168

169169
Both of our tests begin the same way, by opening the page <i><http://localhost:5173></i>, so we should extract the shared code into a <i>beforeEach</i> block run before each test:
170170

@@ -182,7 +182,7 @@ describe('Note app', function() {
182182
})
183183

184184
it('login form can be opened', function() {
185-
cy.contains('login').click()
185+
cy.contains('button', 'login').click()
186186
})
187187
})
188188
```
@@ -195,40 +195,72 @@ We can access the first and the last input field on the page, and write to them
195195

196196
```js
197197
it('user can login', function () {
198-
cy.contains('login').click()
198+
cy.contains('button', 'login').click()
199199
cy.get('input:first').type('mluukkai')
200200
cy.get('input:last').type('salainen')
201201
})
202202
```
203203

204204
The test works. The problem is if we later add more input fields, the test will break because it expects the fields it needs to be the first and the last on the page.
205205

206-
It would be better to give our inputs unique <i>IDs</i> and use those to find them.
207-
We change our login form like so:
206+
Let's take advantage of the existing elements of the login form. The input fields of the login form have been assigned unique <i>labels</i>:
207+
208+
```js
209+
// ...
210+
<form onSubmit={handleSubmit}>
211+
<div>
212+
<label> // highlight-line
213+
username // highlight-line
214+
<input
215+
type="text"
216+
value={username}
217+
onChange={handleUsernameChange}
218+
/>
219+
</label> // highlight-line
220+
</div>
221+
<div>
222+
<label> // highlight-line
223+
password // highlight-line
224+
<input
225+
type="password"
226+
value={password}
227+
onChange={handlePasswordChange}
228+
/>
229+
</label> // highlight-line
230+
</div>
231+
<button type="submit">login</button>
232+
</form>
233+
// ...
234+
```
235+
236+
Input fields can and should be located in tests using <i>labels</i>:
237+
238+
```js
239+
describe('Note app', function () {
240+
// ...
241+
242+
it('user can login', function () {
243+
cy.contains('button', 'login').click()
244+
cy.contains('label', 'username').type('mluukkai') // highlight-line
245+
cy.contains('label', 'password').type('salainen') // highlight-line
246+
})
247+
})
248+
```
249+
250+
When locating elements, it makes sense to aim to utilize the content visible to the user in the interface, as this best simulates how a user would actually find the desired input field while navigating the application.
251+
252+
When the username and password have been entered into the form, the next step is to press the <i>login</i> button. However, this causes a bit of a headache, since there are actually two <i>login</i> buttons on the page. The <i>Togglable</i> component we are using also contains a button with the same name, which is hidden by giving it the visibility attribute style="display: none" when the login form is visible.
253+
254+
To ensure that the test clicks the correct button, we assign a unique <i>id</i> attribute to the login form’s <i>login</i> button:
208255

209256
```js
210257
const LoginForm = ({ ... }) => {
211258
return (
212259
<div>
213260
<h2>Login</h2>
214261
<form onSubmit={handleSubmit}>
215-
<div>
216-
username
217-
<input
218-
id='username' // highlight-line
219-
value={username}
220-
onChange={handleUsernameChange}
221-
/>
222-
</div>
223-
<div>
224-
password
225-
<input
226-
id='password' // highlight-line
227-
type="password"
228-
value={password}
229-
onChange={handlePasswordChange}
230-
/>
231-
</div>
262+
//
263+
232264
<button id="login-button" type="submit"> // highlight-line
233265
login
234266
</button>
@@ -238,18 +270,16 @@ const LoginForm = ({ ... }) => {
238270
}
239271
```
240272

241-
We also added an ID to our submit button so we can access it in our tests.
242-
243273
The test becomes:
244274

245275
```js
246276
describe('Note app', function() {
247277
// ..
248-
it('user can login', function() {
249-
cy.contains('login').click()
250-
cy.get('#username').type('mluukkai') // highlight-line
251-
cy.get('#password').type('salainen') // highlight-line
252-
cy.get('#login-button').click() // highlight-line
278+
it('user can login', function () {
279+
cy.contains('button', 'login').click()
280+
cy.contains('label', 'username').type('mluukkai')
281+
cy.contains('label', 'password').type('salainen')
282+
cy.get('#login-button').click() // highlight-line
253283

254284
cy.contains('Matti Luukkainen logged in') // highlight-line
255285
})
@@ -258,7 +288,7 @@ describe('Note app', function() {
258288

259289
The last row ensures that the login was successful.
260290

261-
Note that the CSS's [ID selector](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors) is #, so if we want to search for an element with the ID <i>username</i> the CSS selector is <i>#username</i>.
291+
Note that the CSS's [ID selector](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors) is #, so if we want to search for an element with the ID <i>login-button</i> the CSS selector is <i>#login-button</i>.
262292

263293
Please note that passing the test at this stage requires that there is a user in the test database of the backend test environment, whose username is <i>mluukkai</i> and the password is <i>salainen</i>. Create a user if needed!
264294

@@ -272,7 +302,7 @@ describe('Note app', function() {
272302
// highlight-start
273303
describe('when logged in', function() {
274304
beforeEach(function() {
275-
cy.contains('login').click()
305+
cy.contains('button', 'login').click()
276306
cy.get('input:first').type('mluukkai')
277307
cy.get('input:last').type('salainen')
278308
cy.get('#login-button').click()
@@ -314,7 +344,7 @@ describe('Note app', function() {
314344
// ...
315345

316346
it('user can login', function() {
317-
cy.contains('login').click()
347+
cy.contains('button', 'login').click()
318348
cy.get('#username').type('mluukkai')
319349
cy.get('#password').type('salainen')
320350
cy.get('#login-button').click()
@@ -324,7 +354,7 @@ describe('Note app', function() {
324354

325355
describe('when logged in', function() {
326356
beforeEach(function() {
327-
cy.contains('login').click()
357+
cy.contains('button', 'login').click()
328358
cy.get('input:first').type('mluukkai')
329359
cy.get('input:last').type('salainen')
330360
cy.get('#login-button').click()
@@ -503,7 +533,7 @@ describe('Note app', function() {
503533
// ...
504534

505535
it.only('login fails with wrong password', function() {
506-
cy.contains('login').click()
536+
cy.contains('button', 'login').click()
507537
cy.get('#username').type('mluukkai')
508538
cy.get('#password').type('wrong')
509539
cy.get('#login-button').click()
@@ -591,7 +621,7 @@ Let's finish the test so that it also checks that the application does not rende
591621
592622
```js
593623
it('login fails with wrong password', function() {
594-
cy.contains('login').click()
624+
cy.contains('button', 'login').click()
595625
cy.get('#username').type('mluukkai')
596626
cy.get('#password').type('wrong')
597627
cy.get('#login-button').click()
@@ -628,7 +658,7 @@ Currently, we have the following tests:
628658
```js
629659
describe('Note app', function() {
630660
it('user can login', function() {
631-
cy.contains('login').click()
661+
cy.contains('button', 'login').click()
632662
cy.get('#username').type('mluukkai')
633663
cy.get('#password').type('salainen')
634664
cy.get('#login-button').click()
@@ -642,7 +672,7 @@ describe('Note app', function() {
642672

643673
describe('when logged in', function() {
644674
beforeEach(function() {
645-
cy.contains('login').click()
675+
cy.contains('button', 'login').click()
646676
cy.get('input:first').type('mluukkai')
647677
cy.get('input:last').type('salainen')
648678
cy.get('#login-button').click()
@@ -980,7 +1010,7 @@ Finally, some notes on how Cypress works and debugging your tests.
9801010
Because of the form of the Cypress tests, it gives the impression that they are normal JavaScript code, and we could for example try this:
9811011
9821012
```js
983-
const button = cy.contains('login')
1013+
const button = cy.contains('button', 'login')
9841014
button.click()
9851015
debugger
9861016
cy.contains('logout').click()

0 commit comments

Comments
 (0)