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.
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.
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.
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-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()?
To demonstrate detection of a bad synchronization practice; it is not presented as the correct Selenium workflow.
A locator appears in two tests. Must it always become a global helper?
No. Centralize it only when both tests share the same semantic UI service and ownership boundary.
What does OWNER001 mean operationally?
A failure or deprecation decision has no accountable domain team, so triage and lifecycle ownership are ambiguous.
Why should upgrade rehearsal start with a critical smoke slice?
It provides fast, high-value compatibility evidence before spending time on the full browser/suite matrix.
What should happen to the original audit after refactoring?
Keep it beside the post-change report so reviewers can see which risks were removed and which exceptions remain intentional.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.