Parallelism exposes non-determinism in a 1,240-test Selenium regression suite
FinBank's web regression suite has 1,240 Selenium tests running against a shared staging environment. Last quarter the suite ran serially in 5 h 40 min on 3 agents; the team moved to 16 parallel browser sessions across 4 agents and wall-clock time fell to 38 min, but the flake rate rose from 3% to 17%. Failures cluster in three areas: tests that log in as the same seeded customer, tests that assert on a dashboard counter fed by an asynchronous reporting job, and tests that order a product and then verify it in a back-office screen. Re-runs pass about 70% of the time, and two testers now spend most of each day triaging and re-running. The product owner wants the suite used as a merge gate within 30 minutes; the CTO wants a proposal that does not simply add more agents.
1. Explain why the move to parallel execution produced this failure pattern, and set out architectural changes in priority order with the effect you expect from each.
Read the worked answer
Parallelism did not create the defects; it removed the accidental ordering and delay that were hiding non-determinism. Three distinct causes map to the three failure clusters.
Shared seeded customer. Tests logging in as the same customer contend for the same records: sessions invalidate each other, carts and locks collide, and one test's cleanup deletes another's data. Fix: per-test data provisioning through the application's own APIs, generating a unique customer per test with a reserved identifier range, plus teardown owned by the test. This is the highest-value change because it removes cross-test coupling rather than masking it.
Dashboard counter from an asynchronous job. Assertions here are effectively time-based; the test checks a value before the aggregation job has committed. Fix: replace sleeps with condition-based polling on a business-meaningful signal (job status endpoint, or an event published when aggregation completes), with a bounded timeout and a clear failure message. Polling alone is insufficient if the job is genuinely eventual; the test must define the freshness guarantee it is asserting.
Order then back-office verification. Two systems propagate asynchronously, so the second assertion races the integration. Fix: either assert at the integration boundary via a stub or queue inspection, or poll the back-office read model with a documented SLA. Do not simply raise the timeout, which converts a fast flake into a slow flake.
Beyond these, the suite needs independence as an explicit design rule: any test that cannot run alone is a defect in the test asset. Sequence-dependent chains should be refactored or moved to a small serial lane that is excluded from the gate.
Adding agents made contention worse and increased cost while leaving the root causes intact. Expected result after changes 1 and 2: flake rate under 2% and a 30-minute gate becomes credible.
2. Design the measurement and triage approach you would use to prove the suite is getting better and to separate automation defects from product defects.
Read the worked answer
The current 17% figure is a single aggregate that cannot guide action, and re-run-to-green hides the very signal needed. The approach:
Classify every failure at first occurrence. Triage categories: product defect, automation defect (script/logic), test data defect, environment/infrastructure defect, and unknown. The unknown bucket must trend toward zero; a large unknown bucket means triage is guesswork. Record the category and a root-cause tag (e.g. shared data, timing, selector, stub mismatch) in the test management or defect tool, not in a spreadsheet that goes stale.
Measure first-run results, not final results. Report pass rate on first attempt, and report the re-run pass rate separately. A test that passes only on re-run is counted as failed for reliability purposes even if the build is ultimately green.
Establish a stability baseline. Execute the full suite against an unchanged build ten times (or use the nightly history over two weeks) and compute per-test flake probability. Publish the distribution: how many tests are above 1%, above 5%. This turns opinion into a ranked repair backlog and lets you track the tail shrinking.
Track leading indicators alongside outcome metrics: count of tests containing fixed sleeps, count of tests sharing a seeded identity, number of distinct test data provisioning paths, and triage effort in person-hours per week. The last one is the business case: two testers at roughly a full day each is a measurable cost that the CTO will recognise.
Finally, use a control chart of first-run pass rate per build so improvement is visible against normal variation, and agree a target with the product owner, for example under 2% flake with no test above 1% in the merge gate.
3. Should the full 1,240-test suite be the 30-minute merge gate? Recommend a gate design and justify the trade-offs, including the risk you are accepting.
Read the worked answer
A 30-minute gate is achievable but not with all 1,240 tests, and the wrong answer is to fit everything by cutting waits. Recommend a tiered design:
- Pull-request smoke (about 150 tests, under 10 minutes): authentication, one happy path per critical journey, and fast API-level checks of changed areas. Purpose is fast rejection of obviously broken changes.
- Merge gate (about 400 tests, 20-25 minutes): risk-ranked tests covering the three clusters plus the highest-risk business rules, scaled across shards once data isolation exists. Only tests with a demonstrated flake probability below 0.5% are eligible.
- Nightly full suite (all 1,240) plus exploratory: catches depth regressions the gate omits.
The eligibility rule matters more than the count. A flaky gate teaches developers to ignore red, which is already happening elsewhere in the organisation; a smaller trustworthy gate preserves its authority. The trade-off is explicit: a subset gate accepts that some regressions reach the mainline and are caught nightly, so the nightly result must be owned, actioned within one working day, and any failure must block the release train rather than being re-run away.
Preconditions before switching on the gate: data isolation for the seeded-customer tests, deterministic synchronisation for the async assertions, and a quarantine mechanism with an owner and expiry so nobody 'fixes' the gate by disabling tests silently. If those are not in place, run the gate in shadow mode for two weeks, record what it would have blocked, and publish the false-block rate. If false blocks exceed roughly 2%, delay the switch rather than weaken the criteria.