I've been playing around with ways of defining reusable helpers (blog post coming) and have been using the Cypress log API and passing { log: false } to allow control of the log output of these helpers/commands. This has been useful, but there are some times that I want to dive into that abstraction (most likely on failure) to see what step of the helper failed.
I think a concept of a group would be useful here. Instead of passing { log: false } to every encapsulated command, having a group would help a lot more
Feature
// This will work on https://github.com/cypress-io/cypress-example-todomvc
export const createTodo = (name) => {
const log = Cypress.log({
name: 'createTodo',
message: name,
consoleProps() {
return {
'Inserted Todo': name,
}
}
})
cy.get('.new-todo', { log: false }).type(`${name}{enter}`, { log: false })
return cy
.get('.todo-list li', { log: false })
.contains('li', name.trim(), { log: false })
.then(($el) => {
log.set({ $el }).snapshot().end()
})
}
// to use this helper:
createTodo('Learn the Cypress Log API')
.then(console.log) // logs the created todo element from the TodoMVC Cypress example
Screenshot of the above:

export const createTodo = (name) => {
const group = Cypress.group({
name: 'createTodo',
message: name,
consoleProps() {
return {
'Inserted Todo': name,
}
}
})
cy.get('.new-todo').type(`${name}{enter}`)
return cy
.get('.todo-list')
.contains('li', name.trim())
.then($el => { group.set({ $el }).snapshot().end() })
Matching the log API seems like a logical choice. In the UI it would show createTodo with the logging the same as the Current behavior example, but would have an arrow that would expand to the grouped commands. This would both simplify creating custom command output (don't have to pass { log: false } to everything) as well as make it easier to understand that's going on under the hood of a custom command. The group would be collapsed by default and expanded if a nested command failed.
This proposal is conceptually similar to the Web Console log's group: https://developer.mozilla.org/en-US/docs/Web/API/Console/group
I'm surprised this didn't get more votes yet.
I'd love to be able to group several commands into one, nicely labelled, group that is collapsed by default, and autoxpands on a failure.
Also note this could be useful when having a larger test (which we are encouraged to do) to group sections of a single test case for better readability.
I was thinking about this more. Basically as test suites get larger, people look to ways to "macro" commands. There are a few ways to do this:
Cypress Custom Commands
// definition
Cypress.Commands.add('fillForm', (first, last) => {
cy.get('#first').type(first)
cy.get('#last').type(last)
}
// usage
cy.fillForm('John', 'Doe')
// log
GET #first
TYPE John
GET #last
TYPE Doe
Regular functions
// definition
function fillForm(first, last) {
cy.get('#first').type(first)
cy.get('#last').type(last)
}
// usage
fillForm('John', 'Doe')
// log
GET #first
TYPE John
GET #last
TYPE Doe
Functional composition (.then)
// definition
const fillForm = (first, last) => ($form) => {
cy.wrap($form).find('#first').type(first)
cy.wrap($form).find('#last').type(last)
}
// usage
cy
.get('#form')
.then(fillForm('John', 'Doe'))
// log
GET #form
THEN
WRAP <form>
FIND #first
TYPE John
WRAP <form>
FIND #first
TYPE Doe
Functional composition (.pipe)
// definition
const fillForm = (first, last) => function fillForm($form) {
cy.wrap($form).find('#first').type(first)
cy.wrap($form).find('#last').type(last)
}
// usage (.pipe)
cy
.get('#form')
.pipe(fillForm('John', 'Doe'))
// log (.pipe)
GET #form
PIPE fillForm
WRAP <form>
FIND #first
TYPE John
WRAP <form>
FIND #first
TYPE Doe
Page Objects
// definitions
class Page {
fillForm(first, last) {
cy.get('#first').type(first)
cy.get('#last').type(last)
}
}
// usage
const page = new Page()
page.fillForm('John', 'Doe')
// log
GET #first
TYPE John
GET #last
TYPE Doe
The function name fillForm isn't shown in the log output of all the examples except cypress-pipe. Even cypress-pipe is confusing, because I only see cy.get('#form').pipe(fillForm('John', 'Doe')) in my test, but a bunch more commands logged in the Command Log. Grouping would make this much easier to understand the relationship between my test code and the log.
The following could be modifications to get grouping to work:
Cypress Custom Commands
// definition
Cypress.Commands.add('fillForm', (first, last) => {
cy.group('fillForm', () => {
cy.get('#first').type(first)
cy.get('#last').type(last)
})
}
// usage
cy.fillForm('John', 'Doe')
// log
GROUP fillForm
- GET #first
- TYPE John
- GET #last
- TYPE Doe
Regular functions
// definition
import { group } from '@cypress/group' // Higher-order function/class decorator
const fillForm = group('fillForm', (first, last) => {
cy.get('#first').type(first)
cy.get('#last').type(last)
})
// usage
fillForm('John', 'Doe')
// log
GROUP fillForm
- GET #first
- TYPE John
- GET #last
- TYPE Doe
Functional composition (.then)
// definition
const fillForm = (first, last) => ($form) => {
cy.group('fillForm', () => {
cy.wrap($form).find('#first').type(first)
cy.wrap($form).find('#last').type(last)
})
}
// usage
cy
.get('#form')
.then(fillForm('John', 'Doe'))
// log
GET #form
THEN
GROUP fillForm
- WRAP <form>
- FIND #first
- TYPE John
- WRAP <form>
- FIND #first
- TYPE Doe
Functional composition (.pipe) (no changes actually needed)
// definition
const fillForm = (first, last) => function fillForm($form) {
cy.wrap($form).find('#first').type(first)
cy.wrap($form).find('#last').type(last)
}
// usage
cy
.get('#form')
.pipe(fillForm('John', 'Doe'))
// log
GET #form
PIPE fillForm
- WRAP <form>
- FIND #first
- TYPE John
- WRAP <form>
- FIND #first
- TYPE Doe
Page Objects
// definitions
import { group } from '@cypress/group' // Higher-order function/class decorator
class Page {
@group('fillForm') // optional if name can't be inferred
fillForm(first, last) {
cy.get('#first').type(first)
cy.get('#last').type(last)
}
}
// usage
const page = new Page()
page.fillForm('John', 'Doe')
// log
GROUP fillForm
- GET #first
- TYPE John
- GET #last
- TYPE Doe
I spent some time looking into this over the weekend. I don't think group can be added without changes to the Cypress runner code. The React UI renders a list, but will need to render a tree. cy.within would be easier to understand as well in the Command Log if it also created a group. Right now you can understand what is inside a cy.within while debugging by clicking on Commands inside a within by the debug output, but not part of the Video or screenshots.
Next step would be to evaluate how much code needs to change to support grouping. cy.within required changes to all commands within querying.coffee to know if querying APIs should target the root element or the withinSubject. Grouping doesn't seem to need that level of interaction with other commands, but does require cooperation with the UI.
Most helpful comment
I was thinking about this more. Basically as test suites get larger, people look to ways to "macro" commands. There are a few ways to do this:
Cypress Custom Commands
Regular functions
Functional composition (.then)
Functional composition (.pipe)
Page Objects
The function name
fillFormisn't shown in the log output of all the examples except cypress-pipe. Evencypress-pipeis confusing, because I only seecy.get('#form').pipe(fillForm('John', 'Doe'))in my test, but a bunch more commands logged in the Command Log. Grouping would make this much easier to understand the relationship between my test code and the log.The following could be modifications to get grouping to work:
Cypress Custom Commands
Regular functions
Functional composition (.then)
Functional composition (.pipe) (no changes actually needed)
Page Objects