Checkpoint Lab — Alerts, Prompts, Modal Dialogs, and Browser Context Changes
Checkpoint the chapter by building a disposable dialog workflow that includes native confirm/prompt decisions, a DOM modal, an intentionally unhandled prompt, evidence preservation, explicit repair, and a deterministic final browser/application state.
Learning objectives
- Build the chapter fixture from an empty directory with exact pinned assumptions and loopback-only scope.
- Predict native-prompt, DOM-modal, focus, and AUT-result transitions before performing them.
- Execute confirm, prompt, and DOM-modal paths with explicit text/state assertions.
- Inject an unhandled-prompt failure using a reversible session capability and preserve first-failure evidence.
- Repair the failure without blanket retries, fixed sleeps, JavaScript bypass, or browser restart.
- Produce an evidence packet and prove a known final state before quitting the session.
1. Checkpoint scenario and safety boundary
You are validating a synthetic release workflow. A confirm decides whether to promote a fake artifact, a prompt captures a fake release label, and a DOM modal records a review note. Then a second disposable session intentionally leaves a browser prompt unhandled so you can observe the configured failure behavior and recover explicitly.
2. Setup and preflight
Create selenium-ch09-checkpoint, then create and
activate an isolated Python environment.
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', selenium.__version__)"
Preflight checklist:
- Python 3.10+.
- Selenium Python exactly 4.47.0.
- One supported local Chromium-family browser available.
- Selenium Manager allowed to resolve the normal local driver path.
- Ports 8775 available on loopback.
- No real browser profile or account session reused.
3. Generate and serve the checkpoint fixture
The following example makes the Generate and serve the checkpoint 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())
The following example makes the Generate and serve the checkpoint fixture behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
python make_fixture.py
python -m http.server 8775 --bind 127.0.0.1 --directory site
The page itself is unchanged from Lesson 2; only the port differs so the checkpoint is self-contained.
4. Predict state transitions before running
Write these predictions into predictions.txt before
automation:
Prediction A — confirm dismiss
- Browser-native confirm becomes active; DOM modal state does not change.
- After dismiss, prompt is gone and #native-result becomes confirm:false.
Prediction B — prompt accept with text
- Browser-native prompt becomes active and blocks ordinary page commands.
- After send_keys('release-9') + accept, #native-result becomes prompt:release-9.
Prediction C — DOM modal approve
- No browser-native prompt exists.
- #dom-modal becomes visible, #modal-note owns focus, then the modal hides and #modal-result records the note.
Prediction D — unhandled prompt with policy=ignore
- An unrelated command raises UnexpectedAlertPresentException.
- The prompt remains active until explicitly accepted/dismissed.
5. Run the explicit native + DOM flow
Save as checkpoint_flow.py. This session uses the
documented default prompt policy and handles all expected prompts
explicitly.
from pathlib import Path
from urllib.parse import urlparse
import json
import selenium
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:8775/index.html"
if (urlparse(URL).hostname or "") not in {"127.0.0.1", "localhost", "::1"}:
raise RuntimeError("Checkpoint requires loopback AUT")
Path("evidence").mkdir(exist_ok=True)
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 5)
evidence = {"selenium": selenium.__version__}
try:
driver.get(URL)
evidence["session_id"] = driver.session_id
evidence["browser"] = driver.capabilities.get("browserName")
evidence["browser_version"] = driver.capabilities.get("browserVersion")
evidence["prompt_policy"] = driver.capabilities.get("unhandledPromptBehavior")
evidence["initial_url"] = driver.current_url
evidence["initial_title"] = driver.title
# 1) Confirm: dismiss deliberately.
driver.find_element(By.ID, "confirm-btn").click()
confirm = wait.until(EC.alert_is_present())
evidence["confirm_text"] = confirm.text
assert confirm.text == "Promote synthetic artifact?"
confirm.dismiss()
assert driver.find_element(By.ID, "native-result").text == "confirm:false"
# 2) Prompt: supply synthetic release label deliberately.
driver.find_element(By.ID, "prompt-btn").click()
prompt = wait.until(EC.alert_is_present())
evidence["prompt_text"] = prompt.text
assert prompt.text == "Synthetic release label?"
prompt.send_keys("release-9")
prompt.accept()
assert driver.find_element(By.ID, "native-result").text == "prompt:release-9"
# 3) DOM modal: no Alert API.
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"
evidence["modal_title"] = modal.find_element(By.ID, "modal-title").text
assert evidence["modal_title"] == "Review synthetic change"
active = driver.switch_to.active_element
evidence["modal_focus"] = active.get_attribute("id")
assert evidence["modal_focus"] == "modal-note"
note = modal.find_element(By.ID, "modal-note")
note.clear()
note.send_keys("review-ok")
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:review-ok"
driver.save_screenshot("evidence/explicit-flow-final.png")
evidence["final_native"] = driver.find_element(By.ID, "native-result").text
evidence["final_modal"] = driver.find_element(By.ID, "modal-result").text
evidence["final_url"] = driver.current_url
evidence["final_title"] = driver.title
finally:
Path("evidence/explicit-flow.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8")
driver.quit()
6. Inject one reversible unhandled-prompt failure
Save this as checkpoint_failure.py. The only injected
change is the new session's prompt policy. No browser files,
profiles, network settings, or AUT data are modified.
from pathlib import Path
from urllib.parse import urlparse
import json
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
URL = "http://127.0.0.1:8775/index.html"
if (urlparse(URL).hostname or "") not in {"127.0.0.1", "localhost", "::1"}:
raise RuntimeError("Checkpoint requires loopback AUT")
Path("evidence").mkdir(exist_ok=True)
options = Options()
options.unhandled_prompt_behavior = "ignore"
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 5)
record = {}
try:
driver.get(URL)
record["session_id"] = driver.session_id
record["prompt_policy"] = driver.capabilities.get("unhandledPromptBehavior")
driver.find_element(By.ID, "alert-btn").click()
try:
_ = driver.title # intentionally unrelated command while prompt is open
raise AssertionError("Expected UnexpectedAlertPresentException")
except UnexpectedAlertPresentException as exc:
record["first_failure"] = type(exc).__name__
record["reported_alert_text"] = exc.alert_text
record["exception_message"] = str(exc)
# Least-destructive correction: inspect the same live prompt and resolve it.
alert = wait.until(EC.alert_is_present())
record["live_prompt_text"] = alert.text
assert alert.text == "Synthetic alert: build complete"
alert.accept()
# Deterministic post-dialog state verification.
assert driver.title == "Dialog Lab"
assert driver.current_url == URL
assert driver.find_element(By.ID, "native-result").text == "alert:closed"
assert driver.find_element(By.ID, "dom-modal").get_property("hidden") is True
record["final_title"] = driver.title
record["final_url"] = driver.current_url
record["final_native"] = driver.find_element(By.ID, "native-result").text
driver.save_screenshot("evidence/recovered-final.png")
finally:
Path("evidence/unhandled-prompt.json").write_text(json.dumps(record, indent=2), encoding="utf-8")
driver.quit()
7. Interpret the failure instead of hiding it
The important evidence sequence is:
- policy returned as
ignore; -
an unrelated title command fails with
UnexpectedAlertPresentException; - the reported/live prompt text identifies the blocker;
- explicit Alert handling resolves the same state;
- title, URL, DOM result, and modal-hidden state prove deterministic recovery.
A blanket retry or browser restart would have destroyed this causal chain.
8. Required evidence packet
Keep these files together:
The following example makes the Required evidence packet behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
predictions.txt
evidence/explicit-flow.json
evidence/explicit-flow-final.png
evidence/unhandled-prompt.json
evidence/recovered-final.png
Record in review notes:
- Python version
- Selenium 4.47.0
- browser name/version and returned prompt policy
- loopback fixture URL
- expected confirm/prompt/modal texts
- first-failure exception and alert text
- final URL/title/native-result/modal-result
- confirmation that driver.quit() ran for both sessions
9. Verification checklist
-
Exactly the expected confirm text was dismissed and AUT reported
confirm:false. -
Exactly the expected prompt text received
release-9and AUT reported it. - The DOM modal was found as DOM state, had expected title/focus, and closed after ordinary WebElement interaction.
-
The injected unhandled prompt produced
UnexpectedAlertPresentExceptionunder returnedignorepolicy. - The same live prompt was explicitly asserted and accepted.
- Final URL/title and result labels are deterministic.
- No fixed sleeps, JavaScript force-clicks, browser restarts, TLS changes, credentials, or production targets were used.
10. Cleanup and rollback
Stop the loopback HTTP server with Ctrl+C. Preserve the evidence packet if required, then remove only the checkpoint directory. The injected behavior was session-local, so quitting the diagnostic session is the rollback.
cd ..
rm -rf selenium-ch09-checkpoint # Linux/macOS
# Windows PowerShell alternative:
# Remove-Item -Recurse -Force .\selenium-ch09-checkpoint
11. What Chapter 09 adds to the production operating model
The operating model now distinguishes browser-native prompt state from DOM modal state, records the negotiated prompt policy, asserts dialog meaning before business decisions, preserves unexpected-prompt evidence, and verifies deterministic post-dialog context/application state. This removes a common source of “mysterious modal flakiness” from CI.
Chapter 10 moves deeper into modern DOM boundaries: Shadow DOM, Web Components, dynamic rerendering, and element-reference lifetime. The same discipline applies—identify the actual browser/document boundary before choosing an API.
12. Summary
The checkpoint demonstrated that dialogs are not one generic Selenium concept. Native prompts and DOM modals have different owners, control planes, evidence, and failure semantics. Reliability comes from classification, explicit business intent, bounded synchronization, and verified recovery.
Knowledge check
Why is ignore useful in the checkpoint
failure-injection session?
It leaves the unexpected prompt open while notifying the blocked command, allowing the learner to preserve the failure and inspect/resolve the same prompt explicitly.
What proves the DOM modal was not a browser-native alert?
It was found as a DOM element with role="dialog",
had normal child inputs/buttons and focus, and was controlled
entirely with WebElement/wait APIs.
Why do the checkpoint scripts assert prompt text before deciding?
Prompt text is part of the decision-point identity; accepting or dismissing an unexpected prompt could exercise the wrong or unsafe application path.
What is the correct recovery after the intentionally blocked title command?
Preserve the exception, inspect/assert the live prompt, resolve it deliberately, then verify URL/title and AUT/DOM state before continuing.
What production topic comes next in Chapter 10?
Shadow DOM, Web Components, dynamic DOM rerendering, and element-reference lifetime across modern front-end boundaries.
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.