Skip to main content

Cypress Utils


Cypress Utils

A collection of utils that make writing tests in cypress less painful.

Usage​

$ yarn add @rexlabs/cypress-utils --dev

In your cypress test file:

import { style } from '@rexlabs/cypress-utils';
cy.get(style('Component.container')).within(() => {
//...
});

Why?​

E2E Testing (React) application can become really verbose really quickly. With selectors being one of the big pain points, as well as faking certain user interactions.

With this libraryt we try to abstract all common patterns, that we see re-occuring in our tests.

Documentation​

Styles​

style(string selector)

Uses the element-styles styletarget attribute to select the DOM nodes. This way you can use component names and style identifiers to select elements, which is really convenient.

Returns a selector string that can be past into cy.get, cy.find, etc.

cy.get(style('Component.container')).click();

trace(string selector)

Same as style, but utilising the data-styletrace attribute from element styles.

cy.get(trace('Comonent :: Trace -> Foo')).find('a').click();

Buttons​

getButton(string label)

Returns the first vivid (!) button within the current context with the given label. Uses style('Button.content') as selector to find the button component.

cy.get(someSelector).within(() => {
getButton('Hello World').click();
});

clickButton(string label)

Same as getButton just including the click action. So basically an alias for getButton(label).click.

cy.get(someSelector).within(() => {
clickButton('Hello World');
});

Action Menus​

clickActionMenu

A common and quite verbose task is to click a specific item of an action menu. This helper is trying to get rid of the verbosity. It finds the first action menu item with the given label in the current context and triggers the click event.

cy.get(someSelector).within(() => {
clickActionMenu('Add Item');
});

The context doesn't need to be the action menu itself. If there is only one action menu with an item with the given label on the page the context is theoretically not necessary, but I think it always helps to work with sane contexts to make the test easier to understand.

Modals​

getModal(string modalId, function context)

Finds the modal with the given modal id (= usually the modal component name), and runs the given function within this context, passing in selectors for header, content, footer and buttons of the selected modal.

getModal('AddItem', ({ header, content, footer, buttons }) => {
cy.get(header).find('h3').should('contain', 'Add Item'); // Check modal title
cy.get(buttons).contains('Done').click();
//...
});

Tables​

tables

Selects ALL tables on the page. Can be used to then pick the table you need from that selection. This is due to the lack of clear identifiers of tables at the moment!

cy.get(tables)
.find(':nth-child(3)')
.within(() => {
//...
});

tableSelectors

Similar to the helpers that getModal passes down, this is a set of selectors that can be used within a table context to make common actions easier.

cy.get(tables).first().within(() => {
cy
.get(tableSelectors.row(3))
.get(tableSelectors.cell(4))
.click();
});

// Available selectors
tableSelectors.header // <= thead of current table
tableSelectors.rows // <= selects ALL rows of the current table
tableSelectors.row(<int>nthChild) // <= selects the nth-child(x) or the current table rows
tableSelectors.cells // <= selects ALL cells
tableSelectors.cell(<int>nthChild) // <= selects the nth-child(x) cell

NOTE that :nth-child starts with index 1, not 0. So when you want to select the first row, you would go:

cy.get(tableSelectors.row(1));

Forms​

Probably one of the most common use cases as well as one of the most verbose ones is dealing with forms, more concrete filling out forms. Besides testing our most crucial paths of the app, we should also aim at targeting forms at throwing a set of different data combinations at them to make sure they don't break at edge cases. This should ideally be abstracted out as much as possible so that adding a new data scenario basically comes down to adding some JSON to a source object / file.

form(string name)

Selects a form by the given form name.

cy.get(form('myTestForm'));

getForm(string name, function context)

Same as above, but creates a context as well and runs the given function within that form context.

getForm('myTestForm', () => {
//...
});

field(string name)

Selects a form field by its field name.

getForm('myTestForm', () => {
cy.get(field('testField'));
});

fill(string fieldName, string value)

Fills out a text field with the given value.

getForm('myTestForm', () => {
fill('testField', 'Hello World');
});

select(string fieldName, string value)

Changes the value of the select field with the given field name to the option with the given value as label (!).

getForm('myTestForm', () => {
select('testSelect', 'Test Option Label');
});

NOTE: this seemed most intuitive, but we run into issues where multiple options can have the same label, but not sure how far we can select options by their actual value...

ALSO: we might need to think about entity selects that populate from API calls that maybe require some typing before the options become visible… select should fill out the search with the requested value to ensure the option is visible and therefore clickable.

Todos​

  • Better form and input handling
  • Abstraction for expandable table rows

Development​

Install dependencies

$ yarn

Available Commands

$ yarn test               # runs all units tests
$ yarn test:watch # runs unit tests when files change
$ yarn build # bundles the package for production

Copyright Β© 2019 Rex Software All Rights Reserved.