An intermediate course of test automation with Cypress from the Talking About Testing school.
- Class 1 - Local environment setup with Docker
- Class 2 - Test's project setup with Cypress
- Class 3 - Simple GUI tests
- Class 4 - Intermediate GUI tests
- Class 5 - API testing
- Class 6 - Optimizing GUI tests
- Class 7 - Tests with many pre-conditions
- Class 8 - Executing commands at the system level
- Class 9 - Running all tests
- Turning off the container
Run the command docker run --publish 80:80 --publish 22:22 --hostname localhost wlsf82/gitlab-ce
and wait for the environment to initialize (this can take a few minutes), then access the following URL http://localhost/ to define the root
user password.
- Log in with the
root
user using the password defined in the previous section - Click on the user avatar in the upright corner of the screen, click the Settings link, and then, in the left side menu, click the Access Token option
- On the name field, type the value
cypress-intermediate-course
, on the Scopes section check the 'api' option, and then click the 'Create personal access token' button
A message that the token was successfully created should be shown, and the token itself. Copy the token by clicking the button in the right of the field and store it to use in class 2.
- In the terminal, type the following command and press RETURN
ssh-keygen -t ed25519 -C "[email protected]"
- You will be prompted for the path to save the key. Press RETURN to accept the default path
- You will be prompted for a password. Press RETURN so that the password will not be needed
- You will be prompted to repeat the password. Press RETURN again so that the password will not be needed
- In the terminal, type the following command and press RETURN to copy the just created public key to the clipboard
pbcopy < ~/.ssh/id_ed25519.pub
- Logged into the application with the
root
user, click on the user avatar in the upright corner of the screen, click the Settings link, and then, in the left side menu, click the SSH Keys option - Paste the public SSH key into the key field. The Title field should be automatically filled
- Finally, click the 'Add key' button
You will also find the instructions about how to set up the SSH key on a Windows operating system in the application under test itself by following this URL http://localhost/help/ssh/README#generating-a-new-ssh-key-pair.
- Access the following URL https://gitlab.com/wlsf82/cypress-intermediate-course
- Click the 'Clone' button
- Choose one of the options (Clone with SSH or Clone with HTTPS), and then click the 'Copy URL' button beside the field of the chosen option
- In the terminal, in the directory where you have your software projects, type
git clone [URL copied in the previous step]
and press RETURN - Finally, access the directory of the just cloned project (
cd cypress-intermediate-course
)
In the terminal, inside the directory cypress-intermediate-course
, run the following command npm init -y
(this command will create the package.json
file in the project root path.)
On the same directory, create a file called .gitignore
with the following content:
.DS_Store
cypress.env.json
cypress/screenshots/
cypress/videos/
node_modules/
temp/
On the project root directory, create a directory called temp/
. This directory will be used later for the git clone test.
In the terminal, on the project root path, run the following command npm i cypress -D
(this command will install Cypress as a dev dependency, and it will create the package-lock.json
file, and the node_modules/
directory)
In the terminal, on the project root path, run the following command npx cypress open
(this command will open Cypress in interactive mode, and it will create the initial structure for the automated tests.)
- Close the Cypress Electron application
- Open the
cypress.json
file that is located in the project root path and change its content by the following:
{
"baseUrl": "http://localhost/"
}
- Still on the project root path, create a file called
cypress.env.json
with the following content:
{
"user_name": "root",
"user_password": "password-from-the-root-user-defined-previously",
"gitlab_access_token": "access-token-create-previously"
}
- Finally, inside the directory
cypress/integration/
, delete theexamples/
directory
- Inside the
cypress/integration/
directory, create a new directory calledgui/
(graphical user interface) - Inside the
cypress/integration/gui/
directory, create a file calledlogin.spec.js
with the following content:
describe('Login', () => {
it('successfully', () => {
cy.login()
cy.get('.qa-user-avatar').should('exist')
})
})
- Inside the
cypress/support/
directory, rename thecommands.js
file asgui_commands.js
and change its content by the following:
Cypress.Commands.add('login', () => {
cy.visit('users/sign_in')
cy.get("[data-qa-selector='login_field']").type(Cypress.env('user_name'))
cy.get("[data-qa-selector='password_field']").type(Cypress.env('user_password'))
cy.get("[data-qa-selector='sign_in_button']").click()
})
- Inside the
cypress/support/
directory, change the content of theindex.js
file by the following:
import './gui_commands'
- Finally, in the terminal, on the project root path, run the following command
npx cypress run
to run the new test in headless mode
- Inside the
cypress/integration/gui/
directory, create a file calledlogout.spec.js
with the following content:
describe('Logout', () => {
beforeEach(() => cy.login())
it('successfully', () => {
cy.logout()
cy.url().should('be.equal', `${Cypress.config('baseUrl')}users/sign_in`)
})
})
- Inside the
cypress/support/
directory, update thegui_commands.js
file with thelogout
command, as shown below:
Cypress.Commands.add('login', () => {
...
})
Cypress.Commands.add('logout', () => {
cy.get('.qa-user-avatar').click()
cy.contains('Sign out').click()
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/logout.spec.js
to run the new test in headless mode
- For the project creation test, we will use the
faker
library, which will help us with the creation of random data. In the terminal, on the project root path, run the following commandnpm i [email protected] -D
(this command will install thefaker
library as a dev dependency) - Inside the
cypress/integration/gui/
directory, create a file calledcreateProject.spec.js
with the following content:
const faker = require('faker')
describe('Create Project', () => {
beforeEach(() => cy.login())
it('successfully', () => {
const project = {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5)
}
cy.gui_createProject(project)
cy.url().should('be.equal', `${Cypress.config('baseUrl')}${Cypress.env('user_name')}/${project.name}`)
cy.contains(project.name).should('be.visible')
cy.contains(project.description).should('be.visible')
})
})
- Inside the
cypress/support/
directory, update thegui_commands.js
file with thegui_createProject
command, as shown below:
Cypress.Commands.add('login', () => {
...
})
Cypress.Commands.add('logout', () => {
...
})
Cypress.Commands.add('gui_createProject', project => {
cy.visit('projects/new')
cy.get('#project_name').type(project.name)
cy.get('#project_description').type(project.description)
cy.get('.qa-initialize-with-readme-checkbox').check()
cy.contains('Create project').click()
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/createProject.spec.js
to run the new test in headless mode
- Inside the
cypress/integration/gui/
directory, create a file calledcreateIssue.spec.js
with the following content:
const faker = require('faker')
describe('Create Issue', () => {
const issue = {
title: `issue-${faker.random.uuid()}`,
description: faker.random.words(3),
project: {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5)
}
}
beforeEach(() => {
cy.login()
cy.gui_createProject(issue.project)
})
it('successfully', () => {
cy.gui_createIssue(issue)
cy.get('.issue-details')
.should('contain', issue.title)
.and('contain', issue.description)
})
})
- Inside the
cypress/support/
directory, update thegui_commands.js
file with thegui_createIssue
command, as shown below:
Cypress.Commands.add('login', () => {
...
})
Cypress.Commands.add('logout', () => {
...
})
Cypress.Commands.add('gui_createProject', project => {
...
})
Cypress.Commands.add('gui_createIssue', issue => {
cy.visit(`${Cypress.env('user_name')}/${issue.project.name}/issues/new`)
cy.get('.qa-issuable-form-title').type(issue.title)
cy.get('.qa-issuable-form-description').type(issue.description)
cy.contains('Submit issue').click()
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/createIssue.spec.js
to run the new test in headless mode
- Inside the
cypress/integration/
directory, create a new directory calledapi/
(application programming interface) - Inside the
cypress/integration/api/
directory, create a file calledcreateProject.spec.js
with the following content:
const faker = require('faker')
describe('Create Project', () => {
it('successfully', () => {
const project = {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5)
}
cy.api_createProject(project)
.then(response => {
expect(response.status).to.equal(201)
expect(response.body.name).to.equal(project.name)
expect(response.body.description).to.equal(project.description)
})
})
})
- Inside the
cypress/support/
directory, create a file calledapi_commands.js
, with the following content:
const accessToken = Cypress.env('gitlab_access_token')
Cypress.Commands.add('api_createProject', project => {
cy.request({
method: 'POST',
url: `/api/v4/projects/?private_token=${accessToken}`,
body: {
name: project.name,
description: project.description,
initialize_with_readme: true
}
})
})
- Inside the
cypress/support/
directory, add to theindex.js
file the import of theapi_commands.js
file, as shown below:
import './api_commands'
import './gui_commands'
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/api/createProject.spec.js
to run the new test in headless mode.
- Inside the
cypress/integration/api/
directory, create a file calledcreateIssue.spec.js
with the following content:
const faker = require('faker')
describe('Create issue', () => {
it('successfully', () => {
const issue = {
title: `issue-${faker.random.uuid()}`,
description: faker.random.words(3),
project: {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5)
}
}
cy.api_createIssue(issue)
.then(response => {
expect(response.status).to.equal(201)
expect(response.body.title).to.equal(issue.title)
expect(response.body.description).to.equal(issue.description)
})
})
})
- Inside the
cypress/support/
directory, update theapi_commands.js
file with theapi_createIssue
command, as shown below:
const accessToken = Cypress.env('gitlab_access_token')
Cypress.Commands.add('api_createProject', project => {
...
})
Cypress.Commands.add('api_createIssue', issue => {
cy.api_createProject(issue.project)
.then(response => {
cy.request({
method: 'POST',
url: `/api/v4/projects/${response.body.id}/issues?private_token=${accessToken}`,
body: {
title: issue.title,
description: issue.description
}
})
})
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/api/createIssue.spec.js
to run the new test in headless mode
- In the
cypress/integration/gui/createIssue.spec.js
file, replace the commandcy.gui_createProject(issue.project)
bycy.api_createProject(issue.project)
. This way, instead of creating the project via GUI, we will create it via API, which is a faster way of doing that, and which also makes the test more independent. - Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/createIssue.spec.js
to run the refactored test in headless mode.
- In the
cypress/integration/gui/
directory, create a file calledsetLabelOnIssue.spec.js
with the following content:
const faker = require('faker')
describe('Set label on issue', () => {
const issue = {
title: `issue-${faker.random.uuid()}`,
description: faker.random.words(3),
project: {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5),
label: {
name: `label-${faker.random.word()}`,
color: '#ffaabb'
}
}
}
beforeEach(() => {
cy.login()
cy.api_createIssue(issue)
.then(response => {
cy.api_createLabel(response.body.project_id, issue.project.label)
cy.visit(`${Cypress.env('user_name')}/${issue.project.name}/issues/${response.body.iid}`)
})
})
it('successfully', () => {
cy.gui_setLabelOnIssue(issue.project.label)
cy.get('.qa-labels-block').should('contain', issue.project.label.name)
cy.get('.qa-labels-block span')
.should('have.attr', 'style', `background-color: ${issue.project.label.color}; color: #333333;`)
})
})
- In the
cypress/support/
directory, update theapi_commands.js
file with theapi_createLabel
command, as shown below:
const accessToken = Cypress.env('gitlab_access_token')
Cypress.Commands.add('api_createProject', project => {
...
})
Cypress.Commands.add('api_createIssue', issue => {
...
})
Cypress.Commands.add('api_createLabel', (projectId, label) => {
cy.request({
method: 'POST',
url: `/api/v4/projects/${projectId}/labels?private_token=${accessToken}`,
body: {
name: label.name,
color: label.color
}
})
})
- In the
cypress/support/
directory, update thegui_commands.js
file with thegui_setLabelOnIssue
command, as shown below:
Cypress.Commands.add('login', () => {
...
})
Cypress.Commands.add('logout', () => {
...
})
Cypress.Commands.add('gui_createProject', project => {
...
})
Cypress.Commands.add('gui_createIssue', issue => {
...
})
Cypress.Commands.add('gui_setLabelOnIssue', label => {
cy.get('.qa-edit-link-labels').click()
cy.contains(label.name).click()
cy.get('body').click()
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/setLabelOnIssue.spec.js
to run the new test in headless mode
- In the
cypress/integration/gui/
directory, create a file calledsetMilestoneOnIssue.spec.js
with the following content:
const faker = require('faker')
describe('Set milestone on issue', () => {
const issue = {
title: `issue-${faker.random.uuid()}`,
description: faker.random.words(3),
project: {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5),
milestone: {
title: `milestone-${faker.random.word()}`
}
}
}
beforeEach(() => {
cy.login()
cy.api_createIssue(issue)
.then(response => {
cy.api_createMilestone(response.body.project_id, issue.project.milestone)
cy.visit(`${Cypress.env('user_name')}/${issue.project.name}/issues/${response.body.iid}`)
})
})
it('successfully', () => {
cy.gui_setMilestoneOnIssue(issue.project.milestone)
cy.get('.block.milestone').should('contain', issue.project.milestone.title)
})
})
- Inside the
cypress/support/
directory, update theapi_commands.js
file with theapi_createMilestone
command, as shown below:
const accessToken = Cypress.env('gitlab_access_token')
Cypress.Commands.add('api_createProject', project => {
...
})
Cypress.Commands.add('api_createIssue', issue => {
...
})
Cypress.Commands.add('api_createLabel', (projectId, label) => {
...
})
Cypress.Commands.add('api_createMilestone', (projectId, milestone) => {
cy.request({
method: 'POST',
url: `/api/v4/projects/${projectId}/milestones?private_token=${accessToken}`,
body: { title: milestone.title }
})
})
- Inside the
cypress/support/
directory, update thegui_commands.js
file with thegui_setMilestoneOnIssue
command, as shown below:
Cypress.Commands.add('login', () => {
...
})
Cypress.Commands.add('logout', () => {
...
})
Cypress.Commands.add('gui_createProject', project => {
...
})
Cypress.Commands.add('gui_createIssue', issue => {
...
})
Cypress.Commands.add('gui_setLabelOnIssue', label => {
...
})
Cypress.Commands.add('gui_setMilestoneOnIssue', milestone => {
cy.get('.block.milestone .edit-link').click()
cy.contains(milestone.title).click()
})
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/gui/setMilestoneOnIssue.spec.js
to run the new test in headless mode
- Inside the
cypress/integration/
directory, create a new directory calledcli/
(command line interface) - Inside the
cypress/integration/cli/
directory, create a file calledgitClone.spec.js
with the following content:
const faker = require('faker')
describe('git clone', () => {
const project = {
name: `project-${faker.random.uuid()}`,
description: faker.random.words(5)
}
beforeEach(() => cy.api_createProject(project))
it('successfully', () => {
cy.cloneViaSSH(project)
cy.readFile(`temp/${project.name}/README.md`)
.should('contain', `# ${project.name}`)
.and('contain', project.description)
})
})
- Inside the
cypress/support/
directory, create a file calledcli_commands.js
with the following content:
Cypress.Commands.add('cloneViaSSH', project => {
const domain = Cypress.config('baseUrl').replace('http://', '').replace('/', '')
cy.exec(`cd temp/ && git clone git@${domain}:${Cypress.env('user_name')}/${project.name}.git`)
})
- Inside the
cypress/support/
directory, add to theindex.js
file the import of thecli_commands.js
file, as shown below:
import './api_commands'
import './cli_commands'
import './gui_commands'
- Finally, in the terminal, on the project root path, run the following command
npx cypress run --spec cypress/integration/cli/gitClone.spec.js
to run the new test in headless mode
Note: In the first time you run the test you will be prompted the following:
Are you sure you want to continue connecting (yes/no)?
Answeryes
and press RETURN.
Note 2: In case the test fails with the below error, run the following command
ssh-keygen -R localhost
, press RETURN, and then re-run the test (npx cypress run --spec cypress/integration/cli/gitClone.spec.js
).
CypressError: cy.exec('cd temp/ && git clone git@localhost:root/project-8074da23-f979-4555-84e8-7a63fb69a326.git') failed because the command exited with a non-zero code.
Pass {failOnNonZeroExit: false} to ignore exit code failures.
Information about the failure:
Code: 128
Stderr:
Cloning into 'project-8074da23-f979-4555-84e8-7a63fb69a326'...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@...
at Object.cypressErr (http://localhost/__cypress/runner/cypress_runner.js:106136:11)
at Object.throwErr (http://localhost/__cypress/runner/cypress_runner.js:106091:18)
at Object.throwErrByPath (http://localhost/__cypress/runner/cypress_runner.js:106123:17)
at http://localhost/__cypress/runner/cypress_runner.js:90175:23
at tryCatcher (http://localhost/__cypress/runner/cypress_runner.js:140400:23)
at Promise._settlePromiseFromHandler (http://localhost/__cypress/runner/cypress_runner.js:138336:31)
at Promise._settlePromise (http://localhost/__cypress/runner/cypress_runner.js:138393:18)
at Promise._settlePromise0 (http://localhost/__cypress/runner/cypress_runner.js:138438:10)
at Promise._settlePromises (http://localhost/__cypress/runner/cypress_runner.js:138517:18)
at Async../node_modules/bluebird/js/release/async.js.Async._drainQueue (http://localhost/__cypress/runner/cypress_runner.js:135125:16)
at Async../node_modules/bluebird/js/release/async.js.Async._drainQueues (http://localhost/__cypress/runner/cypress_runner.js:135135:10)
at Async.drainQueues (http://localhost/__cypress/runner/cypress_runner.js:135009:14)
- Open the
package.json
file located on the project root path - On the
scripts
section, change the value of thetest
script by the followingcypress run
The scripts
section of the package.json
file should look like this:
"scripts": {
"test": "cypress run"
},
- Finally, in the terminal, on the project root path, run the
npm test
command to run all tests in headless mode. You should obtain a result similar to what is shown in the below image.
- In the terminal, on the project root path, run the following command
npx cypress open
(this command will open the Cypress Electron application) - To run all tests in interactive mode, click the 'Run all specs' button. Or, to run a specific test, click in the test you want to run from the tests' list.
- In the terminal, run the following command
docker container ls
, press RETURN, and copy theCONTAINER ID
that refers to thewlsf82/gitlab-ce
image - Finally, execute the command
docker container stop [CONTAINER ID copied in the previous step]
and press RETURN
Note: After turning off the container, in case you want to start the application again, follow the steps described on class 1, and remember to update the values from the
cypress.env.json
file, as described on class 2, step 3
Made with 💚 by Walmyr Filho