QA PRACTICE•IFRAMES SHADOW DOM

Advanced locator lab

iFrames & Shadow DOM

Practice the boundaries that make real-world UI automation different: browsing contexts, nested frames, postMessage, and component encapsulation.

← Back to Locator Hub

Scenario 1 · Embedded QA product

Enter the nested support message and verify that the parent counter receives the frame event.

HSNAKD QA page inside iframeiframe[data-testid="demo-site-iframe"]

Nested support iframe

Nested frame submissions received by parent: 0

Scenario 2 · Web component boundary

Locate and interact with a control inside an open Shadow DOM root.

The button below lives inside an open shadow root. Playwright pierces these by default.

Automation notes

iFrame: switch with frameLocator() before locating nested controls.

Nested frame: keep the parent and child context explicit in the test.

Shadow DOM: open roots are pierceable by Playwright; closed roots require a testability decision.

Test cases · 10 scenarios

Instructions and acceptance criteria

Positive · Edge case · Negative
TC-FRAME-001Positive

Simple iframe button

Switch to a same-origin frame and click the nested action.

Do this: Locate the iframe by title and click the button inside it.

Expected: The nested result becomes visible in the parent status area.

frameLocator('iframe[title=Support]').getByRole('button', { name: 'Send' })
TC-FRAME-002Positive

Nested frames and postMessage

Use two frame boundaries and verify a parent counter event.

Do this: Submit the nested support widget and observe the parent counter.

Expected: The parent receives one message and increments the counter once.

frameLocator('#parent-frame').frameLocator('#nested-frame').getByTestId('frame-submit')
TC-FRAME-003Edge case

srcdoc and cross-origin boundary

Compare same-origin srcdoc access with a cross-origin document.

Do this: Attempt DOM access and document why the cross-origin frame is isolated.

Expected: The test uses frame context or an agreed postMessage/API contract; it does not bypass browser security.

page.frameLocator('iframe[title=External payment]')
TC-FRAME-004Edge case

Dynamic frame lifecycle

Wait for a delayed frame, changing src, and replacement.

Do this: Recover from a Frame was detached event after a re-render.

Expected: The locator resolves the current frame and avoids stale frame handles.

page.frameLocator('[data-testid=dynamic-frame]').getByRole('button')
TC-FRAME-005Positive

Frame selection and title

Compare id, name, title, index, and meaningful accessibility metadata.

Do this: Select the intended frame with a meaningful title.

Expected: The frame has a stable selector and an informative title attribute.

page.frameLocator('iframe[title=QA support widget]')
TC-SHADOW-006Positive

Open Shadow DOM control

Interact with a button, input, counter, and list inside an open root.

Do this: Increment the counter inside the open component.

Expected: The count changes and the component remains keyboard reachable.

page.getByTestId('shadow-host').getByRole('button', { name: /Increment/ })
TC-SHADOW-007Edge case

Nested Shadow DOM

Locate through a host containing another custom element with its own root.

Do this: Traverse both component boundaries and assert the nested label.

Expected: The locator resolves the nested component without document-wide selectors.

page.locator('qa-shell').locator('qa-card').getByRole('heading')
TC-SHADOW-008Negative

Closed Shadow DOM

Understand why the root is inaccessible and choose a testability contract.

Do this: Confirm direct DOM traversal fails, then use an exposed API or host-level hook.

Expected: The test does not attempt to pierce a closed root with XPath.

page.getByTestId('closed-component')
TC-SHADOW-009Edge case

Shadow inside iframe

Switch context first, then locate the web component inside the frame.

Do this: Find the open component after entering the iframe.

Expected: Frame and shadow boundaries are handled as separate steps.

frameLocator('#component-frame').locator('qa-player').getByRole('button')
TC-SHADOW-010Positive

Web Component and slot

Distinguish light DOM slotted content from shadow-owned controls.

Do this: Assert slotted text and click the component’s internal action.

Expected: The test identifies which node belongs to light DOM and which belongs to shadow DOM.

page.locator('qa-card').getByText('Release quality')

Common errors and fixes

ErrorWhat to check
NoSuchElementExceptionFrame context was not selected before searching for the nested element.
stale element referenceThe frame/component re-rendered; resolve the locator again instead of reusing a handle.
Frame was detachedWait for the current frame and retry through frameLocator after replacement.
strict mode violationAdd a frame title, host scope, accessible name, or filter.
XPath cannot pierce Shadow DOMUse the component host/API or an automation tool’s shadow-aware locator.
cross-origin access errorUse frame context plus a supported contract; browser same-origin policy cannot be bypassed.