Chapter 29Lesson 02~270 minutes

Test Architecture, Governance, Coding Standards, and Suite Evolution: Guided Hands-On Workflow

Audit a disposable sample suite for duplication, sleeps, shared state, weak assertions, naming problems, cleanup gaps, and version drift; then apply a coding standard, ownership metadata, and an upgrade rehearsal.

Suite auditCoding standardOwnershipUpgrade rehearsalRefactoring

Learning objectives

  • Generate and audit a disposable Selenium-like sample suite.
  • Detect duplicated locators, fixed sleeps, shared state, weak assertions, missing cleanup, naming issues, and version drift.
  • Apply a review checklist and architecture boundary to the sample.
  • Add ownership and deprecation metadata that can be checked automatically.
  • Run a minimal Selenium/browser upgrade rehearsal plan without mutating production systems.

1. Lab scope and preflight

Current version scope: Selenium 4.47.0 is the pinned binding/Grid baseline for this chapter; browser/driver versions must be recorded from the actual run.

This workflow creates a disposable local directory named governance-lab. The sample files are intentionally poor so the audit has something real to find. No production AUT, account, Grid, browser profile, or external service is required for the mandatory path. Python 3.10+ is sufficient for the static audit; the optional live smoke probe uses selenium==4.47.0 and a supported local browser.

Do not copy the deliberately bad sample

Its fixed sleep, shared-driver placeholder, weak assertions, duplicated locators, and stale Selenium pin exist only to exercise the governance audit.

2. Generate the disposable sample suite

The following example makes the Generate the disposable sample suite behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

# file: make_governance_lab.py
from pathlib import Path
import json

root = Path("governance-lab")
(root / "tests").mkdir(parents=True, exist_ok=True)
(root / "pages").mkdir(exist_ok=True)
(root / "artifacts").mkdir(exist_ok=True)

(root / "requirements.txt").write_text("selenium==4.44.0\npytest==9.1.1\n", encoding="utf-8")
(root / "tests" / "test_checkout.py").write_text('''from selenium.webdriver.common.by import By\nimport time\n\nDRIVER = None  # deliberately shared state for the audit\n\ndef test_a():\n    email = (By.CSS_SELECTOR, "#checkout > form > div:nth-child(2) > input")\n    time.sleep(2)\n    assert True\n\ndef test_b():\n    email = (By.CSS_SELECTOR, "#checkout > form > div:nth-child(2) > input")\n    assert email is not None\n''', encoding="utf-8")
(root / "tests" / "test_login.py").write_text('''from selenium.webdriver.common.by import By\n\ndef test1():\n    locator = (By.CSS_SELECTOR, "#login > div:nth-child(1) > input")\n    assert locator\n''', encoding="utf-8")
(root / "suite_inventory.json").write_text(json.dumps({
  "tests": [
    {"id": "checkout-happy", "owner": "commerce", "business_risk": 5, "runtime_s": 18, "flake_rate": 0.06, "browser_tier": "smoke"},
    {"id": "login-happy", "owner": "", "business_risk": 5, "runtime_s": 7, "flake_rate": 0.01, "browser_tier": "smoke"},
    {"id": "legacy-coupon", "owner": "commerce", "business_risk": 1, "runtime_s": 34, "flake_rate": 0.12, "browser_tier": "broad", "deprecated_flow": True}
  ]
}, indent=2), encoding="utf-8")
(root / "run_history.json").write_text(json.dumps({
  "checkout-happy": ["pass", "pass", "fail", "pass", "pass"],
  "login-happy": ["pass", "pass", "pass", "pass", "pass"],
  "legacy-coupon": ["pass", "fail", "pass", "fail", "pass"]
}, indent=2), encoding="utf-8")
print(root.resolve())

Run python make_governance_lab.py. Before auditing, predict the findings: at minimum there should be a fixed sleep, module-level shared driver state, weak test names/assertion, duplicated/brittle selectors, an ownerless test, a deprecated flow consuming runtime, and version drift from the current 4.47.0 course baseline.

3. Run a deterministic static governance audit

The following example makes the Run a deterministic static governance audit behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

# file: governance-lab/audit_suite.py
from pathlib import Path
import ast, json, re

ROOT = Path(__file__).resolve().parent
CURRENT_SELENIUM = "4.47.0"
findings = []
locators = {}

for path in sorted((ROOT / "tests").glob("test_*.py")):
    text = path.read_text(encoding="utf-8")
    tree = ast.parse(text, filename=str(path))
    if "time.sleep(" in text:
        findings.append({"rule": "SYNC001", "file": path.name, "message": "fixed sleep used as synchronization"})
    if re.search(r"^DRIVER\s*=", text, re.M):
        findings.append({"rule": "STATE001", "file": path.name, "message": "module-level shared driver state"})
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef) and node.name in {"test1", "test_a", "test_b"}:
            findings.append({"rule": "NAME001", "file": path.name, "message": f"weak test name: {node.name}"})
        if isinstance(node, ast.Assert) and isinstance(node.test, ast.Constant) and node.test.value is True:
            findings.append({"rule": "ASSERT001", "file": path.name, "message": "assert True carries no business evidence"})
    for selector in re.findall(r'By\.CSS_SELECTOR,\s*"([^"]+)"', text):
        locators.setdefault(selector, []).append(path.name)

for selector, files in locators.items():
    if len(files) > 1:
        findings.append({"rule": "LOCATOR001", "file": ",".join(sorted(files)), "message": f"duplicated locator: {selector}"})
    if "nth-child" in selector:
        findings.append({"rule": "LOCATOR002", "file": ",".join(sorted(set(files))), "message": f"brittle structural locator: {selector}"})

requirements = (ROOT / "requirements.txt").read_text(encoding="utf-8")
match = re.search(r"selenium==([0-9.]+)", requirements)
if not match or match.group(1) != CURRENT_SELENIUM:
    findings.append({"rule": "VERSION001", "file": "requirements.txt", "message": f"lab baseline expects selenium=={CURRENT_SELENIUM}"})

inventory = json.loads((ROOT / "suite_inventory.json").read_text(encoding="utf-8"))
for item in inventory["tests"]:
    if not item.get("owner"):
        findings.append({"rule": "OWNER001", "file": item["id"], "message": "test has no accountable owner"})
    if item.get("deprecated_flow") and item.get("runtime_s", 0) > 0:
        findings.append({"rule": "DEPREC001", "file": item["id"], "message": "deprecated product flow still consumes suite runtime"})

out = ROOT / "artifacts" / "audit.json"
out.write_text(json.dumps({"finding_count": len(findings), "findings": findings}, indent=2), encoding="utf-8")
print("findings:", len(findings))
for finding in findings:
    print(finding["rule"], finding["file"], "-", finding["message"])
print("report:", out)

Save this as governance-lab/audit_suite.py and run it. The report is evidence, not an automatic rewrite. Each rule names a reason a reviewer should inspect architecture or behavior. Static rules can find obvious patterns but cannot decide whether every abstraction is appropriate.

4. Interpret findings by architectural layer

The following table organizes the key choices and evidence for Interpret findings by architectural layer. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Finding Layer Why it matters Repair direction
SYNC001 fixed sleep synchronization guesses readiness and slows feedback replace with bounded condition tied to AUT state
STATE001 shared driver fixture/session lifecycle breaks isolation and parallel ownership one session per test/concurrent unit
LOCATOR001/002 UI abstraction duplicate/brittle knowledge scatters change cost curate semantic locator in one real owner
ASSERT001 test intent green result does not prove user outcome assert observable business/UI result
VERSION001 environment reproduction and upgrade state drift pin approved candidate and record runtime capabilities
OWNER001 governance failure has no accountable triage owner assign domain owner and review path
DEPREC001 portfolio obsolete flow still consumes runtime/maintenance review coverage and retire deliberately

5. Apply a coding/review standard without hiding test intent

A useful standard is short enough to use in code review and specific enough to reject risky behavior. It should forbid hidden fixed sleeps/retries, lifecycle ownership inside page objects, real secrets, personal profiles, and unbounded evidence. It should require meaningful outcome assertions, failure-safe quit, isolation, version evidence, and owner/risk metadata.

6. Refactor locators and assertions at the correct boundary

The two checkout tests know the same structural selector. Move that locator to a checkout page/component only if both scenarios represent the same semantic UI service. Do not build a giant global locator registry. Replace assert True with an assertion about the post-action state the user cares about. The test remains responsible for that business outcome.

7. Remove shared lifecycle state

A module-level DRIVER makes ownership ambiguous and breaks Chapter 21’s concurrency contract. In pytest, use a fixture that creates a fresh driver and yields it, then quits in a finally-equivalent fixture teardown. Page objects receive the driver; they do not create or destroy it.

8. Add machine-readable ownership and policy metadata

The following example makes the Add machine-readable ownership and policy metadata behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

{
  "schema_version": 1,
  "owners": {
    "commerce": {"path": ["tests/checkout", "pages/checkout"], "reviewers": ["commerce-qa"]},
    "identity": {"path": ["tests/login", "pages/login"], "reviewers": ["identity-qa"]}
  },
  "policy": {
    "selenium_baseline": "4.47.0",
    "runtime_budget_minutes": 12,
    "flake_budget": 0.02,
    "quarantine_requires_owner": true,
    "quarantine_expiry_days": 14,
    "browser_matrix": {"pull_request": ["chrome"], "nightly": ["chrome", "firefox"]}
  }
}

Metadata turns review questions into checks: every owned path can require a reviewer, quarantine can require an owner and expiry, and CI can compare measured runtime/flake trends with budgets. The exact schema is local policy; the important point is that the rules are explicit and version-controlled.

9. Rehearse an upgrade instead of discovering it in the release pipeline

The following example makes the Rehearse an upgrade instead of discovering it in the release pipeline behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

from importlib.metadata import version
from pathlib import Path
import json

EXPECTED = "4.47.0"
actual = version("selenium")
print("selenium:", actual)
if actual != EXPECTED:
    raise SystemExit(f"rehearsal expected selenium=={EXPECTED}; got {actual}")

# In a real rehearsal, run the smallest critical smoke slice in a disposable branch/job,
# record returned browser/driver capabilities, compare failures, then expand the matrix.
plan = {
    "steps": [
        "pin candidate binding/server versions in isolated environment",
        "record browser and driver versions/capabilities",
        "run critical smoke slice",
        "compare failures and evidence against baseline",
        "run scheduled broad browser matrix",
        "promote version policy only after review"
    ]
}
Path("artifacts").mkdir(exist_ok=True)
Path("artifacts/upgrade-rehearsal.json").write_text(json.dumps(plan, indent=2), encoding="utf-8")

Run this only inside an isolated environment pinned to the candidate version. A real rehearsal also runs the critical browser smoke slice and records returned browser/driver capabilities. If the candidate fails, preserve the evidence and keep the approved baseline; do not download a random driver or suppress the failure.

10. Optional live browser baseline probe

The following example makes the Optional live browser baseline probe 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
from importlib.metadata import version
import json
from selenium import webdriver
from selenium.webdriver.common.by import By

out = Path("artifacts")
out.mkdir(exist_ok=True)
driver = webdriver.Chrome()
try:
    driver.get("data:text/html,<title>Governance smoke</title><p data-testid='ready'>ready</p>")
    assert driver.find_element(By.CSS_SELECTOR, "[data-testid='ready']").text == "ready"
    caps = driver.capabilities
    evidence = {
        "selenium": version("selenium"),
        "session_id": driver.session_id,
        "browserName": caps.get("browserName"),
        "browserVersion": caps.get("browserVersion"),
        "platformName": caps.get("platformName"),
        "driverVersion": caps.get("chrome", {}).get("chromedriverVersion"),
        "title": driver.title,
    }
    (out / "browser-baseline.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8")
finally:
    driver.quit()

This tiny smoke does not validate the product. It proves the candidate binding can create and quit a local session and records the actual browser/driver environment. In a repository rehearsal, replace the data URL with an authorized disposable smoke AUT and keep the same evidence contract.

11. Define ownership and deprecation metadata for the sample

Assign login-happy to an identity owner rather than leaving it orphaned. For legacy-coupon, confirm whether the product flow still exists. If removed, record retirement approval and coverage impact; if still supported, remove the deprecated flag and stabilize it. Governance should never delete coverage merely because it is slow.

12. Before/after verification

After refactoring, rerun the audit and record which findings disappeared. The desired outcome is not “zero warnings at any cost”; it is that each remaining exception is deliberate, owned, documented, and justified. Preserve the original audit JSON beside the post-change report so the change is reviewable.

13. Small challenge: choose the control, not the syntax

You inherit three problems: a critical checkout test flakes 4% of runs, a stable browser matrix takes 25 minutes on every commit, and an obsolete low-risk journey takes 5 minutes. Choose three different controls. The likely answers are root-cause repair/time-bounded quarantine for the flake, tiered smoke-versus-nightly matrix for feedback time, and owner-approved retirement for the obsolete journey. “Add workers” is not a universal governance control.

14. Cleanup

Delete only the disposable governance-lab directory you created. If you ran the optional browser probe, verify quit() completed and no course-owned temporary profile/download state remains. Do not modify global browser settings or production repositories as part of this lab.

Next lesson

Test Architecture, Governance, Coding Standards, and Suite Evolution: Configuration, Design Patterns, and Trade-Offs

Continue with Test Architecture, Governance, Coding Standards, and Suite Evolution: Configuration, Design Patterns, and Trade-Offs. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Official references and current-version notes

  • Selenium downloads — Stable Selenium clients and Selenium Server/Grid 4.47.0, released August 10, 2026.
  • Encouraged testing behaviors — Selenium explicitly frames these as guidelines/recommendations rather than universal best practices.
  • Avoid sharing state — Current guidance to isolate test data and create a new WebDriver instance per test.
  • Page object models — Current Selenium guidance on clean separation, centralized page services/locators, and keeping business assertions in tests.
  • Selenium Manager — Official default driver/browser management path used by modern Selenium bindings when drivers are not explicitly supplied.
  • Grid security — Current warning that Grid must be protected from external/public access.
  • Grid CLI options — Current Grid configuration surface to re-check during platform/version policy reviews.
Version and compatibility note

Version-sensitive statements in this lesson retain the pinned baseline used when the lesson was authored. Before changing Selenium, browser, driver, Grid, BiDi, container, or framework dependencies, compare that baseline with current primary documentation instead of silently substituting an unverified “latest” environment.

Knowledge checks

Why does the sample audit intentionally contain time.sleep()?

A locator appears in two tests. Must it always become a global helper?

What does OWNER001 mean operationally?

Why should upgrade rehearsal start with a critical smoke slice?

What should happen to the original audit after refactoring?

Summary and next bridge

A maintainable Selenium suite is a governed portfolio: explicit ownership, architecture boundaries, isolated state, measurable reliability/runtime, version evidence, review rules, and a deliberate upgrade/quarantine/deprecation lifecycle.

Next: Test Architecture, Governance, Coding Standards, and Suite Evolution: Configuration, Design Patterns, and Trade-Offs

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.