- adding new commands to
cy
- supporting retry-ability
- TypeScript definition for new command
- useful 3rd party commands
+++
- keep
todomvc
app running - open
cypress/integration/12-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.
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
+++
Let's write a custom command to create a todo
// instead of this
cy.get('.new-todo')
.type('todo 0{enter}')
// use this
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/12-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/12-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()
})
+++
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/
+++
Video of element coverage, https://slides.com/bahmutov/test-coverage-update
+++
@ul
- Making reusable function is often faster than writing a custom command
- Know Cypress API to avoid writing what's already available @ulend