Here’s the markup:
<ion-input data-cy="email" type="email" class="border" placeholder="EMAIL"></ion-input>
And the test code:
const typedText = 'test@email.com';
cy.get('[data-cy=email]')
.type(typedText, { force: true })
.should('have.value', typedText);
But Cypress throws this error:
CypressError: cy.type()
failed because it requires a valid typeable element.
How can I properly type text into an <ion-input>
using Cypress input commands?
Do I need to target a shadow DOM or the internal native input somehow?
Yeah, I ran into this exact problem when working with Ionic apps. The issue is that <ion-input>
is a custom web component — it wraps a native <input>
inside its Shadow DOM. So, you can’t type directly into the <ion-input>
tag itself.
What worked for me was querying the native input like this:
cy.get('ion-input[data-cy="email"]')
.shadow()
.find('input')
.type('test@email.com')
.should('have.value', 'test@email.com');
This tells Cypress to pierce into the shadow DOM and access the actual input field. Once I started doing this, all my Cypress input tests worked like a charm.
So, in one of my previous projects, I found typing didn’t always trigger the necessary events inside <ion-input>.
Instead of using .type()
, I ended up manually setting the value and firing the ionChange event.
Something like:
cy.get('ion-input[data-cy="email"]')
.then($el => {
const input = $el[0].shadowRoot.querySelector('input');
input.value = 'test@email.com';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('ionChange', { bubbles: true }));
});
It feels a bit hacky, but it worked consistently and didn’t rely on Cypress input mechanics not recognizing Shadow DOM.
I faced this when testing an Ionic React project.
The solution that worked for me was turning on Cypress’s experimental shadow DOM support. Just update your cypress.config.js
like this:
export default defineConfig({
e2e: {
experimentalModifyObstructiveThirdPartyCode: true,
includeShadowDom: true
}
});
Then your test can use .shadow()
as mentioned earlier.
From there, Cypress input interactions start working properly since it understands how to deal with components like <ion-input>.
It made a noticeable difference once I enabled those flags.