In the index.js or the root file of your cypress/support folder, import and register cypress commands
import { registerShadowCommands } from '@updater/cypress-helpers';
registerShadowCommands();
Example
cy
.get('#homeServicesContainer')
.shadowGet('upd-home-services-landing-page-route')
.shadowFind('#title')
.shadowShould('have.text', 'Connect TV & Internet');
Cypress does not have native support for shadowDom
.
This module gives us the ability to run similar commands in shadow DOM.
These commands are not as fully developed as the native ones,
but resemble native Cypress commands in usage.
Only use these commands on elements within a
shadowRoot
, not on elements which may have ashadowRoot
.
What to Expect
- Commands should feel familiar as Cypress ones and behave in similar ways
- There is automatic retrying for certain commands (e.g.
shadowGet
andshadowShould
) - Non-Dom results can be yielded into regular Cypress commands. For example:
cy.shadowGet('some-shadow-element')
.then($el => $el.text())
.should('match', /*.?/g); // no need to build non-DOM interactors!
The main differences are
- Limited API use / less supported features
- Retrying is on a per command (not per chain) basis (except for
shadowGet
, which does support retrying upcoming assertions) - No extra visibility/attachment/covered/disabled checks on
click
andtrigger
- Potentially others...TBD
registerShadowCommands
shadowGet
shadowFind
shadowShould
shadowEq
shadowClick
shadowSelect
shadowTrigger
shadowContains
Register shadow commands on the cy
namespace,
such as shadowGet
, shadowShould
, etc.
Retryable / Async
- deeply searches DOM for all elements which have a
shadowRoot
- queries (with jquery) each
shadowRoot
with provided selector - Returns all matching results
- Subject is optional
Param | Type | Description |
---|---|---|
selector | string |
jquery selector used to find matching elements |
[options] | object |
the options to modify behaviour of the command |
[options.timeout] | number |
number of ms to retry the query until marking the command as failed. Defaults to 4s or the defaultCommandTimeout in cypress.config. |
Example
.shadowGet(selector)
.shadowGet(selector, options)
// searches entire DOM
cy.shadowGet('custom-el-in-shadow')
// searches only within container-el
cy.get('container-el')
.shadowGet('custom-el-in-shadow')
// waits up to 30s (resolve immediately when found)
cy.shadowGet('custom-el-in-shadow', { timeout: 30000 })
Retryable / Async
- must be chained from another command (e.g.
shadowGet
orget
) - queries (via jquery) the yielded element and the yielded element's
shadowRoot
for matches - Only searches within the
shadowRoot
of the yielded element (as well as just the regular DOM children) - Note it is a shallow search within the yielded elements shadowRoot. It will not
do a deep search through shadowRoots for the matching element. For deep search, use
shadowGet
You may wonder why a shallow search is needed. That's because in shadowDom Unique selectors like
id
s can be repeated. Sometimes we just want to search in the immediate root without worrying about coliding with things further down the DOM tree.
Param | Type | Description |
---|---|---|
selector | string |
jquery selector used to find matching elements |
[options] | object |
the options to modify behaviour of the command |
[options.timeout] | number |
number of ms to retry the query until marking the command as failed. Defaults to 4s or the defaultCommandTimeout in cypress.config. |
Example
.shadowFind(selector)
.shadowFind(selector, options)
// shadowFind queries against the subjects' children and shadowRoot
cy.get('container-el')
.shadowFind('button.action')
// shadowGet matches from all shadowRoots
// shadowFind queries against the subjects' children and shadowRoot
cy.shadowGet('custom-el-in-shadow')
.shadowFind('button.action') // will query against the subject's children and shadowRoot
Retryable / Async (Up to 4s, timeout not customizable)
This Utility is most useful when needing to run
assertions against shadowDom elements and it does so by leveraging jquery-chai
. Cypress
also does this, but it does not work in shadowDom.
- it accepts the
string
syntax like Cypress'should
- it does not accept the
function
syntax (but you can still useshould
with shadow elements as long as you run nonjquery-chai
assertions) - This smooths over the issues with Cypress'
jquery-chai
, which does explicit checks that are incompatible with shadowDom. - It uses a clean version of jquery and chai to run assertions against shadowDom elements.
- In general, you can use
should
as long as you do not need to assert against the shadow DOM.
When should I use
shadowShould
and when should I useshould
?Use
shadowShould
whenever you need to run assertions against elements within theshadowDom
. Lite DOM and regular DOM can be used withshould
. Also, any non-DOM values can be used withshould
. You can do something like,
.should(($el) => expect($el.text()).to.match(/.?/))
Or even,
.then(($el) => $el.text()).should('match', /.?/))
.These are examples of taking non-DOM values from the shadowDom elements and using regular Cypress commands and assertions on them.
Param | Type | Description |
---|---|---|
chainer | string |
the string |
value | any |
the value to be checked |
method | string |
the sub-chainer to check (see example) |
Example
.shadowShould(chainers)
.shadowShould(chainers, value)
.shadowShould(chainers, method, value)
cy.get('@dateLabel')
.should('have.text', '2017-11-22');
cy.get('@datepicker')
.shadowFind('button[value="2017-11-22"]')
.shadowShould('have.attr', 'tabindex', '0');
No-Retry / Sync
Yields a subject at the index from a given subject.
For example, if this is chained from a shadowGet
which yields multiple elements, shadowEq
will return
the element at the specified index.
It will not retry and yields whatever is passed into it synchronously.
Param | Type | Description |
---|---|---|
index | number |
specifies the index of the element to yield from the subject |
Example
.shadowEq(selector)
cy.get('container-el')
.shadowFind('button.action')
.shadowEq(2)
cy.shadowGet('custom-el-in-shadow')
.shadowEq(4)
No-Retry / Sync
- Allows you to click on an element within a shadowRoot.
- Can be chained from
shadowGet
,shadowFind
, orshadowEq
- Clicks on the first element (index 0) from the yielded elements of previous command
- Cypress'
click
does not work in shadowDom for multiple reasons - Uses native or jquery .click functionality, but does not do additional checks Cypress' click does such as checking the component is visible, not covered, and not disabled.
- Would need to put in more work to ensure component clicks cannot pass through when the component is not in an actual interactive state.
Example
.shadowClick()
cy.get('container-el')
.shadowGet('custom-element-within-shadow-dom')
.shadowFind('button.action')
.shadowClick()
No-Retry / Sync
- Allows you to select an option from a
select
element within a shadowRoot. - Can be chained from
shadowGet
,shadowFind
, orshadowEq
- Expects an actual
select
element to be the subject - Selects the provided
option
from the first element (index 0) from the yielded elements of previous command - Option can be by
value
or bytext
, but must be strictly equal - Cypress'
select
does not work in shadowDom for multiple reasons - Does not do additional checks Cypress' select does such as checking the component is visible, not covered, and not disabled.
- Would need to put in more work to ensure component selects cannot pass through when the component is not in an actual interactive state.
Param | Type | Description |
---|---|---|
option | String | Number |
The option from the select to select, by value or by text |
Example
.shadowSelect(option)
cy
.shadowGet('upd-select[name="state"]')
.shadowFind('select')
.shadowSelect('AL'); // by value
cy
.shadowGet('upd-select[name="bedrooms"]')
.shadowFind('select')
.shadowSelect('3 Bedroooms'); // by text
No Retry / Sync
- allows to trigger an event similarly to how Cypress'
trigger
works. - This works with elements on the shadow DOM since they pose problems with almost all of Cypress' commands.
- Currently only supports these events:
keydown
keypress
keyup
change
input
- Options can also be provided per event
key
events supports keyboard event options i.e.keyCode
orbubbles
change
andinput
events support thevalue
for the update
Param | Type | Description |
---|---|---|
event | string |
The event from the above list which will be triggered |
options | object | value |
Options which depend on the kind of event and modify the event's behaviour |
Example
.shadowTrigger(event)
.shadowTrigger(event, options)
// Changing an input
cy
.shadowGet('upd-input[name="postalCode"]')
.shadowFind('input')
.shadowTrigger('input', '99511');
// changing value of a custom element
cy
.shadowGet('upd-datepicker')
.shadowTrigger('change', '2019-01-02');
// triggering a key event
cy.get('@datepicker')
.shadowFind('button[aria-selected="true"]')
.shadowTrigger('keydown', { keyCode: 39, bubbles: true });
No-Retry / Sync
Convenience function to assert partial match between the textContent
of
an element and the passed in value.
This does not work like cy.contains
.
Literally just runs this assertion:
expect(subject[0].textContent).to.contain(text)
Param | Type | Description |
---|---|---|
text | string |
the text to match against |
Example
.shadowContains(text)
cy
.shadowGet('some-custom-elem')
.shadowContains('Should contain this text...')
File located at /src/shadowDom/shadowCommands.js
For anyone using typescript here's my first draft of the typings: