Checkpoint Lab — Framework Integration: pytest, JUnit, TestNG, NUnit, and Language Bindings
Prove the same browser behavior with two runnable bindings, compare lifecycle and API differences, and define a team rule that keeps framework mechanics separate from browser semantics.
Learning objectives
- Run equivalent Python/pytest and JavaScript/Mocha tests against one loopback behavior contract.
- Predict and verify session, DOM, AUT, and evidence changes in each binding.
- Build a cross-binding framework matrix with version and lifecycle assumptions.
- Produce evidence showing the two tests assert the same business behavior.
- Define a team rule that prevents framework mechanics from fragmenting browser semantics.
1. Checkpoint scenario and exact assumptions
You will run the same save synthetic name contract
with Python/pytest and JavaScript/Mocha against the loopback
fixture. The Python path requires Python 3.10+, Selenium 4.47.0, and
pytest 9.1.1. The JavaScript path requires Node.js 22+,
selenium-webdriver@4.47.0, and Mocha 11.8.0. A locally
installed supported Chrome/Chromium-family browser is required;
Selenium Manager handles normal driver resolution.
No paid Grid, production URL, real login, proxy, certificate change, or external test account is part of this checkpoint. If a second language runtime is unavailable, inspect its code and run the provided simulation/evidence comparison; do not substitute a public target.
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.
python -m venv .venv
# activate the environment for your shell
python -m pip install selenium==4.47.0 pytest==9.1.1
npm init -y
npm install --save-dev selenium-webdriver@4.47.0 mocha@11.8.0
python make_framework_fixture.py
python -m http.server 8777 --bind 127.0.0.1 --directory framework-lab
Before opening a browser, request
http://127.0.0.1:8777/ with a normal HTTP client and
confirm the title/status fixture is present. Record
python --version, node --version, package
versions, and the chosen browser version in the evidence packet.
3. Prediction gate
Write predictions before running:
- The Python and JavaScript tests will create different session IDs even though they target the same browser type and URL.
-
Both tests will mutate the same kind of DOM state from
idletosaved:Ada, then teardown will close each independent session. -
The evidence directories will differ by binding while the final
actualbusiness value will match exactly.
4. Runnable Python binding test
The following example makes the Runnable Python binding test behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
# file: test_checkpoint_python.py
import json, os
from pathlib import Path
import pytest
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
BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8777/")
@pytest.fixture
def driver():
d = webdriver.Chrome()
try:
yield d
finally:
d.quit()
def test_same_browser_contract(driver):
out = Path("evidence/python"); out.mkdir(parents=True, exist_ok=True)
driver.get(BASE_URL)
driver.find_element(By.CSS_SELECTOR, '[data-testid="name"]').send_keys("Ada")
driver.find_element(By.CSS_SELECTOR, '[data-testid="save"]').click()
WebDriverWait(driver, 5).until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, '[data-testid="status"]'), "saved:Ada")
)
actual = driver.find_element(By.CSS_SELECTOR, '[data-testid="status"]').text
driver.save_screenshot(str(out / "state.png"))
(out / "run.json").write_text(json.dumps({
"binding": "python", "framework": "pytest", "selenium": "4.47.0",
"session_id": driver.session_id, "browser": driver.capabilities.get("browserName"),
"browserVersion": driver.capabilities.get("browserVersion"),
"url": driver.current_url, "actual": actual,
}, indent=2), encoding="utf-8")
assert actual == "saved:Ada"
This is a real pytest test: discovery, fixture setup/teardown, assertion reporting, and browser evidence are all exercised together. Evidence is written before the final assertion so an intentionally changed expectation still preserves the observed browser state.
5. Runnable JavaScript binding test
The following example makes the Runnable JavaScript binding test behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
// file: test_checkpoint_javascript.js
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const {Builder, By} = require('selenium-webdriver');
describe('same browser contract', function () {
this.timeout(15000);
let driver;
const base = process.env.BASE_URL || 'http://127.0.0.1:8777/';
beforeEach(async function () {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async function () {
if (driver) await driver.quit();
});
it('saves Ada', async function () {
const out = path.join('evidence', 'javascript');
fs.mkdirSync(out, {recursive: true});
await driver.get(base);
await driver.findElement(By.css('[data-testid="name"]')).sendKeys('Ada');
await driver.findElement(By.css('[data-testid="save"]')).click();
const status = await driver.findElement(By.css('[data-testid="status"]'));
await driver.wait(async () => (await status.getText()) === 'saved:Ada', 5000);
const actual = await status.getText();
await driver.takeScreenshot().then(data => fs.writeFileSync(path.join(out, 'state.png'), data, 'base64'));
const caps = await driver.getCapabilities();
const session = await driver.getSession();
fs.writeFileSync(path.join(out, 'run.json'), JSON.stringify({
binding: 'javascript', framework: 'mocha', selenium: '4.47.0', session_id: session.getId(),
browser: caps.get('browserName'), browserVersion: caps.get('browserVersion'),
url: await driver.getCurrentUrl(), actual,
}, null, 2));
assert.equal(actual, 'saved:Ada');
});
});
This is a real Mocha test. Its async hooks own the session, every relevant WebDriver Promise is awaited, and the evidence bundle is written before the assertion so a red test still preserves the observed state.
6. Run commands and evidence packet
The following example makes the Run commands and evidence packet behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
# terminal 1: fixture server is already running
# terminal 2:
pytest -q test_checkpoint_python.py
npx mocha test_checkpoint_javascript.js
pytest -q test_framework_contract.py
npx mocha test_framework_contract.js
# inspect evidence
type evidence\python\run.json # Windows PowerShell/cmd alternative: Get-Content
# or on POSIX: cat evidence/python/run.json
# inspect evidence/javascript/run.json similarly
Keep the evidence files separate. Never let the second binding overwrite the first binding’s screenshot, JSON, or logs. If you run them concurrently later, include worker/run IDs in paths as established in Chapter 21.
7. Cross-binding comparison matrix
The following table organizes the key choices and evidence for Cross-binding comparison matrix. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.
| Stack | Discovery/lifecycle | Parameterization | Async model | Current baseline |
|---|---|---|---|---|
| Python + pytest | fixture/yield; test_* discovery | @pytest.mark.parametrize | blocking-looking WebDriver calls | Selenium 4.47.0; pytest 9.1.1 |
| Java + JUnit | @BeforeEach/@AfterEach | @ParameterizedTest | blocking-looking calls | Selenium 4.47.0; JUnit 6.1.3; Java 17+ |
| Java + TestNG | @BeforeMethod/@AfterMethod | @DataProvider | blocking-looking calls | Selenium 4.47.0; TestNG 7.9.0 |
| .NET + NUnit | [SetUp]/[TearDown] | [TestCase] | blocking-looking calls in shown API | Selenium 4.47.0; NUnit 4.6.1 |
| JavaScript + Mocha | beforeEach/afterEach | generated it() cases | Promise/await | selenium-webdriver 4.47.0; Mocha 11.8.0; Node 22+ |
8. Verify equivalence independently
-
Both evidence JSON files report
actual: saved:Ada. - Session IDs are present and different between runs.
- Browser name/version are recorded rather than assumed from the source code.
- The final URL is loopback-only.
- Both processes exit successfully only after the assertion passes and teardown completes.
- No credential, personal data, or public Grid address appears in artifacts.
9. Failure injection: prove framework mechanics do not change the diagnosis
Temporarily change the expected string in one test to
saved:Alan. The browser should still show
saved:Ada; the failure must be classified as an
assertion/test-expectation failure. Then restore the expectation.
Next, stop the fixture server and rerun one test: that is an
environment/network reachability failure, not the same category.
10. Team rule: keep browser semantics separate from framework mechanics
Rule: Every language/framework implementation must preserve the canonical browser contract—stable locators, explicit readiness, one independently owned session per concurrent test, synthetic isolated data, business assertions in the test layer, first-failure evidence, and failure-safe cleanup. Framework annotations, fixtures, plugins, and async syntax may differ, but they may not silently change those semantics.
11. Cleanup and rollback
- Stop the loopback HTTP server.
- Verify both WebDriver sessions are closed.
-
Remove
framework-lab/,evidence/,node_modules/if created only for the lab, and the disposable virtual environment if desired. - Restore any deliberately changed expected value.
- Keep only a redacted comparison report if you want a learning artifact.
12. Production operating model and bridge to Chapter 28
Chapter 27 adds a framework/binding portability layer to the operating model: lifecycle ownership, parameterization, assertion semantics, async discipline, parallel scheduling, and reports are now explicit. Chapter 28 uses that foundation to debug stale elements, timing races, driver failures, and Grid incidents without letting framework wrappers hide the original failure.
Official references and current-version notes
- Selenium downloads — Stable 4.47.0 for Java, Python, .NET, JavaScript, Ruby, and Grid as of August 10, 2026.
- Organizing and Executing Selenium Code — Official examples list JUnit, TestNG, pytest/unittest, NUnit/MSTest, RSpec/Minitest, and Jest/Mocha test runners.
- Install a Selenium library — Current Selenium examples pin selenium==4.47.0, pytest==9.1.1, and JUnit BOM 6.1.3.
- pytest changelog — pytest 9.1.1 release baseline.
- JUnit 6.1.3 User Guide — JUnit 6.1.3; Java 17+ runtime requirement.
- TestNG documentation — Current documentation reports TestNG 7.9.0.
- NUnit framework release notes — NUnit 4.6.1 release baseline.
- selenium-webdriver npm package — JavaScript binding 4.47.0 and Node.js 22+ runtime floor.
- Mocha package — Mocha 11.8.0 stable baseline used by the comparison.
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
What proves Python and JavaScript tests are behaviorally equivalent?
Both independently reach and assert saved:Ada under the same fixture contract while recording their own sessions/capabilities/evidence.
Should the two bindings produce the same session ID?
No. Independent sessions should have different IDs; equality would be a warning about shared state.
If the expected text is deliberately changed to saved:Alan, what category should fail?
The test assertion/expectation layer, while browser evidence should still show the actual saved:Ada result.
What does stopping the local fixture server test?
Environment/network reachability. It should not be normalized as an assertion or browser-locator failure.
What must remain invariant when teams choose different frameworks?
Stable locator/readiness semantics, independent session ownership, isolated data, meaningful assertions, evidence, and failure-safe cleanup.
Summary and next bridge
This lesson keeps test-framework mechanics subordinate to the WebDriver/browser contract: lifecycle, assertions, parameters, async behavior, scheduling, and reports remain explicit rather than hiding session ownership or failure meaning.
Next: Chapter 28 — Debugging Stale Elements, Timing Races, Driver Failures, and Grid Incidents
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.