- adding new commands to
cy
- supporting retry-ability
- TypeScript definition for new command
- useful 3rd party commands
+++
- keep
todomvc
app running - open
cypress/integration/09-custom-commands/spec.js
beforeEach(function resetData () {
cy.request('POST', '/reset', {
todos: []
})
})
beforeEach(function visitSite () {
cy.visit('/')
})
Note:
Before each test we need to reset the server data and visit the page. The data clean up and opening the site could be a lot more complex that our simple example. We probably want to factor out resetData
and visitSite
into reusable functions every spec and test can use.
Now these beforeEach
hooks will be loaded before every test in every spec. The test runner loads the spec files like this:
<script src="cypress/support/index.js"></script>
<script src="cypress/integration/09-custom-commands/spec.js"></script>
Note: Is this a good solution?
+++
And load from the spec file:
// automatically runs "beforeEach" hooks
import '../../support/hooks'
it('enters 10 todos', function () {
...
})
Note: A better solution, because only the spec file that needs these hooks can load them.
+++
// cypress/support/hooks.js
export function resetData () { ... }
export function visitSite () { ... }
⌨️ and update spec.js
Little reusable functions are the best
import {
enterTodo, getTodoApp, getTodoItems, resetDatabase, visit
} from '../../support/utils'
it('loads the app', () => {
resetDatabase()
visit()
getTodoApp().should('be.visible')
enterTodo('first item')
enterTodo('second item')
getTodoItems().should('have.length', 2)
})
Note:
Some functions can return cy
instance, some don't, whatever is convenient. I also find small functions that return complex selectors very useful to keep selectors from duplication.
+++
Pro: functions are easy to document with JSDoc
+++
And then IntelliSense works immediately
+++
And MS IntelliSense can understand types from JSDoc and check those!
https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript
More details in: https://slides.com/bahmutov/ts-without-ts
- share code in entire project without individual imports
- complex logic with custom logging into Command Log
- login sequence
- many application actions
📝 on.cypress.io/custom-commands and Read https://glebbahmutov.com/blog/writing-custom-cypress-command/
+++
Let's write a custom command to create a todo
// instead of this
cy.get('.new-todo')
.type('todo 0{enter}')
// use a custom command "createTodo"
cy.createTodo('todo 0')
+++
Cypress.Commands.add('createTodo', todo => {
cy.get('.new-todo').type(`${todo}{enter}`)
})
it('creates a todo', () => {
cy.createTodo('my first todo')
})
+++
- have IntelliSense working for
createTodo
- have nicer Command Log
+++
How: https://github.com/cypress-io/cypress-example-todomvc#cypress-intellisense
+++
⌨️ in file cypress/integration/09-custom-commands/custom-commands.d.ts
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject> {
/**
* Creates one Todo using UI
* @example
* cy.createTodo('new item')
*/
createTodo(todo: string): Chainable<any>
}
}
+++
Load the new definition file in cypress/integration/09-custom-commands/spec.js
/// <reference path="./custom-commands.d.ts" />
+++
More JSDoc examples: https://slides.com/bahmutov/ts-without-ts
Note: Editors other than VSCode might require work.
+++
ignoreTestFiles
in cypress.json or save ".d.ts" files outside the integration folder.
Note: Otherwise Cypress will try load ".d.ts" file as spec and without TypeScript loader will fail.
+++
Cypress.Commands.add('createTodo', todo => {
cy.get('.new-todo', { log: false })
.type(`${todo}{enter}`, { log: false })
cy.log('createTodo', todo)
})
+++
Cypress.Commands.add('createTodo', todo => {
const cmd = Cypress.log({
name: 'create todo',
message: todo,
consoleProps () {
return {
'Create Todo': todo
}
}
})
cy.get('.new-todo', { log: false })
.type(`${todo}{enter}`, { log: false })
})
+++
cy.get('.new-todo', { log: false })
.type(`${todo}{enter}`, { log: false })
.then($el => {
cmd
.set({ $el })
.snapshot()
.end()
})
Pro-tip: you can have multiple command snapshots.
// result will get value when command ends
let result
const cmd = Cypress.log({
consoleProps () {
return { result }
}
})
// custom logic then:
.then(value => {
result = value
cmd.end()
})
+++
- cypress-real-events
- cypress-grep
- cypress-recurse
- cypress-xpath
- cypress-plugin-snapshots
- cypress-pipe
on.cypress.io/plugins#custom-commands
# already done in this repo
npm install -D cypress-xpath
in cypress/support/index.js
require('cypress-xpath')
+++
With cypress-xpath
it('finds list items', () => {
cy.xpath('//ul[@class="todo-list"]//li')
.should('have.length', 3)
})
How does xpath
command retry the assertions that follow it?
cy.xpath('...') // command
.should('have.length', 3) // assertions
+++
// use cy.verifyUpcomingAssertions
const resolveValue = () => {
return Cypress.Promise.try(getValue).then(value => {
return cy.verifyUpcomingAssertions(value, options, {
onRetry: resolveValue,
})
})
}
Easily retry your own functions
npm home cypress-pipe
Advanced example: https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/
+++
const o = {}
setTimeout(() => {
o.foo = 'bar'
}, 1000)
- until it becomes defined
- and is equal to
⌨️ test "passes when object gets new property"
it('creates todos', () => {
// add a few todos
cy.window()
.its('app.todos')
.toMatchSnapshot()
})
+++
+++
- ignore "id" field, because it is dynamic
- update snapshot if you add todo
- parent vs child command
- overwriting
cy
command
on.cypress.io/custom-commands, https://www.cypress.io/blog/2018/12/20/element-coverage/
+++
Cypress.Commands.overwrite('type',
(type, $el, text, options) => {
// just adds element selector to the
// list of seen elements
rememberSelector($el)
return type($el, text, options)
})
https://www.cypress.io/blog/2018/12/20/element-coverage/
- Making reusable function is often faster than writing a custom command
- Know Cypress API to avoid writing what's already available
Read https://glebbahmutov.com/blog/writing-custom-cypress-command/ and https://glebbahmutov.com/blog/publishing-cypress-command/
➡️ Pick the next section