Checkpoint Lab — WebElement State, Interactions, Forms, and Validation
Automate a multi-field local form with deterministic validation, predict state transitions, deliberately trigger intercepted and stale interactions, diagnose both without JavaScript bypass, and verify final application state plus cleanup evidence.
Learning objectives
- Build and automate a multi-field loopback form with client-side validation and deterministic application state.
- Predict at least two DOM/control/application state changes before submission and verify them independently afterward.
- Trigger a click-interception failure with a disposable overlay and repair it through normal UI state, not JavaScript bypass.
- Trigger a stale-element failure through a controlled rerender and repair it by reacquiring the current element reference.
- Produce an evidence packet with versions, session IDs, capabilities, screenshots, state snapshots, exceptions, and final result.
- Document verification, cleanup, reproducibility assumptions, and the bridge to Chapter 06 synchronization.
1. Checkpoint acceptance contract
Build one disposable profile form and prove the complete interaction chain: read state, predict transitions, perform native user-like input, observe validation, intentionally reproduce an intercepted click and a stale reference, repair each at the correct layer, submit successfully, capture evidence, and clean up.
- No external website, real identity, payment, email, or production account is used.
-
The AUT and browser run locally; the HTTP server binds only to
127.0.0.1. - Selenium Python is pinned to 4.47.0; record actual browser/driver capability values at runtime.
- JavaScript exists inside the disposable fixture to model application behavior, but the Selenium test does not use JavaScript to bypass interaction failures.
- At least two predicted state changes are written down before execution and checked afterward.
- The evidence packet includes versions, session/capabilities, screenshots, exception names, control state, and final application result.
2. Setup and preflight
The following example makes the Setup and preflight behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
mkdir selenium-ch05-checkpoint
cd selenium-ch05-checkpoint
python -m venv .venv
# Linux/macOS:
. .venv/bin/activate
# Windows PowerShell:
# .\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install "selenium==4.47.0"
mkdir site evidence
The following example makes the Setup and preflight behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
python -c "import selenium; print('selenium', selenium.__version__)"
python --version
3. Create the checkpoint fixture
The following example makes the Create the checkpoint fixture behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Chapter 05 Checkpoint</title>
<style>
body{font:16px system-ui;max-width:780px;margin:40px auto;padding:0 20px}label{display:block;margin:12px 0 5px}
#overlay{position:fixed;inset:0;background:#111b;z-index:20;display:grid;place-items:center;color:white}
#overlay[hidden]{display:none}#status{margin-top:16px;padding:10px;border:1px solid #777}
</style></head>
<body data-build="ch05-checkpoint-v1">
<div id="overlay"><div><p>Training notice</p><button id="dismiss">Continue</button></div></div>
<h1>Automation profile</h1>
<form id="form" novalidate>
<label>Name <input id="name" required value="Seed User"></label>
<label>Email <input id="email" type="email" required value="seed@example.invalid"></label>
<label>Role <select id="role"><option value="viewer">Viewer</option><option value="developer">Developer</option><option value="operator">Operator</option></select></label>
<label><input id="newsletter" type="checkbox"> Newsletter</label>
<label><input name="plan" type="radio" value="basic" checked> Basic</label>
<label><input name="plan" type="radio" value="team"> Team</label>
<label><input id="terms" type="checkbox" required> Accept lab terms</label>
<button id="rerender" type="button">Refresh name control</button>
<button id="submit" type="submit">Save</button>
</form>
<div id="status" role="status" data-state="idle">Idle</div><pre id="result">{}</pre>
<script>
const q=(s)=>document.querySelector(s), form=q('#form'), status=q('#status');
q('#dismiss').onclick=()=>q('#overlay').hidden=true;
q('#rerender').onclick=()=>{ const old=q('#name'); old.outerHTML=`<input id="name" required value="${old.value}">`; };
form.addEventListener('submit',(e)=>{
e.preventDefault();
if(!form.checkValidity()){status.dataset.state='invalid';status.textContent='Validation failed';return;}
const data={name:q('#name').value,email:q('#email').value,role:q('#role').value,newsletter:q('#newsletter').checked,plan:q('input[name="plan"]:checked').value,terms:q('#terms').checked};
q('#result').textContent=JSON.stringify(data,null,2);status.dataset.state='saved';status.textContent='Profile saved';
});
</script></body></html>
Save it as site/index.html, then start the loopback
server in a separate terminal:
python -m http.server 8765 --bind 127.0.0.1 --directory site
4. Write predictions before running Selenium
Record these in evidence/predictions.txt before
execution. The exact wording can vary, but the predictions must be
falsifiable:
Prediction 1: after typing Ava Example, name.get_property("value") becomes "Ava Example" while the original declared value attribute remains "Seed User" until the fixture replaces the node.
Prediction 2: clicking Save while the training overlay is visible raises ElementClickInterceptedException and application status stays idle.
Prediction 3: after clicking Refresh name control, the cached name WebElement becomes stale; reacquiring #name returns a current node with the same typed value.
Prediction 4: after valid selection and normal Save, status becomes saved and result JSON contains the synthetic profile.
5. Run the complete checkpoint script
The following example makes the Run the complete checkpoint script 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
import json, traceback
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import ElementClickInterceptedException, StaleElementReferenceException
BASE_URL = "http://127.0.0.1:8765/"
E = Path("evidence"); E.mkdir(exist_ok=True)
driver = webdriver.Chrome()
report = {"selenium": selenium.__version__, "events": []}
try:
driver.get(BASE_URL)
assert driver.current_url.startswith("http://127.0.0.1:8765/")
report.update({
"session_id": driver.session_id,
"capabilities": {k: driver.capabilities.get(k) for k in ("browserName","browserVersion","platformName")},
"url": driver.current_url,
"build": driver.find_element(By.TAG_NAME,"body").get_dom_attribute("data-build"),
})
name = driver.find_element(By.ID, "name")
before_attr = name.get_dom_attribute("value")
before_prop = name.get_property("value")
name.clear(); name.send_keys("Ava Example")
report["name_state"] = {
"before_attribute": before_attr,
"before_property": before_prop,
"after_attribute": name.get_dom_attribute("value"),
"after_property": name.get_property("value"),
}
assert name.get_property("value") == "Ava Example"
# Failure 1: the overlay should intercept the user-like Save click.
try:
driver.find_element(By.ID, "submit").click()
raise AssertionError("Expected the overlay to intercept Save")
except ElementClickInterceptedException as exc:
report["events"].append({"failure":"intercepted", "exception":type(exc).__name__})
driver.save_screenshot(str(E / "01-intercepted.png"))
assert driver.find_element(By.ID, "status").get_dom_attribute("data-state") == "idle"
# Repair through the supported UI path, not JavaScript.
driver.find_element(By.ID, "dismiss").click()
# Failure 2: controlled rerender makes the cached name reference stale.
driver.find_element(By.ID, "rerender").click()
try:
_ = name.get_property("value")
raise AssertionError("Expected cached name reference to be stale")
except StaleElementReferenceException as exc:
report["events"].append({"failure":"stale", "exception":type(exc).__name__})
name = driver.find_element(By.ID, "name")
assert name.get_property("value") == "Ava Example"
# Complete the valid form state.
email = driver.find_element(By.ID, "email")
email.clear(); email.send_keys("ava@example.invalid")
Select(driver.find_element(By.ID, "role")).select_by_value("developer")
newsletter = driver.find_element(By.ID, "newsletter")
if not newsletter.is_selected(): newsletter.click()
driver.find_element(By.CSS_SELECTOR, 'input[name="plan"][value="team"]').click()
terms = driver.find_element(By.ID, "terms")
if not terms.is_selected(): terms.click()
pre_submit = {
"name": name.get_property("value"),
"email_valid": email.get_property("validity")["valid"],
"newsletter": newsletter.is_selected(),
"terms": terms.is_selected(),
"role": Select(driver.find_element(By.ID, "role")).first_selected_option.get_attribute("value"),
}
report["pre_submit"] = pre_submit
assert pre_submit == {"name":"Ava Example","email_valid":True,"newsletter":True,"terms":True,"role":"developer"}
driver.find_element(By.ID, "submit").click()
status = driver.find_element(By.ID, "status")
result = json.loads(driver.find_element(By.ID, "result").text)
assert status.get_dom_attribute("data-state") == "saved"
assert status.text == "Profile saved"
assert result == {"name":"Ava Example","email":"ava@example.invalid","role":"developer","newsletter":True,"plan":"team","terms":True}
report["final"] = {"status":status.text,"result":result}
driver.save_screenshot(str(E / "02-saved.png"))
except Exception as exc:
report["unexpected_error"] = {"type":type(exc).__name__,"message":str(exc),"traceback":traceback.format_exc()}
driver.save_screenshot(str(E / "unexpected-failure.png"))
raise
finally:
(E / "checkpoint.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
driver.quit()
6. Interpret the evidence instead of only checking pass/fail
-
Attribute/property prediction: before rerender,
the runtime property changes with typing while the original
declared
valueattribute remains the seed value. -
Intercept prediction: the first Save attempt is
blocked while status remains
idle; screenshot01-intercepted.pngshould visibly contain the overlay. -
Stale prediction: the cached name reference fails
after rerender; the newly located
#nameelement contains the current value. - Valid-state prediction: email validity, role, newsletter, terms, and name are correct before Save.
-
Application outcome: status becomes
savedand result JSON exactly matches the expected synthetic profile.
If any prediction is false, preserve that finding. Do not alter the fixture or test merely to match the expected narrative; diagnose the smallest mismatched state.
7. Add one negative-path check
Run a second short test with a malformed email or terms unchecked.
The final assertion must be application state invalid,
and no result JSON should be accepted. This proves the suite
distinguishes a successful click from a successful submission.
driver.get(BASE_URL)
driver.find_element(By.ID, "dismiss").click()
email = driver.find_element(By.ID, "email")
email.clear(); email.send_keys("bad-email")
driver.find_element(By.ID, "submit").click()
status = driver.find_element(By.ID, "status")
assert status.get_dom_attribute("data-state") == "invalid"
assert status.text == "Validation failed"
8. Verification checklist
- Exact binding version, Python version, browser name/version, platform, and session ID are captured.
- The AUT URL is loopback-only and the fixture build marker is recorded.
- At least two predictions were written before execution and checked after execution.
- Intercepted-click and stale-reference exceptions are reproduced intentionally and named in the report.
- No Selenium-side JavaScript removes the overlay, edits disabled state, or force-clicks a control.
- The valid-path result and negative-path application state are both asserted.
-
Screenshots and
checkpoint.jsonexist before cleanup. - The browser session and local HTTP server are terminated.
9. Cleanup and rollback
The following example makes the Cleanup and rollback behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
# Stop http.server with Ctrl+C first.
deactivate 2>/dev/null || true
cd ..
rm -rf selenium-ch05-checkpoint
In a real repository, rollback means reverting only test/fixture changes associated with this exercise while preserving incident evidence long enough for review. Do not delete shared browser profiles, global driver caches, or unrelated test data.
10. What Chapter 05 adds to the production operating model
You can now reason from a locator to a current WebElement, distinguish exact runtime state from markup, perform browser-mediated interactions, interpret interactability/staleness failures, and prove application outcomes instead of command completion. Chapter 06 adds deterministic synchronization so these state transitions can be awaited without sleeps or accidental races.
Knowledge check
Why does the checkpoint intentionally keep the overlay instead of deleting it before the test?
Because it creates a controlled, user-visible obstruction that demonstrates WebDriver click-interception semantics and evidence-first diagnosis.
What proves that rerender, not the locator, caused the stale-element failure?
The old reference fails after the controlled DOM replacement, while reacquiring the same stable ID returns a current element with the expected value.
Why capture pre-submit state if the final result is already asserted?
It separates control/interactability preconditions from the application outcome and makes a failure causally diagnosable.
What is the correct response if the negative-path submit click succeeds?
That is expected; the assertion is that the application state becomes invalid, demonstrating click success is not submission success.
What new topic is required to make dynamic versions of this lab deterministic?
Synchronization: waiting for the intended state transition with explicit conditions rather than sleeps, covered in Chapter 06.
Official references and version notes
- Selenium 4.47 release — current pinned Selenium release baseline for this chapter.
- Interacting with web elements — click, send keys, clear, interactability checks, and click-interception semantics.
- Information about web elements — displayed, enabled, selected, text, CSS, and attribute/property inspection.
-
Python WebElement API 4.47.0
— current
get_dom_attribute,get_property,get_attribute, interaction, and state-query APIs. -
Working with select list elements
— current support-library semantics for real HTML
<select>controls.
Version-sensitive behavior was rechecked against Selenium primary documentation on 2026-08-28. Mandatory examples pin Selenium Python 4.47.0 on Python 3.10+, use a supported locally installed Chromium-family browser, ordinary Selenium Manager resolution, and loopback-only fixtures. Grid, browser clouds, enterprise identity, and WebDriver BiDi are not required in this chapter. The checkpoint deliberately exercises browser-visible overlay and rerender behavior; these are local fixture mechanics, not recommendations to inject failures into production pages.
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.