Alerts, Prompts, Modal Dialogs, and Browser Context Changes: Guided Hands-On Workflow
Use one synthetic loopback page to compare browser-native alert/confirm/prompt behavior with a DOM modal. Every interaction records the prompt or DOM state before mutation and verifies the application-visible result afterward.
Learning objectives
- Create a free, disposable local dialog fixture and pin the Selenium Python binding.
- Trigger and handle alert, confirm, and prompt dialogs with bounded prompt-presence synchronization.
- Provide prompt input to the browser prompt rather than the page DOM.
- Demonstrate an unexpected prompt blocking the next WebDriver command and preserve the exception evidence.
- Automate a DOM modal through ordinary locators/waits and compare its focus/state model with native prompts.
- Finish with a challenge that selects the correct control plane from observed state.
1. Setup and preflight
Create an empty directory such as selenium-ch09-lab.
Use Python 3.10+ and an isolated environment. The pinned mandatory
path is Selenium 4.47.0 with one supported local Chromium-family
browser.
python -m venv .venv
# Linux/macOS
. .venv/bin/activate
# Windows PowerShell alternative:
# .\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install selenium==4.47.0
python -c "import selenium; print(selenium.__version__)"
127.0.0.1. Do not substitute a real
account, production admin UI, commerce flow, MFA/CAPTCHA page, or
customer site.
2. Generate the local dialog fixture
The following example makes the Generate the local dialog fixture behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
from pathlib import Path
root = Path("site")
root.mkdir(exist_ok=True)
(root / "index.html").write_text("""<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Dialog Lab</title>
<style>
body{font-family:system-ui,sans-serif;max-width:760px;margin:2rem auto;padding:0 1rem}
button{margin:.25rem;padding:.55rem .8rem}.modal-backdrop{position:fixed;inset:0;background:#0008;display:grid;place-items:center}
.modal-card{background:white;color:#111;padding:1rem;border-radius:.6rem;min-width:280px}.modal-backdrop[hidden]{display:none}
</style></head>
<body>
<h1 id="page-title">Dialog Lab</h1>
<p id="native-result">native:none</p>
<p id="modal-result">modal:none</p>
<button id="alert-btn">Open alert</button>
<button id="confirm-btn">Open confirm</button>
<button id="prompt-btn">Open prompt</button>
<button id="dom-modal-btn">Open DOM modal</button>
<div id="dom-modal" class="modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="modal-title" hidden>
<div class="modal-card">
<h2 id="modal-title">Review synthetic change</h2>
<label>Note <input id="modal-note" value="fixture-note"></label>
<button id="modal-approve">Approve</button>
<button id="modal-cancel">Cancel</button>
</div>
</div>
<script>
const nativeResult = document.querySelector('#native-result');
const modalResult = document.querySelector('#modal-result');
document.querySelector('#alert-btn').addEventListener('click', () => {
alert('Synthetic alert: build complete');
nativeResult.textContent = 'alert:closed';
});
document.querySelector('#confirm-btn').addEventListener('click', () => {
const ok = confirm('Promote synthetic artifact?');
nativeResult.textContent = `confirm:${ok}`;
});
document.querySelector('#prompt-btn').addEventListener('click', () => {
const value = prompt('Synthetic release label?', '');
nativeResult.textContent = value === null ? 'prompt:cancelled' : `prompt:${value}`;
});
const modal = document.querySelector('#dom-modal');
document.querySelector('#dom-modal-btn').addEventListener('click', () => {
modal.hidden = false;
document.querySelector('#modal-note').focus();
});
document.querySelector('#modal-approve').addEventListener('click', () => {
modalResult.textContent = `modal:approved:${document.querySelector('#modal-note').value}`;
modal.hidden = true;
});
document.querySelector('#modal-cancel').addEventListener('click', () => {
modalResult.textContent = 'modal:cancelled';
modal.hidden = true;
});
</script></body></html>""", encoding="utf-8")
print(root.resolve())
Save the generator as make_fixture.py, run it, then
serve the generated site directory:
python make_fixture.py
python -m http.server 8774 --bind 127.0.0.1 --directory site
Keep that server terminal open. All dialog side effects are represented by visible result labels, so the browser action and application outcome can be verified separately.
3. Alert: inspect meaning, then accept
An alert has text but no business choice beyond closure. Wait for
prompt presence, assert the message, accept, and only then verify
the page state produced after alert() returns.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
URL = "http://127.0.0.1:8774/index.html"
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 5)
try:
driver.get(URL)
driver.find_element(By.ID, "alert-btn").click()
alert = wait.until(EC.alert_is_present())
print("alert text:", alert.text)
assert alert.text == "Synthetic alert: build complete"
alert.accept()
assert driver.find_element(By.ID, "native-result").text == "alert:closed"
finally:
driver.quit()
4. Confirm: accept and dismiss are different business paths
The confirm dialog returns true on accept and
false on dismiss. The test should choose the path
required by the scenario and verify the AUT outcome, not merely
prove that Selenium closed a prompt.
driver.find_element(By.ID, "confirm-btn").click()
confirm = wait.until(EC.alert_is_present())
assert confirm.text == "Promote synthetic artifact?"
confirm.dismiss()
assert driver.find_element(By.ID, "native-result").text == "confirm:false"
# Re-open to prove the other branch deliberately.
driver.find_element(By.ID, "confirm-btn").click()
confirm = wait.until(EC.alert_is_present())
confirm.accept()
assert driver.find_element(By.ID, "native-result").text == "confirm:true"
5. Prompt: input belongs to the browser prompt
send_keys() here targets the prompt's input field
through the Alert API. It is not the same as sending keys to the
active page element.
driver.find_element(By.ID, "prompt-btn").click()
prompt = wait.until(EC.alert_is_present())
assert prompt.text == "Synthetic release label?"
prompt.send_keys("rc-42")
prompt.accept()
assert driver.find_element(By.ID, "native-result").text == "prompt:rc-42"
6. Unexpected prompt: preserve the first failure
For this controlled demonstration, create a separate session with
unhandled_prompt_behavior="ignore". That policy leaves
the prompt open and reports an error when an unrelated command
encounters it.
from selenium import webdriver
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.unhandled_prompt_behavior = "ignore"
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 5)
try:
driver.get("http://127.0.0.1:8774/index.html")
print("returned policy:", driver.capabilities.get("unhandledPromptBehavior"))
driver.find_element(By.ID, "confirm-btn").click()
try:
# This command is unrelated to prompt handling and should be blocked.
print(driver.title)
raise AssertionError("Expected an unexpected-alert failure")
except UnexpectedAlertPresentException as exc:
print("blocked command:", type(exc).__name__)
print("reported alert text:", exc.alert_text)
confirm = wait.until(EC.alert_is_present())
assert confirm.text == "Promote synthetic artifact?"
confirm.dismiss()
assert driver.find_element(By.ID, "native-result").text == "confirm:false"
finally:
driver.quit()
The repair is not “retry title.” The repair is to recognize the prompt as the blocking state, assert its meaning, resolve it intentionally, and then continue from a verified post-dialog state.
7. DOM modal: ordinary element semantics
The DOM modal has a role, title, input, buttons, visibility state, and focus. No Alert API is involved.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 5)
try:
driver.get("http://127.0.0.1:8774/index.html")
driver.find_element(By.ID, "dom-modal-btn").click()
modal = wait.until(EC.visibility_of_element_located((By.ID, "dom-modal")))
assert modal.get_attribute("role") == "dialog"
assert modal.find_element(By.ID, "modal-title").text == "Review synthetic change"
active = driver.switch_to.active_element
assert active.get_attribute("id") == "modal-note"
note = modal.find_element(By.ID, "modal-note")
note.clear()
note.send_keys("checkpoint-42")
modal.find_element(By.ID, "modal-approve").click()
wait.until(EC.invisibility_of_element_located((By.ID, "dom-modal")))
assert driver.find_element(By.ID, "modal-result").text == "modal:approved:checkpoint-42"
finally:
driver.quit()
8. Before/after state: prove what each action changed
The following table organizes the key choices and evidence for Before/after state: prove what each action changed. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.
| Action | Before | WebDriver control | After / evidence |
|---|---|---|---|
| open alert | no native prompt | click DOM button | prompt text visible through Alert API; normal commands blocked until resolution |
| dismiss confirm | confirm open | alert.dismiss() |
AUT writes confirm:false |
| accept prompt with text | prompt open | send_keys + accept |
AUT writes prompt:rc-42 |
| open DOM modal | modal hidden | ordinary button click | role=dialog visible, modal input focused |
| approve DOM modal | modal visible | ordinary WebElement click | modal hidden, application result updated |
9. Challenge: choose the control from the state model
Add a second DOM button labeled “Delete synthetic record” that opens the existing DOM modal with different title text. Your task is to decide—without copying a sequence—whether to use the Alert API or normal DOM controls. State the evidence that proves your classification before interacting.
A strong answer checks for the expected DOM role/title and uses
WebElement semantics. It does not call
switch_to.alert just because the UI blocks the
background.
10. Cleanup
Stop the local server with Ctrl+C. Preserve any text output/screenshots required for review, then remove only the disposable lab directory. No external account, browser-cloud session, or production data was created.
11. Summary and next step
You have now exercised both control planes and observed the causal chain from trigger → prompt/modal state → explicit decision → application-visible result. Lesson 3 turns these mechanics into design choices for maintainable suites.
Knowledge check
Why does alert.send_keys("rc-42") not type into
the page?
The Alert API sends text to a browser-native prompt input; the page DOM is blocked while the prompt is open.
What is the main assertion after dismissing a confirm?
The application-visible business outcome, such as
confirm:false, not merely the absence of the
prompt.
Why use a separate ignore-policy session for the
unexpected-prompt demo?
It confines failure injection to a disposable scenario and keeps the prompt open so the learner can preserve the error and inspect the same blocking state.
A visible overlay has role="dialog" and DOM
buttons. Which API should control it?
Normal element locators/waits/interactions because it is a DOM modal, not a browser-native user prompt.
Why is a fixed sleep unnecessary before
switch_to.alert?
A bounded condition such as
EC.alert_is_present() waits for the exact
observable state transition the test needs.
Official references and version notes
- Selenium 4.47 release notes — stable baseline pinned for this chapter.
- Selenium downloads — current stable client and Selenium Server/Grid versions.
- JavaScript alerts, prompts, and confirmations — current Selenium interaction examples.
- Selenium Python Alert API — text, accept, dismiss, and prompt input.
-
Selenium Python Options API
—
unhandled_prompt_behaviorand its 4.47.0 default. -
Expected Conditions
—
alert_is_present()for bounded prompt synchronization. -
W3C WebDriver Working Draft — User prompts
— prompt handling and
unexpected alert opensemantics.
Version-sensitive behavior was rechecked against current primary
documentation on 2026-08-28. Mandatory examples pin Selenium
Python 4.47.0, require Python 3.10+, use a supported local
Chromium-family browser with Selenium Manager for normal local
driver resolution, and target only loopback fixtures. Selenium
Python 4.47.0 documents
dismiss and notify as the default
unhandled_prompt_behavior; the chapter uses
ignore only in a controlled failure-injection session
so the learner can observe and then explicitly resolve the prompt.
Classic WebDriver is sufficient for the mandatory chapter path;
BiDi/CDP are not required to handle these dialog semantics.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.