- adding new commands to
cy - supporting retry-ability
- TypeScript definition for new command
- useful 3rd party commands
+++
- keep
todomvcapp running - open
cypress/e2e/09-custom-commands/spec.js
// name the beforeEach functions
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/e2e.js"></script>
<script src="cypress/e2e/09-custom-commands/spec.js"></script>Note: Is this a good solution?
Little reusable functions are the best
import {
enterTodo,
getTodoApp,
getTodoItems,
resetDatabase,
visit
} from '../../support/utils'
beforeEach(() => {
resetDatabase()
visit()
})
it('loads the app', () => {
getTodoApp().should('be.visible')
enterTodo('first item')
enterTodo('second item')
getTodoItems().should('have.length', 2)
})Todo: look at the "cypress/support/utils.js"
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/
+++
- add a custom command
- add a custom query
- overwrite a command
- overwrite a query (v12.6.0+)
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/e2e/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/e2e/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.
+++
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-map 👍👍👍
- cypress-real-events 👍👍
- @bahmutov/cy-grep
- cypress-recurse 👍
- cypress-plugin-snapshots
- cypress-xpath 🔻
on.cypress.io/plugins#custom-commands
# already done in this repo
npm install -D cypress-xpathin cypress/support/e2e.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
})
})
}- parent vs child command
- overwriting
cycommand
on.cypress.io/custom-commands, https://www.cypress.io/blog/2018/12/20/element-coverage/
+++
Cypress.Commands.overwrite('type', (type, $el, text, options) => {
console.log($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/
- https://cypresstips.substack.com/p/my-favorite-cypress-plugins
- https://cypresstips.substack.com/p/my-favorite-cypress-plugins-part
➡️ Pick the next section or jump to the 10-component-testing chapter



