intermediate21 min

Frames, Windows and Tabs, and the Actions API

Explicitly switching WebDriver's focus between frames and windows — unlike a browser tab a human just looks at — plus the Actions API for interactions a plain click can't express.

What you'll learn

  • Switch WebDriver's focus into and out of an iframe correctly
  • Handle a new window or tab opened from an existing one, including switching back
  • Use the Actions API to build a multi-step interaction like hover-then-click or drag-and-drop

Prerequisites

Explanation

WebDriver has a single, explicit focus at any moment — the main page, or one specific frame — and findElement only ever searches within whatever currently has focus. driver.switchTo().frame("payment-frame") (by name, index, or a located WebElement) moves focus into that iframe's own document; elements inside it become findable only after this switch. Critically, unlike a native alert (previous lesson), switching into a frame requires an explicit switch back: driver.switchTo().defaultContent() returns focus to the main page — forgetting this is a common, real bug where code that should target the main page silently fails to find anything, because WebDriver's focus is still inside the frame from an earlier interaction.

A new window or tab (opened by a link with target="_blank" or a window.open() call) does not automatically become WebDriver's focus — the original window remains focused until you explicitly switch: String originalWindow = driver.getWindowHandle(); // ... trigger the action that opens a new window ... for (String handle : driver.getWindowHandles()) { if (!handle.equals(originalWindow)) driver.switchTo().window(handle); } is the standard pattern — capture the original handle before triggering the new window, then iterate getWindowHandles() (a Set<String>, unordered) to find the one that's new. This has a genuine race-condition risk similar to popup-capture in other automation tools: the new window may not exist yet at the instant getWindowHandles() is first called, which is exactly why this pattern is often combined with a wait for the handle count to actually increase before searching for the new one.

The Actions API (new Actions(driver).moveToElement(menuItem).click(subMenuItem).perform()) builds a sequence of low-level input events — mouse moves, clicks, key presses — composed together and executed as one coordinated interaction via a single .perform() call. This is the correct, honest tool for interactions a plain .click() genuinely cannot express: hovering to reveal a dropdown menu before clicking an item inside it, drag-and-drop (.dragAndDrop(source, target)), or a multi-key combination (.keyDown(Keys.SHIFT).click(element).keyUp(Keys.SHIFT)). Calling .perform() is what actually executes the whole built-up sequence — building an Actions chain without eventually calling .perform() does nothing at all, a real, easy-to-miss mistake.

Example

Modeling the explicit-focus model for frames/windows and an Actions-style chained sequence, as data.

class FakeDriverFocus {
  constructor() { this.focus = "main"; this.windowHandles = new Set(["main"]); }
  switchToFrame(frameId) { this.focus = "frame:" + frameId; }
  switchToDefaultContent() { this.focus = "main"; }
  openNewWindow(handle) { this.windowHandles.add(handle); } // does NOT change focus automatically
  switchToWindow(handle) { this.focus = handle; }
}

const driver = new FakeDriverFocus();
driver.switchToFrame("payment");
console.log(driver.focus); // "frame:payment"
driver.switchToDefaultContent(); // MUST switch back explicitly
console.log(driver.focus); // "main"

driver.openNewWindow("popup-1");
console.log(driver.focus); // still "main" -- opening a window does not switch focus automatically
driver.switchToWindow("popup-1");
console.log(driver.focus); // "popup-1" -- now genuinely focused there

// Actions-style: a sequence of steps, only executed together when "performed."
function buildAndPerform(steps) {
  return steps.map((s) => "executed: " + s).join(" -> "); // models .perform() running the whole chain
}
console.log(buildAndPerform(["moveTo(menu)", "click(subMenuItem)"]));

Try it yourself

Open a second new window without switching to it, and confirm focus correctly stays on 'main'.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write findNewWindowHandle(handlesBefore, handlesAfter) modeling the real 'find the new window' pattern: handlesBefore and handlesAfter are arrays of handle strings. Return the handle present in handlesAfter but NOT in handlesBefore, or null if none (or more than one -- an ambiguous case) is found.

Checks: finds the single new window handle · returns null when no new window appeared · returns null when the result is ambiguous (multiple new handles)

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write buildActionsChain(steps) that returns steps joined with ' -> ' ONLY if the last step is 'perform' (modeling that a chain does nothing until .perform() is actually called); otherwise return 'NOT EXECUTED (missing perform())'.

Checks: a chain ending in perform() executes correctly · a chain missing perform() does not execute, modeling the real bug this causes

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Guided local lab

Automate a Realistic Multi-Page Workflow Using Explicit Waits

Runs on your computer
This lab runs on your own computer, in your own terminal and editor — not in your browser. VisaSparkSchools does not execute, run, or verify these commands for you. Follow the verification steps yourself to confirm your result.

Build a real Selenium test automating a multi-step workflow across at least two real pages, combining explicit waits, stable locators, and correct window/frame handling if applicable to your chosen site.

Required tools

  • JDK (21 LTS or newer)
  • Apache Maven (3.9+)
  • Selenium WebDriver (4.x)
  • A terminal (any)

Setup

  1. Continue from the selenium-learning-lab project created in this course's first guided local lab.
  2. Pick a real, publicly-accessible site with at least a two-step flow you're comfortable automating for practice (a search-then-click-a-result flow works well and is broadly available).

Project structure

selenium-learning-lab/
  pom.xml
  src/
    test/java/com/visaspark/selenium/
      MultiPageWorkflowTest.java

Starter files

src/test/java/com/visaspark/selenium/MultiPageWorkflowTest.java

package com.visaspark.selenium;

import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;

class MultiPageWorkflowTest {
    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setup() {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    @AfterEach
    void teardown() {
        driver.quit();
    }

    @Test
    void searchThenNavigateToAResult() {
        // TODO: navigate to your chosen site's starting page
        // TODO: use an EXPLICIT wait (wait.until(ExpectedConditions...)) before interacting
        //       with the search input -- do not use Thread.sleep
        // TODO: perform a search action
        // TODO: wait for and click a real result link
        // TODO: assert something about the resulting second page (title, a visible heading, or the URL)
    }
}

Requirements

  • The test uses at least one explicit WebDriverWait with a specific ExpectedConditions call — no Thread.sleep anywhere.
  • The test genuinely navigates across at least two distinct real pages/URLs.
  • Locators use id, name, or cssSelector targeting meaningful attributes — no deep structural XPath.
  • @BeforeEach/@AfterEach correctly create and tear down a fresh driver per test.
  • The final assertion verifies something real and specific about the second page reached.

Commands to run

  • Run the workflow test

    mvn test -Dtest=MultiPageWorkflowTest

Expected behavior

Running the test launches a real browser, performs the multi-step workflow with each step correctly waiting for real readiness (never a fixed sleep), navigates across at least two real pages, and passes with a specific, correct final assertion.

Verify it yourself

  • mvn test -Dtest=MultiPageWorkflowTest

    Expected: BUILD SUCCESS; the browser is observed navigating across at least two distinct real pages during the run

  • grep -n Thread.sleep src/test/java/com/visaspark/selenium/MultiPageWorkflowTest.java

    Expected: No output — confirms no fixed sleep was used anywhere in the test

Troubleshooting

  • `TimeoutException: Expected condition failed`Confirm the ExpectedConditions call matches an element that genuinely appears on the real page you're testing against — inspect the real page's HTML to confirm your locator strategy is correct.
  • The test clicks the wrong result or a stale elementRe-locate elements freshly after any page transition rather than reusing a WebElement reference obtained on the previous page — see this course's element-location lesson on staleness.
  • Test passes locally but is flaky in repeated runsConfirm every interaction is preceded by an appropriate explicit wait (elementToBeClickable for things you click, visibilityOfElementLocated for things you just need to see) — a passing-but-flaky test often has one un-waited-for interaction.

Stuck? Get a hint.

Extension challenge

Extend the test to open a link that opens in a new tab (target="_blank"), switch WebDriver's focus to that new window using the getWindowHandles() diffing pattern from this lesson, assert something on it, then explicitly switch back to the original window handle.

When you've verified this locally, use the "Mark lesson complete" button below to record your progress.

Common mistakes

  • Forgetting to switch back to default content after finishing work inside a frame -- subsequent findElement calls intended for the main page silently fail to find anything, because WebDriver's focus is still inside the frame.
  • Assuming a newly-opened window automatically becomes WebDriver's focus -- it doesn't; the original window stays focused until you explicitly call switchTo().window(handle) with the new handle.
  • Building an Actions chain (moveToElement/click/keyDown/etc.) and forgetting the final .perform() call -- without it, none of the built-up sequence actually executes at all.

Knowledge check

Knowledge check

1. After finishing an interaction inside an iframe via switchTo().frame(...), what must happen before interacting with the main page again?
2. Does WebDriver automatically switch focus to a newly-opened browser window or tab?
3. What happens if an Actions chain is built (moveToElement, click, etc.) but .perform() is never called?

Takeaway

WebDriver's focus is explicit and singular — switching into a frame or a new window requires an explicit switch, and (unlike alerts) switching out of a frame requires an explicit switch back too; the Actions API builds a sequence that does nothing at all until .perform() actually executes it.

Summary

switchTo().frame(...) moves focus into an iframe; switchTo().defaultContent() must explicitly return it. A new window/tab doesn't auto-focus; find its handle via getWindowHandles() diffing and switchTo().window(handle). The Actions API chains low-level interactions (hover, drag-and-drop, key combinations) that only execute when .perform() is called.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.