Log in Using Collected Words

How to collect information from the page and conditionally type it.

Have you seen web sites that show you a list of random words to write down when signing up? When you go to log in, the site shows the same random words with some words missing. You need to enter the missing words exactly as they appeared, and this is how you log in. Here is a Cypress test showing this login in action:

Log in using words application test

Let's see how the Cypress test can "remember" the words and type them at the correct positions. The application has two screens: the first one shows the words to find and save, the second one shows the input elements instead of some words. We need to look up the saved word at that position and enter it.

🎁 You can find the source code for this blog post in the repository bahmutov/cypress-login-words. You can also watch the explanation in the video Log In Using Words.

Login fails

First, let's confirm the application does not let log in with incorrect words. On the second screen we can simply type "word" + index number.

cypress/integration/spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it('does not log in with wrong words', () => {
cy.visit('public/index.html')
// wait 1 second for clarity
.wait(1000)
cy.contains('button', 'I remember', { matchCase: false }).click()
// the section changes
cy.get('section#login').should('be.visible')
cy.get('input[type=text]').each(($input, k) => {
// take the jQuery element $input
// and wrap it using Cypress cy.wrap command
// now we can correctly chain Cypress commands
cy.wrap($input).type('word' + (k + 1))
})
cy.contains('#login-button', 'Log in', { matchCase: false }).click()
cy.contains('#login-button', 'Try again').should('be.visible')
})

The test types random strings and fails to log in

We are using my favorite Cypress commands cy.contains, cy.get, cy.each, and cy.wrap.

📚 You can find lots of Cypress command examples at my site https://glebbahmutov.com/cypress-examples. It has a good search that quickly shows my practical Cypress use examples.

Save the words

In order for the end-to-end test to log in correctly, the test needs to save the shown words. We can store the words in a regular array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it('remembers the words', () => {
cy.visit('public/index.html')
// wait 1 second for clarity
.wait(1000)
// remember the words from the list
const words = []
cy.get('#show-words .words li')
.each(($li) => {
words.push($li.text())
})
.then(() => {
// we can use the data from the page
// in cy.then callback function
cy.log(words.join(', '))
})
})

The test grabs the shown words from the page

We store the extracted words in the local variable words. After the cy.each command finishes, that array is filled with words. We can use the array after cy.each command by using cy.then callback.

🤔 Are you not sure why and how Cypress executes its commands? Read my blog post Visualize Cypress Command Queue.

Type the saved words

Now that we have the words in the correct order, we need to look at the list shown on the second page. Some of the list items are <input> elements and we need to enter the correct string from the array of strings. Here is how we look at each li item, and use cy.each command to check if there is an input element inside. We are using the jQuery .find method to synchronously see if there is <li><input>...</li> situation. If yes, we need to type the word at index k using a Cypress command.

1
2
3
4
5
6
cy.get('#login .words li').each(($li, k) => {
const $input = $li.find('input')
if ($input.length) {
cy.wrap($input).type(words[k])
}
})

Nice!

😳 Wait, isn't this an example of "Conditional Testing", which is an anti-pattern? Yes, we are looking at the page to decide what the test needs to do. Sometimes it is necessary, so I have a few recipes how to handle it.

Here is the full test that saves and types the words, and checks if the page transitions are successful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
it('logs in', () => {
cy.visit('public/index.html')
// wait 1 second for clarity
.wait(1000)
// remember the words from the list
const words = []
cy.get('#show-words .words li')
.each(($li) => {
words.push($li.text())
})
.then(() => {
// we can use the data from the page
// in cy.then callback function
cy.log(words.join(', '))

cy.contains('button', 'I remember', { matchCase: false }).click()
// the section changes
cy.get('section#login').should('be.visible')
cy.get('#login .words li').each(($li, k) => {
const $input = $li.find('input')
if ($input.length) {
cy.wrap($input).type(words[k])
}
})
})

cy.contains('#login-button', 'Log in', { matchCase: false }).click()
cy.contains('#login-button', 'Success').should('be.visible')
})

Beautiful, isn't it.