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. Returnstrue/falseto the page.prompt("Enter your name:")— text input + OK and Cancel. Returns the typed string ornull.
In Selenium, all three go through the same Alert interface returned by driver.switchTo().alert(). The methods you'll use:
alert.getText()— read the messagealert.accept()— click OKalert.dismiss()— click Cancelalert.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:
- Always wait for
alertIsPresent()before switching. Without the wait,switchTo().alert()throwsNoAlertPresentExceptionif the dialog hasn't quite appeared yet — a classic timing issue. - Read
getText()before accepting. Once accepted, the alert is gone andgetText()throws. - Always finish with
accept()ordismiss(). 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 deletedA 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 throwsNoAlertPresentException. 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. Withoutwait.until(alertIsPresent()),switchTo().alert()races the dialog and throws intermittently. Always wait. - Calling
sendKeyson a non-prompt dialog. It throwsElementNotInteractableExceptionon plainalert()andconfirm()dialogs.sendKeysis only for prompts. If you're not sure of the dialog type, checkgetText()first or wrap in a try/catch.
🎯 Practice task
Drive every dialog type. 25–35 minutes.
- Add
AlertsTestfrom this lesson to your project. Run all three tests; all should pass against expandtesting.com's JS alerts demo. - Force a
NoAlertPresentException. Remove thewait.until(ExpectedConditions.alertIsPresent())line fromshouldAcceptAlert. Run the test under CPU throttling (DevTools → Performance → 6× slowdown). Watch it fail intermittently. Add the wait back. Watch it stop. - 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 throwsNoAlertPresentException. Then handle the same modal correctly: wait for.modal.showto be visible, click.btn-close. Recognise the difference. - 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.
- Prompt with empty input. What happens if the user types nothing and clicks OK?
prompt.accept()withoutsendKeysfirst. 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 returnsnulland the page oftenString()s it). - Stretch — handle dialogs registered via
window.confirm. UseJavascriptExecutorto overridewindow.confirmbefore the action that triggers it:((JavascriptExecutor) driver).executeScript("window.confirm = () => true");. Now anyconfirm()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.