Chapter 05Lesson 05~245 minutes

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.

CheckpointState transitionsIntercept + staleEvidence packetCleanup

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
Preflight: confirm a supported local browser is installed. Selenium Manager may resolve the corresponding driver automatically. Do not point this lab at a remote/public Grid or non-loopback AUT.

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 value attribute remains the seed value.
  • Intercept prediction: the first Save attempt is blocked while status remains idle; screenshot 01-intercepted.png should visibly contain the overlay.
  • Stale prediction: the cached name reference fails after rerender; the newly located #name element contains the current value.
  • Valid-state prediction: email validity, role, newsletter, terms, and name are correct before Save.
  • Application outcome: status becomes saved and 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.json exist 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?

What proves that rerender, not the locator, caused the stale-element failure?

Why capture pre-submit state if the final result is already asserted?

What is the correct response if the negative-path submit click succeeds?

What new topic is required to make dynamic versions of this lab deterministic?

Next lesson

Synchronization: Implicit, Explicit, Fluent, and Custom Waits

Use explicit state conditions to wait for the browser/AUT transitions that Chapter 05 made observable.

Official references and version notes

Version and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.