Handling Alerts, Confirms, and Prompts

7 min read

JavaScript's alert(...), confirm(...), and prompt(...) open native browser dialogs that live outside the DOM. You can't find them with By.id or By.cssSelector — the elements simply aren't there. Selenium gives you a separate API for them: driver.switchTo().alert(). This lesson covers all three dialog types, the wait pattern for handling them safely, and why most modern web apps don't use these dialogs at all (and what to do instead).

The three dialog types

Each is triggered by one JS function in the page code:

  • alert("...") — one OK button. Read the message, click OK.
  • confirm("Are you sure?") — OK and Cancel. Returns true/false to the page.
  • prompt("Enter your name:") — text input + OK and Cancel. Returns the typed string or null.

In Selenium, all three go through the same Alert interface returned by driver.switchTo().alert(). The methods you'll use:

  • alert.getText() — read the message
  • alert.accept() — click OK
  • alert.dismiss() — click Cancel
  • alert.sendKeys("text") — type into a prompt before accepting

Handling a plain alert

import org.openqa.selenium.Alert;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
 
driver.findElement(By.id("trigger-alert")).click();
 
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
 
Alert alert = driver.switchTo().alert();
String message = alert.getText();
System.out.println("Alert said: " + message);
 
alert.accept();

Three habits to bake in:

  1. Always wait for alertIsPresent() before switching. Without the wait, switchTo().alert() throws NoAlertPresentException if the dialog hasn't quite appeared yet — a classic timing issue.
  2. Read getText() before accepting. Once accepted, the alert is gone and getText() throws.
  3. Always finish with accept() or dismiss(). A leftover alert blocks every subsequent interaction with the page until it's handled.

Handling a confirm

The confirm dialog has both buttons. Whether you accept or dismiss usually depends on the test's intent:

driver.findElement(By.id("delete-account")).click();
 
wait.until(ExpectedConditions.alertIsPresent());
Alert confirm = driver.switchTo().alert();
 
Assert.assertEquals(
    confirm.getText(),
    "Are you sure you want to delete your account? This cannot be undone.",
    "Confirm message should warn the user"
);
 
// Test path 1: cancel
confirm.dismiss();   // user backs out — account NOT deleted
 
// Test path 2: confirm
// confirm.accept();   // user proceeds — account deleted

A pair of test methods — one that dismisses and asserts the account still exists, one that accepts and asserts it's gone — is the standard coverage for any confirm flow.

Handling a prompt

Prompts are rare in modern web apps but show up in older systems and JavaScript exercises:

driver.findElement(By.id("rename-button")).click();
 
wait.until(ExpectedConditions.alertIsPresent());
Alert prompt = driver.switchTo().alert();
 
prompt.sendKeys("My New Name");
prompt.accept();
 
// Verify the page picked up the new name
WebElement nameDisplay = wait.until(
    ExpectedConditions.textToBePresentInElementLocated(
        By.id("display-name"), "My New Name"
    )
);

sendKeys on an Alert is only valid on prompts. Calling it on an alert() or confirm() dialog throws ElementNotInteractableException.

The handler flow

Native dialogs vs in-DOM modals — read the page first

The single most common confusion: most modern apps don't use native alert/confirm/prompt. They use in-DOM modals built from <div> elements styled to look like dialogs (Bootstrap modals, Material UI dialogs, Headless UI, custom implementations).

For in-DOM modals, switchTo().alert() does nothing useful — the modal isn't an alert, it's just markup. You handle them like any other page elements:

// IN-DOM MODAL — find and interact like normal
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".modal")));
driver.findElement(By.cssSelector(".modal .confirm-btn")).click();
 
// NATIVE BROWSER DIALOG — needs switchTo().alert()
wait.until(ExpectedConditions.alertIsPresent());
driver.switchTo().alert().accept();

How to tell which is which: open DevTools and inspect. If the dialog appears in the Elements panel as a <div>, it's in-DOM. If you can't see it in the DOM at all, it's a native browser dialog.

Modern apps lean heavily toward in-DOM modals because they're styleable, customisable, and don't block JavaScript execution the way native dialogs do. Native alert/confirm/prompt survive mostly in legacy admin tools, simple CMSes, and JavaScript-tutorial pages.

A complete confirm test

package com.mycompany.tests.tests;
 
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
 
import java.time.Duration;
 
public class AlertsTest {
 
    WebDriver driver;
    WebDriverWait wait;
 
    @BeforeMethod
    public void setup() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        // A practice page that triggers all three dialog types
        driver.get("https://practice.expandtesting.com/javascript-alerts");
    }
 
    @Test
    public void shouldAcceptAlert() {
        driver.findElement(By.cssSelector("button[onclick='jsAlert()']")).click();
 
        wait.until(ExpectedConditions.alertIsPresent());
        Alert alert = driver.switchTo().alert();
        Assert.assertEquals(alert.getText(), "I am a JS Alert");
        alert.accept();
 
        Assert.assertEquals(
            driver.findElement(By.id("result")).getText(),
            "You successfully clicked an alert"
        );
    }
 
    @Test
    public void shouldDismissConfirm() {
        driver.findElement(By.cssSelector("button[onclick='jsConfirm()']")).click();
 
        wait.until(ExpectedConditions.alertIsPresent());
        Alert confirm = driver.switchTo().alert();
        Assert.assertEquals(confirm.getText(), "I am a JS Confirm");
        confirm.dismiss();
 
        Assert.assertEquals(
            driver.findElement(By.id("result")).getText(),
            "You clicked: Cancel"
        );
    }
 
    @Test
    public void shouldTypeIntoPrompt() {
        driver.findElement(By.cssSelector("button[onclick='jsPrompt()']")).click();
 
        wait.until(ExpectedConditions.alertIsPresent());
        Alert prompt = driver.switchTo().alert();
        prompt.sendKeys("Selenium QA");
        prompt.accept();
 
        Assert.assertEquals(
            driver.findElement(By.id("result")).getText(),
            "You entered: Selenium QA"
        );
    }
 
    @AfterMethod
    public void teardown() {
        if (driver != null) driver.quit();
    }
}

Three tests, every dialog type, every Alert method.

How Cypress and Playwright handle this

// Cypress — auto-accepts alerts/confirms by default; opt in to assertions
cy.on("window:confirm", (text) => {
  expect(text).to.equal("Are you sure?");
  return false;   // dismiss
});
 
// Playwright — register a handler before triggering
page.once("dialog", async dialog => {
  expect(dialog.message()).toContain("Are you sure?");
  await dialog.accept();
});

Both modern frameworks handle the dialog asynchronously — you set up a handler, then trigger the action. Selenium's flow is more procedural: trigger, wait, switch, act. Neither approach is wrong; they fit different programming models.

The Selenium tool entry lists every Alert method, and the WebDriverWait expected conditions reference covers the wait side.

⚠️ Common mistakes

  • Calling switchTo().alert() on an in-DOM modal. It throws NoAlertPresentException. The modal is a <div>, not a native dialog. Inspect with DevTools first; if the dialog appears in the DOM, treat it like any other element.
  • Forgetting to wait before switching. A click() that triggers an alert is asynchronous — the alert renders a beat later. Without wait.until(alertIsPresent()), switchTo().alert() races the dialog and throws intermittently. Always wait.
  • Calling sendKeys on a non-prompt dialog. It throws ElementNotInteractableException on plain alert() and confirm() dialogs. sendKeys is only for prompts. If you're not sure of the dialog type, check getText() first or wrap in a try/catch.

🎯 Practice task

Drive every dialog type. 25–35 minutes.

  1. Add AlertsTest from this lesson to your project. Run all three tests; all should pass against expandtesting.com's JS alerts demo.
  2. Force a NoAlertPresentException. Remove the wait.until(ExpectedConditions.alertIsPresent()) line from shouldAcceptAlert. Run the test under CPU throttling (DevTools → Performance → 6× slowdown). Watch it fail intermittently. Add the wait back. Watch it stop.
  3. Compare with an in-DOM modal. Visit any site with a Bootstrap modal (https://getbootstrap.com/docs/5.3/components/modal/ has demos). Try driver.switchTo().alert() — it throws NoAlertPresentException. Then handle the same modal correctly: wait for .modal.show to be visible, click .btn-close. Recognise the difference.
  4. Confirm — both branches. Write two tests for the confirm scenario: one that accepts and asserts the consequence, one that dismisses and asserts the page didn't change. This is the canonical pair of tests for any destructive action.
  5. Prompt with empty input. What happens if the user types nothing and clicks OK? prompt.accept() without sendKeys first. Run the test; assert the displayed value is the empty string. Now what if they click Cancel — prompt.dismiss(). Assert the value is "null" (literal text, since JavaScript returns null and the page often String()s it).
  6. Stretch — handle dialogs registered via window.confirm. Use JavascriptExecutor to override window.confirm before the action that triggers it: ((JavascriptExecutor) driver).executeScript("window.confirm = () => true");. Now any confirm() returns true automatically, and you don't need to switch dialogs at all. This is sometimes the cleanest test setup for legacy pages.

Next lesson: iframes — the other place "the element you're looking for isn't in the DOM you're searching." Selenium can't find anything inside an iframe until you explicitly switch to it, and switching back is just as easy to forget.

// tip to track lessons you complete and pick up where you left off across devices.