Iframe Steps

Iframe steps let scenarios reach inside embedded frames - payment widgets, captchas, embedded videos, third-party forms - that normal steps cannot see because they only search the top document. The group is backed by Playwright's frameLocator API: you switch the active scope to a frame, run frame-scoped click / fill / assert steps, then switch back to the root document.

Switching is cheap and lazy. The switch step stores a frame locator on the scenario world; the frame itself is resolved when the first frame-scoped step runs, and Playwright's auto-waiting applies inside the frame just as it does outside. Frames can be addressed four ways: by CSS selector, by an explicit locator string, by the iframe's title attribute, or by its name attribute - pick whichever the embed markup makes stable.

Every frame-scoped step fails with a friendly, actionable error when no frame is active or the element is missing - typically reminding you to switch first, or to wait for the iframe to appear (When I wait for "iframe" to appear) before switching. Only the steps ending in inside the iframe honor the frame scope; all other steps keep operating on the main document.

Quick reference

StepWhat it does
When I switch to iframe "iframe.payment"Scope subsequent frame steps to an iframe by CSS selector.
When I switch to iframe with locator "iframe.payment"Same, phrased for explicit Playwright frame-locator strings.
When I switch to the iframe with title "Payment"Scope to the iframe whose title attribute matches.
When I switch to the iframe with name "payment"Scope to the iframe whose name attribute matches.
When I switch to the root documentClear the frame scope; steps target the top document again.
When I click "Confirm" inside the iframeClick visible text inside the active iframe.
When I click "submit-btn" by attr inside the iframeClick by id, class, name, or data-testid inside the active iframe.
When I fill in "card_number" with "4242 4242 4242 4242" inside the iframeFill a field by [name] or id inside the active iframe.
Then I should see "Payment received" inside the iframeAssert visible text exists inside the active iframe.
Then I should not see "Error" inside the iframeAssert visible text is absent inside the active iframe.

Steps in detail

When I switch to iframe "..." / with locator "..."

Both accept any Playwright-compatible selector for the <iframe> element - iframe.payment, #stripe-frame, iframe[name=widget], iframe[src*=embed]. The two phrasings are interchangeable; use whichever reads better. The original page handle is remembered so the scope can be restored later.

When I switch to iframe "iframe.payment"
When I switch to iframe with locator "#stripe-frame"

When I switch to the iframe with title "..." / with name "..."

Attribute shorthands that build iframe[title="..."] or iframe[name="..."] selectors for you. The title variant is the accessibility-friendly choice - well-built embeds carry a descriptive title, and it doubles as documentation in the feature file.

When I switch to the iframe with title "Stripe checkout"
When I switch to the iframe with name "captcha"

When I switch to the root document

Clears the frame scope so subsequent frame-scoped steps (and your mental model) return to the top document. Always switch back once you are done inside the frame - assertions about the host page made while a frame is active can otherwise look in the wrong place.

When I switch to iframe "#payment"
And I press "Pay"
And I switch to the root document
Then I should see "Payment received"

When I click "..." inside the iframe / by attr inside the iframe

The text variant clicks the first element whose visible text matches exactly. The by attr variant tries id, class, [name], and [data-testid] in that order - the failure message tells you all four were checked. Both require an active frame scope.

When I switch to iframe ".captcha-frame"
And I click "I am not a robot" inside the iframe
And I click "submit-btn" by attr inside the iframe

When I fill in "..." with "..." inside the iframe

Fills the first element matching [name="field"] or #field inside the active frame. This is the step for hosted payment fields, which live in cross-origin iframes precisely so the host page cannot touch them directly.

When I switch to the iframe with title "Payment"
And I fill in "card_number" with "4242 4242 4242 4242" inside the iframe
And I fill in "cvv" with "123" inside the iframe
And I fill in "exp_date" with "12/30" inside the iframe

Then I should (not) see "..." inside the iframe

Presence and absence assertions on exact visible text within the active frame - the frame-scoped counterparts of the global I should see steps.

Then I should see "Payment received" inside the iframe
And I should not see "Declined" inside the iframe

Complete example

Feature: Embedded payment widget

  Scenario: Pay inside the provider's iframe and confirm on the host page
    Given I am on "/checkout"
     When I wait for "iframe" to appear
      And I switch to the iframe with title "Stripe checkout"
      And I fill in "card_number" with "4242 4242 4242 4242" inside the iframe
      And I fill in "cvv" with "123" inside the iframe
      And I fill in "exp_date" with "12/30" inside the iframe
      And I click "Confirm" inside the iframe
     Then I should see "Payment received" inside the iframe
      And I should not see "Declined" inside the iframe
     When I switch to the root document
     Then I should see "Order complete"

Tips

  • Wait for the iframe element to exist before switching (When I wait for "iframe" to appear) - embeds that inject their frame via JavaScript often are not in the DOM on page load.
  • Nested frames: the scope always resolves from the top page, so target the innermost iframe with a selector specific enough to reach it (e.g. iframe[src*=embed]).
  • Only inside the iframe steps are frame-scoped. If you need richer interactions inside a frame (selects, keyboard input), drive them from JavaScript steps or extend this group.

Back to all step groups