PRACTICAL GUIDE / Selenium two factor authentication test strategy
A practical Selenium strategy for two-factor login
Learn how to cover TOTP, recovery codes, and WebAuthn in Selenium without turning a deterministic suite into a security bypass or flaky wait.
In this guide7 sections
- Decide which security claim the test owns
- Separate routine journeys from the real factor
- Exercise TOTP and WebAuthn with the right mechanism
- Diagnose the challenge before increasing a wait
- Put narrow security checks into CI
- Review three failures without rerunning them
- Accept the trade-offs and stop at the right boundary
What you will learn
- Decide which security claim the test owns
- Separate routine journeys from the real factor
- Exercise TOTP and WebAuthn with the right mechanism
- Diagnose the challenge before increasing a wait
The login test reaches the OTP screen in CI and then waits for a code nobody can reliably deliver. Locally, an engineer copies the code from a phone and calls the flow tested. Neither result tells you whether the second-factor policy, the code verifier, and the protected journey all work.
Decide which security claim the test owns
A useful strategy starts by splitting one vague requirement, “test two-factor login,” into claims that fail for different reasons. The browser is only one participant. The application decides whether a factor is required, an identity provider may create the challenge, a delivery service may send a message, and a verifier decides whether the response is valid. After that, the application still has to establish the correct authenticated session.
Those boundaries deserve different tests. A policy test proves that a password-only session cannot reach a protected page when the account requires a second factor. A verifier test proves that valid, invalid, expired, and previously consumed responses receive the right outcome. A delivery check proves that an SMS or email provider accepted and delivered a message. A browser journey proves that the user can move from the challenge page to the expected protected state. Putting every claim into one Selenium test creates a slow diagnostic lottery.
Selenium’s own testing guidance recommends avoiding routine automation of 2FA delivery. That advice is practical, not an argument for leaving authentication untested. A test that polls a shared mailbox or asks a carrier to deliver an SMS adds queues, throttling, spam filtering, number recycling, and external outages to every run. When the test fails, the screenshot often shows only an empty code field. It cannot tell whether the application failed to request a code or the provider delayed it.
Write down the claim beside the test before choosing a mechanism. “A TOTP-enabled account is challenged after a correct password” is a policy claim. “The verifier rejects the same TOTP twice” is a replay claim. “A registered WebAuthn credential can satisfy the challenge” is a browser integration claim. “The account page loads after authentication” is an ordinary product journey. These sentences make review easier because a reviewer can ask whether the assertion actually observes the named outcome.
The account matters as much as the code. Give each parallel worker its own identity, or provision one identity per test. A shared account can be locked by one invalid-code scenario while another test is entering a valid code. Recovery codes are consumed by design, so a static recovery code stored in a repository will eventually fail even if the product is correct. TOTP tests can also collide when two workers submit the same time-step value and the server enforces one-time use.
Treat the factor seed, recovery code, and bypass credential as security material. Keep them out of browser logs, screenshots, test names, exception messages, and CI artifacts. A screenshot of six dots is usually harmless, but page source can contain hidden fields or debug data. Capture only evidence you have reviewed. “More logs” is not a safe default on an authentication page.
The minimum portfolio I expect for a common TOTP product is small. One test checks that the policy presents the challenge. One narrow test uses a dedicated seed to complete a real TOTP challenge. Verifier-level tests cover bad, expired, and replayed values without a browser. Most authenticated UI tests start from an approved test-only authenticated state or use a tightly scoped bypass. A separate delivery monitor covers SMS or email if delivery is a business promise.
Separate routine journeys from the real factor
Running a real factor in every UI test feels thorough, but it reduces useful coverage. Twenty tests that all fail at the same OTP screen have not tested twenty product behaviors. They have tested one dependency twenty times and prevented the suite from reaching billing, profile, search, or checkout.
For routine journeys, use an authentication seam owned by the application team. Selenium’s guidance mentions options such as a special token or disabling 2FA for selected users in a test environment. The important word is selected. A global switch that disables the control for the whole environment makes negative policy tests impossible and can hide a deployment mistake. A dedicated test identity, explicit test-environment guard, short lifetime, and audit record keep the seam narrow.
Do not disguise the seam as production behavior. Name the fixture authenticated_test_session or second_factor_bypass_user, not login. Put the bypassed tests in reports under product journeys, not under security coverage. That prevents a dashboard from claiming the second factor passed when the test never exercised it.
The browser session created through a seam must still look like the state produced by the real login. Compare the important properties once: cookie names and flags visible to the test environment, identity claims exposed to the application, account role, tenant, and authentication level. Do not copy an arbitrary cookie from a developer browser. It may carry the wrong tenant, outlive the test, depend on a machine-specific encryption key, or stop working when session rotation changes.
Consider a checkout suite with many authenticated cases. Only the small group that owns password-only denial, valid second factor, and recovery needs to begin at the login page. The remaining cases can enter with a fresh test session for the account and focus on checkout. If the session helper breaks, those cases fail in setup with a clear message instead of timing out on a page element. If the 2FA policy breaks, the dedicated policy test fails even though the journey tests continue to use their seam.
A second example is an administrator portal with step-up authentication. Initial login may require only a password, while exporting customer data requires a fresh factor. Pre-authenticating every test at the highest assurance level would skip the control you care about. Start the export test at the lower assurance level, request the export, assert that the challenge appears, satisfy it, and then assert that the export is allowed. Other admin tests that do not concern step-up behavior should not pay that cost.
Recovery codes deserve their own lifecycle. Provision a new code or reset the dedicated account before the test, use exactly one code, assert the transition, and dispose of the account or record the code as consumed. Never retry the same recovery-code test automatically. A retry can turn the intended successful submission into a replay attempt, then report a misleading failure on an otherwise correct system.
The trade-off is that an authentication seam becomes test infrastructure. Someone must secure it, document it, keep it aligned with production session creation, and remove it from production builds or gate it with controls that cannot be enabled by ordinary users. That work is still cheaper than debugging hundreds of failures caused by an SMS queue, but it is not free.
Exercise TOTP and WebAuthn with the right mechanism
TOTP is attractive in CI because the runner can calculate the same time-based value as the server. That convenience comes with two hard requirements. The runner needs the shared seed, and its clock must be close enough to the verifier’s clock for the accepted window. Use a seed that belongs only to a disposable test identity. Do not reuse a human employee’s authenticator enrollment.
The following Python example implements the common six-digit, 30-second, HMAC-SHA-1 profile. Confirm those parameters against your own service before using it. The locators are an application-facing test contract, not Selenium conventions. The final heading assertion can fail if authentication does not produce the protected account page.
import base64
import hashlib
import hmac
import os
import re
import struct
import time
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
def decode_seed(secret_b32: str) -> bytes:
cleaned = re.sub(r"[\s-]", "", secret_b32).upper()
return base64.b32decode(cleaned + "=" * (-len(cleaned) % 8), casefold=True)
def totp(secret_b32: str, at: int) -> str:
key = decode_seed(secret_b32)
counter = struct.pack(">Q", at // 30)
digest = hmac.new(key, counter, hashlib.sha1).digest()
offset = digest[-1] & 0x0F
value = struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7FFFFFFF
return f"{value % 1_000_000:06d}"
def timestamp_away_from_boundary() -> int:
now = int(time.time())
seconds_left = 30 - (now % 30)
if seconds_left <= 5:
time.sleep(seconds_left + 1)
now = int(time.time())
return now
def test_dedicated_user_can_complete_totp():
base_url = os.environ["TEST_BASE_URL"].rstrip("/")
username = os.environ["TOTP_TEST_USERNAME"]
password = os.environ["TOTP_TEST_PASSWORD"]
seed = os.environ["TOTP_TEST_SEED"]
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 15)
try:
driver.get(f"{base_url}/login")
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "password").send_keys(password)
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-password']").click()
code_input = wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='otp-code']"))
)
code_input.send_keys(totp(seed, timestamp_away_from_boundary()))
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-otp']").click()
heading = wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='account-overview-heading']")
)
)
assert heading.text == "Account overview"
finally:
driver.quit()The decode_seed() step is not decoration. Base32 encodes five bits per character, so a 128-bit secret occupies 26 significant characters and needs six = characters to reach the 32-character block boundary. Enrollment screens and otpauth:// URIs almost always hand you the stripped form, and 128-bit secrets are common, so a 26-character seed is what a tester copies out of the product. Passing it straight to base64.b32decode() raises binascii.Error: Incorrect padding before a browser ever opens. The 80-bit and 160-bit secrets that produce 16- and 32-character seeds happen to land on a block boundary and decode without complaint, which is why this defect hides until one particular test identity is enrolled. Restoring the padding with "=" * (-len(cleaned) % 8) handles every length, and stripping spaces and hyphens accepts the grouped formatting some enrollment pages display. Pin the helper with the RFC 6238 test vectors: the seed GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ must yield 287082 at T=59 and 050471 at T=1111111111.
Generate the code immediately before entering it. A value calculated near the end of a time step can expire while the page animates or the request waits in a CI queue. Do not fix that by accepting an enormous server window. A wide window weakens replay resistance and changes production behavior to accommodate a test. Instead, let the test wait until it is comfortably inside the next step, or expose the verifier’s accepted time-step information in controlled server diagnostics.
WebAuthn is not TOTP with a different UI. The browser talks to an authenticator using a public-key protocol, and Selenium exposes virtual-authenticator commands for browsers that implement the WebDriver extension. That is the correct tool for testing registration, assertion, user-verification flags, resident credentials, and related application behavior. It does not emulate the physical reliability of a USB key, a fingerprint reader, or an operating-system prompt.
This diagnostic fixture creates a CTAP2 software authenticator and always removes it. The test still has to drive your application’s WebAuthn registration or login UI. The assertion proves that the driver accepted the authenticator and assigned it an identifier; later product assertions must prove registration or authentication.
import pytest
from selenium import webdriver
from selenium.webdriver.common.virtual_authenticator import (
Protocol,
Transport,
VirtualAuthenticatorOptions,
)
@pytest.fixture
def driver_with_virtual_authenticator():
driver = webdriver.Chrome()
try:
options = VirtualAuthenticatorOptions(
protocol=Protocol.CTAP2,
transport=Transport.INTERNAL,
has_resident_key=True,
has_user_verification=True,
is_user_consenting=True,
is_user_verified=True,
)
driver.add_virtual_authenticator(options)
assert driver.virtual_authenticator_id is not None
yield driver
finally:
try:
if driver.virtual_authenticator_id is not None:
driver.remove_virtual_authenticator()
finally:
driver.quit()Keep a physical-device check if your release depends on hardware behavior, native prompts, enterprise attestation, or platform biometrics. Run it on suitable hardware at a lower frequency. A virtual authenticator gives determinism and controllable states; the cost is that it cannot prove the last physical mile.
Diagnose the challenge before increasing a wait
An OTP timeout has several distinct signatures. Start with the page state. Did the password submission create a challenge? Is the challenge type TOTP, SMS, email, recovery code, push approval, or WebAuthn? Did the identity provider redirect to a different origin? Does the current test identity actually have that factor enrolled? A generic wait for input[name=code] hides all of those questions.
Capture a bounded state record at the transition. Useful fields include the test ID, a hashed or synthetic account identifier, the page origin, the visible challenge type, the server-provided challenge ID if it is safe to retain, and the browser timestamp. Do not record the password, seed, OTP, recovery code, full token, or an unreviewed page dump. The objective is correlation, not surveillance.
The helper below classifies a page by explicit markers and reports only the page origin for an unknown state. It can fail when the product removes or mislabels a marker, so it is not an oracle that always passes. Ask the application team to keep these non-secret markers stable across UI changes.
from urllib.parse import urlsplit
from selenium.webdriver.common.by import By
def authentication_phase(driver) -> str:
markers = {
"otp": "[data-auth-phase='otp']",
"webauthn": "[data-auth-phase='webauthn']",
"authenticated": "[data-auth-phase='authenticated']",
"denied": "[data-auth-phase='denied']",
}
matches = [
name
for name, selector in markers.items()
if any(
element.is_displayed()
for element in driver.find_elements(By.CSS_SELECTOR, selector)
)
]
if len(matches) != 1:
parsed = urlsplit(driver.current_url)
host = parsed.hostname or ""
if ":" in host:
host = f"[{host}]"
port = f":{parsed.port}" if parsed.port is not None else ""
origin = f"{parsed.scheme}://{host}{port}"
raise AssertionError(
f"Expected one authentication phase, found {matches}; origin={origin}"
)
return matches[0]If the phase is still otp after submission, inspect the application response rather than sleeping. An explicit invalid-code response points to the seed, time step, account enrollment, or formatting. A rate-limit response points to shared identity or retries. A new challenge identifier can mean the application rotated the challenge after a rejected attempt. If the phase becomes authenticated but the protected page sends the user back to login, the factor succeeded and session establishment failed. Those are different owners.
Clock skew has recognizable evidence. The same seed consistently works early in each 30-second interval and fails near the boundary. Several runners fail together after a base-image change. The verifier log records a counter outside its accepted window while the submitted code format is valid. Compare runner time with a trusted time source and server time, without printing the seed or code. A random locator timeout does not establish clock skew.
Account contention also looks like bad timing. One worker sees “code already used,” another passes, and both report the same synthetic account. Invalid-attempt counters rise faster than one test can produce them. Recovery codes disappear between setup and action. Search by test identity and account ID before changing the wait. The fix is identity isolation, not a longer timeout.
Delivery failure has different evidence. The application records a challenge and a successful handoff to its provider, but the provider reports delayed or rejected delivery. Selenium cannot repair that path. Move the delivery assertion into a focused monitor that can inspect provider status safely, then keep the browser journey deterministic. If the application never asked for delivery, the issue remains in the application or identity-provider integration.
A final near-miss is an ordinary navigation bug after successful verification. The audit record says the factor passed, but the browser remains on a stale page because a callback failed, a cookie was scoped to the wrong domain, or the redirect target rejected the session. Confirm the verifier outcome and inspect the cookie and navigation boundary. Re-entering the OTP only creates noise and may trigger replay protection.
Put narrow security checks into CI
CI should make the layers visible. Run deterministic authenticated journeys on every change. Run policy and verifier checks on every relevant change or merge. Run one real TOTP or virtual-authenticator path in a restricted job. Run external delivery and physical-device checks on a schedule or release gate that matches their cost and reliability.
Do not expose factor secrets to pull requests from untrusted forks. Most CI systems intentionally withhold secrets in that situation. Treat a skipped restricted job as “not evaluated,” not passed. Branch protection can require the job only where secrets are available, while untrusted changes still run password-only denial and deterministic tests that need no sensitive material.
The following workflow shows the separation. The secret-bearing TOTP job is gated to pushes on the protected repository and runs a targeted marker. The broad suite never receives the TOTP seed. Action versions are illustrative integration choices and should follow your organization’s pinning policy.
name: authentication-tests
on:
pull_request:
push:
branches: [main]
jobs:
deterministic-authenticated-journeys:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install -r requirements.txt
- run: pytest -m "auth_policy or authenticated_journey"
env:
TEST_BASE_URL: ${{ vars.TEST_BASE_URL }}
restricted-real-totp:
if: github.event_name == 'push' && github.repository == 'example/product'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install -r requirements.txt
- run: pytest -m real_totp --maxfail=1
env:
TEST_BASE_URL: ${{ vars.TEST_BASE_URL }}
TOTP_TEST_USERNAME: ${{ secrets.TOTP_TEST_USERNAME }}
TOTP_TEST_PASSWORD: ${{ secrets.TOTP_TEST_PASSWORD }}
TOTP_TEST_SEED: ${{ secrets.TOTP_TEST_SEED }}Make retries opt-in for this job. Retrying a failed product page test can be tolerable during investigation, but automatically retrying invalid OTP attempts changes account state. It can consume a recovery code, cross a lockout threshold, or reuse a TOTP value. Preserve the first failure and reset the dedicated identity through an approved setup path before another attempt.
Roll out the strategy in stages. First, label existing tests by the claim they make and identify every shared account. Second, extract routine journeys from the real-factor path and give them a supported session seam. Third, add focused policy and verifier coverage to replace any security claim lost during extraction. Fourth, move secrets into the restricted job and audit artifacts for leakage. Finally, track external delivery and hardware checks separately so their availability does not distort product-regression results.
Expect the first migration to reveal false confidence. A suite may have hundreds of tests that enter an OTP but no assertion that password-only access is denied. Another suite may prove the challenge page appears but never verify an invalid or replayed code. Count claims, not test cases. Ten tests following the same happy path are one kind of evidence.
Review three failures without rerunning them
Suppose the narrow TOTP job fails near a time-step boundary. The phase record says otp, the account audit says the challenge remained active, and the verifier classifies the submitted step as older than its accepted window. The runner clock is correct when checked after the job, but the code was generated before a long browser startup and entered near the next boundary. That evidence supports moving generation closer to submission. It does not support increasing Selenium's element timeout, widening the server's acceptance window, or blaming the locator.
Now consider a failure with the same screenshot but a different record. The verifier never received a submission, and the browser console shows the form's own client validation rejecting the entry as too short. Three pieces of evidence line up: the OTP input carries minlength="8", the dedicated identity is enrolled for an eight-digit profile, and the helper's final line is f"{value % 1_000_000:06d}", which always returns exactly six characters. Clock work will not help, because nothing was ever submitted. The code generator and the service parameters disagree about digit count. Put digit count, algorithm, and period in reviewed test configuration, and add a unit test against a known vector approved by the identity team. Keep the seed itself out of that fixture.
A recovery-code case can fail after the protected page appears. The browser reaches the account heading, then teardown reports that the expected unused-code count did not decrease. That is not a Selenium success with a noisy cleanup. The product may have established a session without consuming the recovery credential, which leaves a replay risk. The browser assertion and verifier-state assertion own different claims, so retain both outcomes and escalate the security claim even though navigation passed.
WebAuthn gives another useful contrast. If add_virtual_authenticator fails before the product page is opened, the browser or driver does not support the required automation extension in that environment. If the authenticator is added and the page says no credential is registered, inspect relying-party ID and credential setup. If the assertion event succeeds but the application returns to the challenge, investigate server verification and session establishment. One screenshot of the challenge cannot distinguish those stages.
Finally, review a bypass incident as a control failure. A product journey passes with the special test token in an environment where the security suite expected a real challenge. The immediate product assertion is valid, but the job was routed through the wrong authentication tier. Make the environment expose a non-secret authentication-method marker and have security tests reject the bypass method. This oracle fails if configuration accidentally weakens the path, rather than trusting a job name or account convention.
These examples also show why automatic reruns are poor evidence. A new TOTP time step, a reset account, or a different WebAuthn session can erase the original state. Preserve the first attempt's challenge classification, then reproduce only after the owner can state which hypothesis the rerun tests.
Accept the trade-offs and stop at the right boundary
Determinism costs fidelity. A bypassed session does not prove the factor. A generated TOTP does not prove SMS delivery. A virtual authenticator does not prove a physical key. A verifier test does not prove the browser UI. The strategy works because each limitation is explicit and another focused check owns the missing claim.
Real-factor coverage costs secret management and runtime. Dedicated accounts need provisioning, cleanup, rotation, and lockout recovery. TOTP runners need trustworthy clocks. WebAuthn credentials need controlled setup. Delivery monitors need provider access and careful retention. Budget those tasks as authentication test infrastructure, not incidental Selenium helpers.
There are clear cases where Selenium should not drive the factor. Do not automate a personal phone, a production employee account, or a consumer’s real email inbox. Do not weaken the production policy by allowlisting a broad CI network. Do not keep recovery codes in source control. Do not turn off 2FA for an entire shared environment because unrelated tests are failing.
Avoid browser automation for verifier edge cases that can be exercised below the UI. Expiry windows, replay counters, attempt limits, recovery-code consumption, and audit events are easier to cover through the service’s supported test interface or component tests. The browser adds no useful observation if every case submits the same form and only the verifier response changes.
Do not use a virtual authenticator to claim hardware certification. It is excellent for deterministic WebAuthn application behavior, including controllable user-verification states. It cannot reproduce a vendor firmware defect, USB permission, Bluetooth transport, native biometric enrollment, or enterprise device posture. Keep a manual or device-lab path when those risks matter.
Skip end-to-end delivery on every commit when the provider contract is already covered and the dependency is slow or costly. One scheduled delivery monitor can detect an integration outage without blocking every product pull request. Conversely, keep delivery in a release gate if receiving the message is the product itself. The right frequency follows the business claim, not a blanket preference for fast tests.
The review question is simple: what defect would make this assertion fail? A password-only denial should fail if the protected resource becomes reachable. A replay test should fail if the verifier accepts a consumed response. A journey test should fail if the established session lacks access. If the only assertion is that Selenium found an OTP field, the test proves a page rendered, not that two-factor authentication works.
// FIELD DISPATCH
Get the QA Field Notes
Weekly QA battles, AI testing guides, and interview drills. Free on Substack.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 02Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 03Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 04Official w3.org reference
w3.org
Primary documentation selected and verified for the claims in this guide.
FAQ / QUICK ANSWERS
Questions testers ask
Should Selenium automate a real SMS OTP?
Usually it should not. A browser test that waits on a carrier, handset, or shared inbox tests several unreliable systems at once, so keep one narrow delivery smoke check outside the main regression suite.
How can a CI test enter a TOTP code safely?
Use a dedicated test identity and store its seed in the CI secret manager, then generate the code only in the restricted job that needs it. Never print the seed or generated code, and rotate the identity on a documented schedule.
Can Selenium test a WebAuthn second factor?
Yes, when the browser and driver expose the WebAuthn virtual-authenticator commands. That covers browser and application handling of a software authenticator, but it does not prove a physical key, biometric sensor, or enterprise device policy works.
Why does an OTP test pass locally but fail in CI?
Clock skew, a reused test account, delivery latency, and a different challenge policy are more likely than a Selenium locator problem. Record the challenge type and server time window before increasing a wait.
What is the safest way to bypass 2FA in end-to-end tests?
A test-environment-only bypass with a dedicated identity and auditable scope is safer than a production credential shared across the suite. Keep separate tests for the policy that requires the factor, because a bypassed journey cannot prove that control.
RELATED GUIDES
Continue the learning route
GUIDE 01
Selenium Grid Trace Correlation with Test IDs
Master Selenium grid trace correlation with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 02
Selenium Java Tutorial: Build a Maintainable Test Suite
Selenium Java tutorial for beginners covering Maven setup, WebDriver, waits, TestNG, JUnit, page objects, debugging, CI, and reliable UI tests.
GUIDE 03
Page Load Strategy and Navigation Readiness in Selenium 4
Choose a Selenium page load strategy, separate document milestones from app readiness, and diagnose navigation timeouts without hiding slow failures.
GUIDE 04
Test Passkeys and WebAuthn with Selenium Virtual Authenticator
Test passkey registration and sign-in with Selenium virtual authenticators, explicit WebAuthn options, negative paths, and reliable diagnostics.
GUIDE 05
Use Java Records for Selenium Test Data
Master Selenium Java records test data with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.