@braze/cypress-utils
v4.4.0
Published
Utilities for cypress testing at Braze
Keywords
Readme
cypress-utils
Utilities for cypress testing at Braze
This package provides a composable interface for re-using cypress selectors. Example declaration of selectors and their possible usage in a test can be found in the examples/ directory
Context definitions are an object containing one of 3 kinds of selectors:
- a function that returns a cypress selector
- a function that accepts arguments and returns a cypress selectors
- a selector with child selectors
All selectors can also define context actions to abstract common actions
Function selector
const context = makeContext("context type", {
el: () => cy.get(".element");
});
/** use in the test as */
context.el.click();
context.el.should("be.visible");
// and so on - you can interact with context.el as if you were interacting
// with cy.get(".element")Function selector that accepts arguments
const context = makeContext("context type", {
row: {
self: (num: number) => cy.get(`li:nth-child(${num})`);
needsParams: true,
children: {}
}
});
/** use in the test as */
context.row(3).click();
context.row(5).should("be.visible");
// and so on - you can interact with context.row(n) as if you were interacting
// with cy.get("li:nth-child(n)")
// any type or number of arguments is acceptable
const context = makeContext("another example", {
badge: {
self: (label: string, num: number) => cy.get(`div:contains("${label}") li:nth-child(${num})`)
needsParams: true,
children: {}
});
/** use in the test as */
context.badge("failed", 4).should("be.visible");Selector with child selectors
// you can declare children inline
const context = makeContext("context type", {
table: {
self: () => cy.get("table");
children: {
header: {
self: () => cy.get("thead"),
row: {
self: (num: number) => cy.get(`tr:nth-child(${num})`),
needsParams: true,
children: {
th: {
self: (num: number) => cy.get(`th:nth-child(${num})`),
needsParams: true,
children: {}
}
}
}
}
}
}
});
// or define re-usable components to include inside other context declarations
const tableContext = extractDeclaration({
header: () => cy.get("thead"),
body: {
self: () => cy.get("tbody"),
children: {
row: {
self: (num: number) => cy.get(`tr:nth-child(${num})`),
needsParams: true,
children: {},
},
},
},
});
const context = makeContext("larger re-usable context", {
table: {
self: cy.get("#table-wrapper"),
children: tableContext,
},
// ... other selectors
})
Context actions
Commonly repeated functionality can be abstracted into actions. Actions are functions
that accept as their first argument a Cypress chainable, and an arbitrary number of arguments.
In the test, The value of self is passed as the first argument, and any arguments
passed into the action.
As an example, with the previous example's context, if you find yourself writing a test like this:
context.table.in((table) => {
table.body.in((body) => {
body.row(1).realHover().click();
});
});
cy.findByText("Framulous").should("be.visible");
context.table.in((table) => {
table.body.in((body) => {
body.row(2).realHover().click();
});
});
cy.findByText("Eggsplicious").should("be.visible");
context.table.in((table) => {
table.body.in((body) => {
body.row(3).realHover().click();
});
});
cy.findByText("Otherficious").should("be.visible");You can define an action clickTableRow:
const context = makeContext("larger re-usable context", {
table: {
self: cy.get("#table-wrapper"),
children: tableContext,
actions: {
clickTableRow: (self, table, num: number) => {
self.within(() => {
getActionContext(tableContext, table).body.in((body) => {
body.row(num).realHover().click();
});
});
},
},
},
});and then call it in the test:
context.table.clickTableRow(1);
cy.findByText("Framulous").should("be.visible");
context.table.clickTableRow(2);
cy.findByText("Eggsplicious").should("be.visible");
context.table.clickTableRow(3);
cy.findByText("Otherficious").should("be.visible");Actions should be used sparingly, and only after it is abundantly clear that they
alleviate duplication, or are necessary to avoid gotchas, such as the need to
call realHover() prior to click() to avoid flakiness.
Actions appear with intellisense on the chainable returned from a context, and have full type safety for the parameters. In addition, the action will log both its name and user-supplied parameters to allow debugging/inspection in the Cypress command log.
Context actions gotchas
Some context actions should be executed within the parent, as in the clickTableRow example above.
Others, however, should be executed in the parent scope. For those, omit the self.within wrapped around the getActionContext as in the example above.
API
makeContext
makeContext creates a context that can be used directly in a test. It accepts 2 arguments:
- a description of the thing that is represented
- a context declaration
The description is only used in error messages when accessing a selector that does not exist in the declaratin to make it easier to debug in Cypress's log.
extractDeclaration
extractDeclaration is purely a convenience helper that allows declaring a context with full type safety and inference.
Whenever declaring a re-usable context declaration, it should be used
extractSelector
extractSelector is a convenience helper that allows declaring a selector with full type safety and inference. This can be
used for standalone selector declaration.
getActionContext
getActionContext is useful to launder a context passed to a context action function to get type safety. It should be passed
the children object from which to extract type information, and the context object, and returns the same context object
with full type safety.
