cypress-selectors
is a library that provides a bunch of convenient declarative selectors for Cypress.
It helps to organize and re-use selectors and turns this:
const getSearchInput = () => cy.get('input');
const getSubmitSearchButton = () => cy.get('[cypress-id]=submit-search');
const getSearchResults = () => cy.get('.search-result');
const getMain = () => cy.xpath(`//div[@cypress-id='main']`)
into that:
class HomePage {
@ByType('input') searchInput: Selector;
@ByAttribute('submit-search') submitSearch: Selector;
@ByClass('search-result') searchResults: Selector;
@ByXPath(`//div[@cypress-id='main']`) main: Selector;
}
npm i -D cypress-selectors
The docs are located at https://anton-kravchenko.github.io/cypress-selectors
- Searching elements by
attribute
,class
,id
,type
,selector
,text
,link text
,name
andxpath
:
import { By } from 'cypress-selectors';
import type { Selector } from 'cypress-selectors';
class Selectors {
@By.Id('main')
static main: Selector; // equivalent of - cy.get('#main')
@By.Type('input')
static input: Selector; // equivalent of - cy.get('input')
@By.Class('button')
static button: Selector; // equivalent of - cy.get('.button')
@By.Attribute('header')
static header: Selector; // equivalent of - cy.get('[cypress-id=header')
@By.Selector('ul > li .focus')
static listItem: Selector; // equivalent of - cy.get('ul > li .focus')
@By.XPath(`//input`)
static xInput: Selector; // equivalent of - cy.xpath('//input')
@By.Name('email')
static email: Selector; // equivalent of - cy.get(`[name="email"]`)
@By.Text.Exact('Foo')
static bar: Selector; // equivalent of - cy.xpath(`//*[text()='Foo']`)
@By.Text.Partial('Foo')
static p: Selector; // equivalent of - cy.xpath(`/*[contains(text(), 'Foo')]`)
@By.Link.ExactText('Link A')
static linkA: Selector; // equivalent of - cy.xpath(`//a[text()='Link A']`)
@By.Link.PartialText('Link B')
static linkB: Selector; // equivalent of - cy.xpath(`//a[contains(text(), 'Link B')]`)
}
-
Searching child elements
2.1 By linking parent selector via reference
class Selectors { @ById('main') static parent: Selector; @ByClass('button', { parent: Selectors.parent }) static children: Selector; // equivalent of - cy.get('#root .button') }
2.2 By linking parent selector via
alias
andparentAlias
attributesclass Selectors { @ById('main', { alias: 'root' }) static parent: Selector; @ByClass('button', { parentAlias: 'root' }) static children: Selector; // equivalent of - cy.get('#root .button') }
-
Implementing Page Objects (PageObject is considered to be an anti-pattern although)
class SearchPagePO { @ById('input') searchInput!: Selector; @ByAttribute('submit-search') submitSearch!: Selector; searchFor(term: string): SearchPagePO { this.searchInput.type(term); this.submitSearch.click(); return this; } }
-
Searching by non-default attribute (by default
ByAttribute
usescypress-id
)class Selector { @ByAttribute('submit', { attribute: 'cy-data' }) static customAttribute: Selector; }
-
Selecting elements by index
class Selector { @ByAttribute('row', { eq: 0 }) static firstRow: Selector; @ByAttribute('row', { eq: 1 }) static secondRow: Selector; }
-
Selecting elements by XPath
class Selector { @ByXPath(`//div[@cypress-id='app']/div[@cypress-id='children']`) static app: Selector; @ByXPath(`count(//div)`) static numberOfDivElements: Selector; }
-
Specifying custom timeout for selectors
class Selectors { /* Will try to find an element for up to 10 seconds */ @ById('main', { timeout: 10 * 1000 }) static parent: Selector; /* By default, timeout for any selector is inherited from "defaultCommandTimeout" value of Cypress configuration */ @ById('app') static parent: Selector; }
import { ResetSelectorsConfiguration, ConfigureSelectors } from 'cypress-selectors';
/* Setting configuration */
ConfigureSelectors({
defaultAttribute: 'cy-id', // Default: 'cypress-id' - sets default attribute to be used by @ByAttribute selector
isLoggingEnabled: true, // Default: false - logs generated selectors before accessing elements
searchOnlyFirstLevelDescendants: true, /* Default: false
=> if true: the lib will be using `Child Selector` for resolving `child-parent` relationship - https://api.jquery.com/child-selector/
=> if false: the lib will be using `Descendant Selector` for resolving `child-parent` relationship - https://api.jquery.com/descendant-selector/ */
});
/* Re-setting configuration to defaults */
ResetSelectorsConfiguration();
-
The library is built around decorators which are still a stage-2 proposal.
-
children-parent
linking viaalias
andparentAlias
works only within a single class - if you need to link selectors from different classes usechildren-parent
linking via reference as shown in2.2
. -
children-parent
linking via reference uses static class fields stage-3 proposal. For some reason,babel-loader
andts-loader
transpile code that defines static class fields differently.For example, if you transpile the following code with
babel-loader
using@babel/preset-typescript
preset and@babel/plugin-proposal-decorators, @babel/plugin-proposal-class-properties
plugins:class Selectors { @ById('main') static parent: Selector; @ByClass('button', { parent: Selectors.parent }) static children: Selector; }
you will get
cannot access 'parent' before initialization
error, while if being transpiled viats-loader
it works as expected.However, this example could be fixed by just extracting
parent
selector to a separate class as following:class ParentSelectors { @ById('main') static parent: Selector; } class ChildrenSelectors { @ByClass('button', { parent: ParentSelectors.parent }) static children: Selector; }
If
child-parent
linking is defined this way if will work with bothbabel-loader
andts-loader
. -
The documentation doesn't go into details on how to set up Cypress and transpiling via
ts-loader
. However, the setup of this project could be used as a good reference. The whole setup is done in 2 files:webpack.config.js
andtsconfig.json
. If you need another reference on setting up a project like this - check out this article. -
All of the examples are declaring selectors as
static
class fields. This is not a requirement - the same functionality could be achieved with nonstatic
class fields. However please note, thatchild-parent
relationship is not going to work withoutparent
being declared asstatic
class field.