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
rootuser 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 "root@example.com" - 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
rootuser, 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.jsonfile 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.jsonwith 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.jswith the following content:
describe('Login', () => {
it('successfully', () => {
cy.login()
cy.get('.qa-user-avatar').should('exist')
})
})- Inside the
cypress/support/directory, rename thecommands.jsfile asgui_commands.jsand 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.jsfile by the following:
import './gui_commands'- Finally, in the terminal, on the project root path, run the following command
npx cypress runto run the new test in headless mode
- Inside the
cypress/integration/gui/directory, create a file calledlogout.spec.jswith 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.jsfile with thelogoutcommand, 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.jsto run the new test in headless mode
- For the project creation test, we will use the
fakerlibrary, which will help us with the creation of random data. In the terminal, on the project root path, run the following commandnpm i faker@5.5.3 -D(this command will install thefakerlibrary as a dev dependency) - Inside the
cypress/integration/gui/directory, create a file calledcreateProject.spec.jswith 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.jsfile with thegui_createProjectcommand, 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.jsto run the new test in headless mode
- Inside the
cypress/integration/gui/directory, create a file calledcreateIssue.spec.jswith 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.jsfile with thegui_createIssuecommand, 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.jsto 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.jswith 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.jsfile the import of theapi_commands.jsfile, 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.jsto run the new test in headless mode.
- Inside the
cypress/integration/api/directory, create a file calledcreateIssue.spec.jswith 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.jsfile with theapi_createIssuecommand, 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.jsto run the new test in headless mode
- In the
cypress/integration/gui/createIssue.spec.jsfile, 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.jsto run the refactored test in headless mode.
- In the
cypress/integration/gui/directory, create a file calledsetLabelOnIssue.spec.jswith 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.jsfile with theapi_createLabelcommand, 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.jsfile with thegui_setLabelOnIssuecommand, 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.jsto run the new test in headless mode
- In the
cypress/integration/gui/directory, create a file calledsetMilestoneOnIssue.spec.jswith 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.jsfile with theapi_createMilestonecommand, 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.jsfile with thegui_setMilestoneOnIssuecommand, 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.jsto 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.jswith 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.jswith 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.jsfile the import of thecli_commands.jsfile, 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.jsto 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)?Answeryesand 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.jsonfile located on the project root path - On the
scriptssection, change the value of thetestscript 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 testcommand 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 IDthat refers to thewlsf82/gitlab-ceimage - 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.jsonfile, as described on class 2, step 3
Made with 💚 by Walmyr Filho
