grafana/k6 · error

finding iframe with selector

Error message

finding iframe with selector %q: %w

What it means

stepIntoFrame() resolves chained selectors that cross iframe boundaries (e.g. 'iframe#a >> button'). It waits for the iframe element itself using the selector prefix before the '>>' boundary, and that waitForSelector call failed — usually a timeout waiting for the iframe element to appear or become visible.

Solutions

  1. Wait for the iframe to exist/attach first: await page.locator('iframe#app').waitFor() before using the chained selector
  2. Verify the iframe selector actually matches: (await page.locator('iframe#app').count()) should be 1
  3. Increase the timeout on the action or globally (page.setDefaultTimeout / options.timeout)
  4. If the iframe is cross-origin, ensure it has fully loaded — prefer page.waitForLoadState('load') or frame-level waits before stepping in

Example fix

// before
await page.locator('iframe#widget >> button.go').click();

// after
await page.locator('iframe#widget').waitFor({ state: 'attached' });
await page.waitForLoadState('load');
await page.locator('iframe#widget >> button.go').click({ timeout: 30_000 });
Defensive patterns

Strategy: validation

Validate before calling

const iframeSel = 'iframe#widget';
await page.locator(iframeSel).waitFor({ state: 'attached', timeout: 15_000 });
if ((await page.locator(iframeSel).count()) !== 1) {
  throw new Error(`expected exactly one iframe for ${iframeSel}`);
}

Try / catch

try {
  await page.locator('iframe#widget >> button').click({ timeout: 15_000 });
} catch (e) {
  if (/finding iframe with selector/.test(String(e))) {
    await page.waitForLoadState('load');
    await page.locator('iframe#widget >> button').click({ timeout: 30_000 });
  } else throw e;
}

Prevention

When it happens

Trigger: Using a frame-crossing selector/locator where the <iframe> element does not exist yet or stays hidden within the timeout: page.locator('iframe[title="app"] >> text=Submit').click() before the iframe is injected; iframe with display:none; wrong iframe selector (no match ever); default timeout too short for slow lazy-loading.

Common situations: SPAs that mount iframes late (after auth, after data fetch); ad or widget iframes hidden until interaction; misspelled iframe selectors; slow CI machines where the default timeout expires first.

Related errors


AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18). Data as JSON: /api/errors/ee6c12b4c1c1125e. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/common/element_handle.go:643

	return false, fmt.Errorf(
		"waiting for states %v of element %q", states, reflect.TypeOf(result))
}

// stepIntoFrame steps into an iframe/frame. Due to CORS, we need to perform this
// step outside of the browser (chromium). It returns the frame that it has stepped
// into and the selector to use within that frame.
func (h *ElementHandle) stepIntoFrame(
	apiCtx context.Context, parsedSelector *Selector, frameNavIndex int, opts *FrameWaitForSelectorOptions,
) (*Frame, string, error) {
	// Split selector at frame navigation boundary
	beforeFrame, afterFrame := h.splitSelectorAtFrame(parsedSelector, frameNavIndex)

	// Find the iframe element using the "before frame" selector
	iframeSelector := h.reconstructSelector(beforeFrame)

	iframeHandle, err := h.waitForSelector(apiCtx, iframeSelector, opts)
	if err != nil {
		return nil, "", fmt.Errorf("finding iframe with selector %q: %w", iframeSelector, err)
	}

	// This is a valid response from waitForSelector. It means that the element
	// was either hidden or detached.
	if iframeHandle == nil {
		return nil, "", ErrElementNotVisible
	}

	frame, err := iframeHandle.ContentFrame()
	if err != nil {
		return nil, "", fmt.Errorf("getting iframe frame: %w", err)
	}

	// Wait for selector in the iframe using the "after frame" selector
	afterFrameSelector := h.reconstructSelector(afterFrame)

	return frame, afterFrameSelector, nil
}

View on GitHub (pinned to 3fcf5388d7)