ScreenshotNeo

BlogHow-to

How to Test Shadow DOM Elements in Cypress Studio

Cypress Studio cannot record Shadow DOM interactions, but Cypress tests can still query them. Record the supported flow, then add the right Shadow DOM command.

By the ScreenshotNeo team4 October 20268 min read

Short answer: Cypress Studio cannot record interactions inside Shadow DOM. Cypress tests can still test elements in an open shadow root: use .shadow() to traverse from a known host, or use includeShadowDom: true when a query should search through shadow boundaries. A practical workflow is to record the supported parts of the end-to-end flow in Studio, save the spec, then add or edit the Shadow DOM commands in the spec file.

This is a Studio recording limitation, not a general Cypress testing limitation. The Cypress Studio guide lists “iFrames and Shadow DOM are not supported.” Cypress separately documents commands for traversing and querying Shadow DOM. The steps below combine those documented capabilities into a workflow; Cypress does not prescribe this exact recording-then-editing recipe.

1. Record the supported flow, then edit the spec

  1. Open Cypress in interactive mode and start a new end-to-end test, or open a spec you want to extend with Studio.
  2. Use Studio to record the interactions it supports outside the shadow root, such as clicking, typing, checking a box, or selecting an option.
  3. Save the changes. Studio writes them to the spec, where you can edit the generated code inline or in your editor.
  4. Add the Shadow DOM query at the point in the flow where the component is ready and the target control should be available.
  5. Run the spec. If the query fails, inspect the Cypress Command Log and snapshots, then verify the host selector, root mode, and target selector.

Studio is for end-to-end tests; its guide says Component Testing is not supported. The guide also lists Cucumber-style tests and recording across multiple origins as unsupported. Studio requires internet access and sourcemaps. Studio AI is separate from ordinary Studio recording: AI assertion recommendations require Cypress 15.11.0 or later and a Cypress Cloud account with a linked project.

2. Traverse from a known shadow host with .shadow()

Use this approach when you know which component owns the control and want the test to make that boundary explicit. For example, if checkout-panel is the shadow host and its open root contains a button:

cy.get('checkout-panel')
  .shadow()
  .find('button')
  .click()

.shadow() must be chained from a DOM element that is a shadow host. It yields that host’s shadow root, so you can continue with Cypress commands such as .find(), .contains(), assertions, and actions. Cypress retries the command while waiting for the host, its shadow root, and chained assertions, subject to the test’s timeout settings.

Prefer a selector that identifies the intended control rather than a broad selector if the component contains multiple buttons:

cy.get('checkout-panel')
  .shadow()
  .find('[data-cy="confirm-order"]')
  .should('be.visible')
  .and('be.enabled')
  .click()

The data-cy attribute here is an example selector; use an attribute that exists in your application. If the host itself is nested inside another open shadow root, traverse each host in order:

cy.get('app-shell')
  .shadow()
  .find('checkout-panel')
  .shadow()
  .find('[data-cy="confirm-order"]')
  .click()

3. Search through shadow boundaries with includeShadowDom

When the query should search through Shadow DOM rather than explicitly walking from one selected host, pass includeShadowDom: true to the query:

cy.get('.shadow-button', { includeShadowDom: true })
  .should('be.visible')
  .click()

This can make a query concise when its selector is distinctive. The selector still needs to identify the intended element; if several components contain a matching class, scope the query or use an explicit host traversal.

Cypress also documents configuring includeShadowDom more broadly through the includeShadowDom configuration option. A per-query option keeps the behavior local and visible in the spec; a global setting can be useful when many queries in the project are meant to cross shadow boundaries. Check the cy.get() documentation for the configuration syntax supported by your Cypress version.

4. Choose the query style for the component

Approach Use it when What to watch
.shadow() You know the host and want to target a specific component’s root. The preceding subject must be the shadow host. Traverse each nested host explicitly.
includeShadowDom: true You want a query to include shadow DOM in its search. A broad selector can match more than one element. Scope or strengthen it where needed.

These are both documented APIs, not a universal Cypress recommendation for every component. Choose based on how clearly the test identifies the component and target.

5. Runnable example spec

This example assumes the application has a route at /checkout, a checkout-panel custom element with an open shadow root, and a confirm button inside it. Replace the route and selectors with the ones in your app.

describe('checkout shadow component', () => {
  it('confirms an order through the component shadow root', () => {
    cy.visit('/checkout')

    cy.get('checkout-panel')
      .shadow()
      .find('[data-cy="confirm-order"]')
      .should('be.visible')
      .and('be.enabled')
      .click()

    cy.get('[data-cy="order-confirmation"]')
      .should('be.visible')
  })
})

To use the inclusive query approach instead, replace the component traversal with:

cy.get('[data-cy="confirm-order"]', { includeShadowDom: true })
  .should('be.visible')
  .and('be.enabled')
  .click()

6. Handle timing, nesting, and root limitations

  • Wait for the application state, not an arbitrary pause. Cypress queries retry while waiting for matching elements. If the component loads asynchronously, assert on a stable readiness signal or the target element. Add a fixed delay only when the application behavior genuinely requires a time-based wait.
  • Traverse nested roots one host at a time. A control several components deep may require multiple .shadow() calls. Confirm the DOM structure in the browser before choosing the chain.
  • Use the correct root mode. The documented examples cover traversal or querying through Shadow DOM. They do not establish that Studio can record Shadow DOM or that these commands provide access to closed roots. Do not infer closed-root compatibility from an open-root example.
  • Keep selectors tied to intent. Prefer a stable test attribute or a scoped selector over a generic button when the component has several similar controls.
  • Keep the assertion close to the action. Checking that the target is visible or enabled can reveal whether the failure is a lookup, readiness, or action problem.

7. Troubleshooting

Symptom Likely cause Fix
Studio does not record the click inside the component. Shadow DOM interaction recording is a documented Studio limitation. Record the supported surrounding flow, save the spec, then add the Cypress query in code.
.shadow() reports that the subject is not a shadow host or cannot yield a root. The selected element is not the host, the component has not attached its root yet, or the selector found the wrong element. Inspect the DOM, select the custom-element host, and wait for a component readiness condition before traversing.
The element is not found after .shadow(). The target selector is wrong, the control is nested under another host, or the app has not rendered it yet. Check the root contents and selector; traverse additional hosts if necessary, and assert on a stable readiness signal.
includeShadowDom finds the wrong element or multiple elements. The selector is too broad across the document and its shadow trees. Use a more specific selector or scope from the intended component with .shadow().
The test times out while searching. The host or target never becomes available, the page is in an unexpected state, or the default command timeout is too short for the real load. Check the Command Log and page state first. Fix the app or selector if the element never appears; adjust Cypress timeout configuration only when the expected operation legitimately takes longer.
A click after shadow traversal behaves ambiguously in Chrome. Cypress documents a known Chrome click issue for this case. Try the documented position option .click('top') and confirm the intended hit target.
Studio itself does not start or record as expected. Studio requirements may be unmet, such as internet access or sourcemaps; Studio AI has additional Cloud and version prerequisites. Check the Studio guide and distinguish ordinary recording from Studio AI assertion recommendations.

8. Performance, reliability, and maintenance

Shadow DOM queries do not require a separate browser automation stack: they are Cypress commands in the same spec. Reliability depends mainly on targeting the correct host and using stable selectors, then letting Cypress retry queries and assertions. Avoid fixed waits as a substitute for a meaningful readiness condition; they can make a spec slower while still failing when timing varies.

Explicit .shadow() traversal shows the component path in the test and helps avoid accidental matches elsewhere. includeShadowDom can reduce traversal code, but broad queries may become less precise as the page gains components. Keep selectors and root boundaries aligned with the behavior under test.

There is no separate Shadow DOM service cost in the documented workflow: it uses Cypress commands in your existing test. Cypress Studio AI has separate prerequisites, including a Cypress Cloud account and the specified Cypress version; consult current Cypress documentation for any plan or pricing terms before adopting it.

9. Or skip the browser setup

If the goal is to capture how a page looks rather than test its component behavior, ScreenshotNeo provides a website screenshot API. It does not replace Cypress interaction tests. One GET request captures a URL as an image or PDF; see the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers say which verdict and billing outcome applied. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.

10. FAQ

Can Cypress test Shadow DOM if Studio cannot record it?

Yes. Studio’s recording limitation does not prevent Cypress code from using documented Shadow DOM query commands.

Does this workflow apply to component tests?

The Studio guide says Component Testing is not supported in Studio. The examples here describe Cypress commands in an end-to-end spec.

Does includeShadowDom need to be enabled globally?

No. Cypress documents it as a per-query option and also provides a configuration option for broader behavior.

Can I use these examples to test a closed shadow root?

The cited Cypress documentation establishes the APIs described here but does not establish closed-root compatibility. Do not assume these examples cover closed roots.

Primary Cypress references