Handling Flaky Tests in Cucumber
What Are Flaky Tests?
A flaky test is an automated test that sometimes passes and sometimes fails even though the application code and the test code have not changed. The same scenario may pass in one execution, fail in the next execution, pass again after rerun, and then fail later in a CI pipeline. This inconsistent behavior makes flaky tests one of the most damaging problems in automation.
Run 1 -> PASS
Run 2 -> FAIL
Run 3 -> PASS
Run 4 -> PASS
Run 5 -> FAIL
In Cucumber automation, flaky tests can appear in UI scenarios, API scenarios, database validation scenarios, hybrid UI and API flows, and parallel execution pipelines. A flaky scenario may fail because an element was not ready, an API response was delayed, the browser behaved differently, shared test data changed, a backend job was still processing, or another thread modified the same resource.
In simple terms, a flaky test is an unreliable test that produces inconsistent results under the same expected conditions. A stable automated test should give the same result when the system state, test data, environment, and code are unchanged. When it does not, the team must investigate the test, framework, data, and environment instead of ignoring the failure.
Why Flaky Tests Are Dangerous
Flaky tests create confusion because they blur the difference between a real defect and an automation issue. When a build fails, the team should be able to trust the signal. A failed build should mean something important needs attention. If failures are frequently flaky, people start rerunning builds without investigation. Over time, automation loses authority.
Build Failed
-> Real Bug?
-> Flaky Test?
-> Rerun
-> Time Wasted
This is dangerous because real defects may be ignored. If a team becomes used to random failures, they may dismiss a genuine bug as another flaky test. Flaky tests also delay releases, increase maintenance cost, consume CI resources, and frustrate developers and testers. A flaky suite makes automation feel like noise instead of feedback.
Reliable automation should help teams move faster. Flaky automation does the opposite. It slows decisions because every failure requires a debate: is the product broken, is the test broken, or is the environment unstable? Handling flaky tests is therefore not a cosmetic cleanup activity. It is essential for trustworthy CI/CD and release confidence.
Flaky Test Lifecycle
The typical flaky test lifecycle starts innocently. A test is written and passes during local execution. Later, it fails once in CI. Someone reruns the job, and it passes. Because the rerun passed, the failure is ignored. The same failure appears again later, and the team repeats the rerun habit. Eventually, the test becomes known as unstable, and people stop trusting it.
Write Test
-> Execute
-> Random Failure
-> Rerun
-> Pass
-> Team Ignores Failure
-> Real Bugs May Be Missed
This lifecycle is harmful because it normalizes instability. Every flaky failure should be treated as useful information. It may reveal a synchronization weakness, poor locator, data dependency, race condition, environmental instability, or real intermittent product defect. The goal should be to understand the pattern and fix the root cause, not to rerun blindly.
Characteristics of Flaky Tests
Flaky tests usually have recognizable characteristics. They pass sometimes and fail other times. They are difficult to reproduce consistently. They may fail more often in CI than locally. They may fail on one browser but not another. They may fail only under parallel execution. They may depend on timing, network speed, data state, or backend processing time.
A flaky test often has symptoms such as TimeoutException, NoSuchElementException, StaleElementReferenceException, assertion mismatches, API timeouts, missing records, duplicate data errors, or report attachment issues. The exception message is only the starting point. The real cause may be deeper than the visible error.
Common Causes of Flaky Tests
Flaky tests come from many sources. The most common causes include synchronization issues, dynamic web elements, poor locators, shared test data, unstable environments, network problems, browser differences, race conditions, hardcoded waits, test dependencies, API response delays, and database state conflicts.
Flaky Tests
-> Synchronization Issues
-> Dynamic Elements
-> Poor Locators
-> Shared Test Data
-> Environment Issues
-> Network Problems
-> Browser Differences
-> Race Conditions
-> Hardcoded Waits
-> Test Dependencies
-> API Delays
-> Database State
Handling flaky tests begins with classification. A timing problem needs better waits. A locator problem needs stable selectors. A data problem needs isolation. A thread-safety problem needs independent resources. An environment problem needs monitoring and capacity review. A real intermittent product defect needs application investigation. Different causes require different fixes.
Synchronization Issues
Synchronization issues are one of the most frequent causes of flaky Selenium tests. A test may click a button before it is clickable, read text before it is updated, search for an element before it appears, or validate a page before JavaScript finishes rendering. The test is faster than the application state.
Click Button
-> Page Still Loading
-> Element Not Found
-> Failure
The solution is proper synchronization. Use explicit waits for meaningful conditions such as visibility, clickability, invisibility of loaders, URL changes, text presence, completed AJAX-driven state, or expected DOM updates. Waiting for time is weaker than waiting for a condition. A stable test should wait for the application to become ready, not guess how long readiness might take.
Hardcoded Waits
Hardcoded waits such as Thread.sleep(5000) are a common attempted fix for flakiness, but they often create new problems. If the application is slower than five seconds, the test still fails. If the application is ready in one second, the test wastes four seconds. When used throughout a suite, fixed waits make execution slow without guaranteeing stability.
Wrong:
Thread.sleep(5000);
Better:
WebDriverWait with a specific condition
Hardcoded waits also hide the real readiness condition. The test does not express what it is waiting for. A better approach is to wait until the target element is visible, clickable, present, absent, or updated. In API tests, wait or poll for a meaningful backend state rather than sleeping blindly.
Poor Locators
Poor locators cause flaky UI tests because they depend on unstable DOM details. A locator based on a dynamic ID, absolute XPath, index position, generated class name, or fragile layout may work today and fail tomorrow. It may also work in one browser or screen size but fail in another.
Bad locator:
//*[@id='input12345']
Better options:
Stable ID
Name
data-test attribute
Relative CSS or XPath based on stable attributes
Stable locators are a framework investment. Use meaningful IDs, names, labels, roles, text where appropriate, or dedicated test attributes such as data-testid. Avoid locators that describe the current visual structure instead of the element's stable identity. A reliable locator should survive normal UI refactoring.
Dynamic Web Elements
Modern web applications often render elements dynamically. A button may appear after an API call. A table may update after filtering. A modal may animate into view. A spinner may disappear only after data is loaded. If the test interacts immediately, the element may not exist yet or may not be interactable.
Loading Spinner
-> Button Appears Later
-> Immediate Click
-> Failure
The fix is to wait for the right state. Wait until the spinner disappears, the button is clickable, the table contains expected rows, or the modal is fully visible. Dynamic UI testing requires application-aware waits. Generic sleeps are rarely enough.
AJAX and SPA Applications
Single-page applications and AJAX-heavy applications update the page without full reloads. The browser may show the same URL while the DOM changes in the background. A Selenium test that waits only for page load may continue too early because the important data is still being fetched.
Click
-> AJAX Request
-> Response
-> DOM Updated
For these applications, tests should wait for business-visible readiness. That may mean a status message appears, a table refreshes, a save button becomes enabled, a loading indicator disappears, or a specific API-driven value is displayed. The wait should match what the user or system actually needs before the next action.
Race Conditions
A race condition occurs when the result depends on the order or timing of concurrent operations. In automation, this can happen when parallel scenarios update and read the same record, when cleanup runs while another test still needs data, or when asynchronous application processing has not completed before validation starts.
Thread A -> Updates Record
Thread B -> Reads Record
Race conditions are hard to debug because they may not happen every time. The solution is isolation and proper synchronization. Tests should use independent data, avoid shared mutable state, and wait for real completion conditions. If the application itself has a race condition, the flaky test may be revealing a real product problem.
Shared Test Data
Shared test data is another major cause of flaky Cucumber scenarios. If two scenarios use the same customer, order, account, cart, or database record, they can interfere with each other. One scenario may change the record while another scenario expects the original value. In parallel execution, this becomes much more likely.
Wrong:
Scenario 1 -> Customer1001
Scenario 2 -> Customer1001
Better:
Scenario 1 -> Customer1001
Scenario 2 -> Customer1002
Use unique data, dedicated users, generated identifiers, data pools, or API setup. Store generated values in scenario context and clean them up safely. Test data should be designed for repeatability and parallel execution. A test that passes only when no other test touches the same data is not reliable.
Environment Instability
Sometimes the test is not the only problem. The environment may be unstable. Servers may be slow, databases may be under load, third-party services may be unavailable, deployments may be incomplete, feature flags may differ, or network latency may increase. These conditions can create false failures even when the test logic is reasonable.
Handling environment-related flakiness requires evidence. Monitor application health, response time, server logs, database status, queues, background jobs, and third-party dependencies. If failures cluster during deployments or peak load, the root cause may be environmental. Automation reports should capture enough details to support this analysis.
Browser Differences
Some flaky behavior appears only in specific browsers. A scenario may pass in Chrome and fail in Firefox, or pass locally and fail in a remote browser. Browser rendering, timing, driver versions, default settings, download behavior, alerts, and file handling can differ.
Chrome -> PASS
Firefox -> FAIL
To handle browser-related flakiness, keep browser drivers compatible, use stable locators, avoid browser-specific assumptions, standardize window size, configure downloads deliberately, and capture browser name and version in reports. Cross-browser failures should be investigated as compatibility or framework issues, not dismissed automatically.
API Response Delays
API tests can also be flaky. A request may timeout because the server is busy. A backend process may accept a request but update data later. A test may validate a record before asynchronous processing completes. A dependent service may respond slowly. These conditions create inconsistent results.
Request
-> Server Busy
-> Delayed Response
-> Timeout
API flakiness should be handled with appropriate timeouts, polling for eventual consistency, clear assertions, correlation IDs, and environment monitoring. Do not increase timeouts blindly without understanding the expected service behavior. Slow responses may indicate real performance problems.
Database State
Database state can make tests flaky when previous tests leave behind unexpected records or when current tests assume data exists in a specific condition. A previous scenario may delete a customer, lock a record, change a status, or leave incomplete data. The next scenario then fails because the starting state is not what it expected.
Previous Test -> Deletes Customer
Current Test -> Customer Missing
Each test should prepare or verify its required starting state. Data setup should be explicit, predictable, and safe. Cleanup should remove only what the scenario created. Avoid broad database cleanup in shared environments because it may interfere with parallel tests or other teams.
Test Dependencies
Dependent scenarios are a common source of flakiness. If one scenario logs in, another creates a customer, and another places an order using the same state, the suite becomes order-dependent. If the login scenario fails, unrelated scenarios fail. If scenarios run in parallel, the order may change and the suite breaks.
Wrong:
Login Test
-> Customer Test
-> Order Test
BDD scenarios should be independent. Each scenario should create or prepare the state it needs. Shared setup can be done through hooks, APIs, fixtures, or background steps when appropriate, but one scenario should not depend on another scenario's result. Independent scenarios are easier to run, debug, and parallelize.
Thread Safety Issues
Parallel execution introduces thread-safety risks. Shared WebDriver, shared variables, shared files, shared test data, shared scenario context, and shared report objects can all cause flaky behavior. A test may pass sequentially but fail randomly when several scenarios run together.
Use one WebDriver per thread or scenario, usually with ThreadLocal or dependency injection. Keep scenario context scenario-scoped. Avoid static mutable variables. Generate unique filenames for screenshots and downloads. Use test data that does not conflict. Thread safety is a foundation for reliable parallel execution.
Network Issues
Network problems can cause intermittent failures. Slow internet, VPN latency, DNS failures, temporary outages, proxy issues, or unstable test environment connections may affect UI and API tests. These failures may look like timeouts, page load failures, connection errors, or incomplete responses.
Investigate infrastructure before blaming the test. Compare local and CI results. Check whether failures happen at specific times, machines, networks, or environments. Add logging around request timing and browser navigation where needed. Reliable automation depends on reliable infrastructure.
External Systems
External systems such as payment gateways, email providers, SMS services, identity providers, analytics systems, or third-party APIs can create flaky tests when they are unavailable or slow. If an end-to-end test depends on a real external service, the test result may reflect that service's availability rather than the application behavior under test.
Payment Gateway
-> Unavailable
-> Test Failed
Use mocks, stubs, service virtualization, test doubles, or contract-level testing where appropriate. Full external integration tests still have value, but they should be separated from fast, reliable CI checks. Do not let unstable third-party dependencies break every pipeline run unless the purpose of the test is specifically to validate that integration.
Detecting Flaky Tests
Flaky tests can be detected by tracking repeated intermittent failures. Signs include scenarios that pass after rerun, fail on one machine but not another, fail at different steps each time, fail only in parallel execution, or fail without any related code change. A single failure may not prove flakiness, but patterns over time are strong evidence.
Teams should track flaky tests as real work items. Mark the failure pattern, affected scenario, environment, browser, error message, and suspected cause. A flaky test dashboard or report trend can help identify the most damaging scenarios. The worst flaky tests should be fixed first because they consume the most trust and time.
Debugging Flaky Tests
Debugging flaky tests requires evidence and pattern analysis. Review screenshots, logs, stack traces, browser console errors, network calls, API requests and responses, environment health, execution timing, test data, and parallel execution context. Do not rely only on the exception name. A NoSuchElementException may be caused by a missing element, but it may also be caused by slow loading, wrong data, failed API call, or unexpected page navigation.
Rerun the scenario alone and then in parallel. Run it on the same browser and a different browser. Run it with additional logging. Check whether the failure happens after certain tests. Check whether it correlates with environment load. Flaky test debugging is systematic investigation, not guessing.
Retry Mechanism
Retry mechanisms rerun failed tests automatically. They can reduce noise from known transient infrastructure failures, but they should not be used to hide unstable tests. A test that passes only after retry is still signaling a problem. The retry result should be reported clearly so the team knows which scenarios required reruns.
Failure
-> Retry
-> Pass
Retries are acceptable as a temporary safety net while the root cause is investigated. They are not a long-term fix. Blind retries can mask real product defects and make the suite appear healthier than it is. Use retries sparingly and track them.
Stabilizing UI Tests
To stabilize UI tests, use explicit waits, stable locators, Page Object Model, proper synchronization, independent scenarios, reliable test data, and clear assertions. Avoid Thread.sleep(), fragile XPath expressions, assumptions about animation timing, and validations that depend on unstable visual details.
Page objects should hide low-level Selenium operations and expose meaningful page behavior. Wait utilities should centralize synchronization. Locators should use stable attributes. Screenshots and logs should help diagnose failures. UI tests are naturally more sensitive than API tests, so they need disciplined design.
Stabilizing API Tests
API tests are often faster and more stable than UI tests, but they can still be flaky. Stabilize them with independent test data, reliable authentication, appropriate timeouts, clear response validation, schema checks where useful, correlation IDs, and controlled cleanup. Avoid relying on shared records or hidden environment state.
If the application uses asynchronous processing, design API tests around eventual consistency. Poll for the expected state with a timeout instead of validating immediately after a request when the system is not expected to update instantly. API tests should reflect real system behavior without becoming timing-dependent.
Common Mistake: Blindly Retrying Tests
Blind retries hide problems. If a scenario fails and passes on retry, the build may become green, but the underlying issue remains. Over time, the suite may collect many retry-dependent scenarios. Execution becomes slower, reports become misleading, and real defects can be missed.
Use retry data as diagnostic input. Track which scenarios retry, how often they retry, and why. A frequently retried scenario should be investigated and fixed. A retry mechanism should support stability work, not replace it.
Common Mistake: Ignoring Flaky Tests
Ignoring flaky tests is costly. Today's flaky test can become tomorrow's blocked release. When people stop trusting the suite, automation loses its purpose. A team may start skipping failed tests, rerunning builds repeatedly, or disabling important scenarios because they are inconvenient.
Flaky tests should be triaged. If a scenario is unstable, identify the cause and assign ownership. If the test is low value and expensive to stabilize, remove or redesign it. If it covers critical behavior, prioritize the fix. The worst option is leaving instability unexplained.
Common Mistake: Weak Assertions
Weak assertions can make tests flaky or misleading. A test that validates unstable UI text, timing-dependent counts, random ordering, or implementation details may fail even when the business behavior is correct. Conversely, a test with too few assertions may pass without proving anything useful.
Assertions should validate meaningful business outcomes. For UI tests, assert visible outcomes that matter to the user. For API tests, assert status, schema, key fields, and business state. For database checks, assert only what is necessary and stable. Strong assertions improve confidence and reduce false failures.
Best Practices
Keep tests independent. Use explicit waits instead of fixed delays. Build stable locators. Use unique test data. Reset or isolate environment state. Remove dependencies between scenarios. Review intermittent failures immediately. Use retries only for known transient issues while investigating the root cause. Monitor flaky test trends. Continuously refactor unstable tests.
Also keep reports useful. A flaky test cannot be fixed without evidence. Capture screenshots for UI failures, request and response details for API failures, logs for execution flow, browser and environment information for cross-browser issues, and scenario identifiers for parallel failures. Good evidence shortens investigation time.
Enterprise Strategy for Flaky Tests
An enterprise flaky-test strategy treats instability as a quality problem. The process should be clear: detect the failure, analyze the evidence, classify the root cause, fix the issue, verify stability, and monitor recurrence. The objective is to eliminate flakiness, not simply mask it.
Scenario
-> Execution
-> Failure
-> Analyze
-> Root Cause
-> Fix
-> Stable Test
-> Reliable CI/CD
Teams can define a flaky-test policy. For example, a scenario that fails intermittently three times in a week must be reviewed. A critical flaky scenario must be fixed before release. A low-value flaky scenario may be removed or replaced with a better test at a lower layer. Policies help teams act consistently.
Flaky vs Stable Tests
A flaky test gives random pass or fail results, depends on timing, uses shared data, relies on fragile locators, and is hard to trust. A stable test gives consistent results, uses proper synchronization, has isolated data, relies on stable locators, and produces predictable outcomes. Stability is not accidental. It is designed into the test and the framework.
| Flaky Test | Stable Test |
|---|---|
| Random pass or fail | Consistent results |
| Timing-dependent | Proper synchronization |
| Shared data | Isolated data |
| Fragile locators | Stable locators |
| Hard to trust | Reliable execution |
| Frequent reruns | Predictable outcomes |
Root Cause Categories
Classifying flaky tests helps teams fix them faster. A failure may belong to the test code category, framework category, data category, application category, environment category, or infrastructure category. A synchronization bug in a page object is a framework or test issue. A real intermittent backend delay may be an application or environment issue. A shared customer record is a data issue. A cloud browser timeout may be infrastructure-related.
Classification prevents blame-based debugging. The goal is not to prove that automation or application code is at fault. The goal is to find the real cause. A good flaky-test review asks what changed during execution, what resource was shared, what condition was assumed, and what evidence confirms the root cause.
Using Reports to Reduce Flakiness
Reports should help teams understand flaky failures. A useful report includes scenario name, tags, browser, environment, thread, failed step, screenshot, logs, API request and response details when relevant, and assertion messages. Without this information, a flaky failure may require rerunning just to collect clues.
For parallel execution, reports should also preserve scenario-specific evidence. Screenshots should not overwrite each other. Logs should not mix without context. API responses should be attached to the correct scenario. Better reporting does not fix flakiness by itself, but it makes root-cause analysis much faster.
When to Quarantine a Flaky Test
Sometimes a flaky test must be temporarily quarantined. This means removing it from blocking CI execution while keeping it visible for investigation. Quarantine should be used carefully. It is useful when a flaky test blocks teams repeatedly and the fix requires time. It is harmful when it becomes a place where unstable tests are forgotten.
A quarantined test should have an owner, reason, tracking ticket, and review date. Critical coverage should be replaced if possible. The goal is to restore reliable coverage, not permanently reduce the suite. Quarantine is a management tool, not a solution.
Flaky Test Triage Process
A practical flaky-test triage process keeps the team from treating every intermittent failure as a mystery. Start by collecting the failure evidence: scenario name, tags, environment, browser, thread count, execution time, stack trace, screenshot, logs, API details, and data used. Then classify the failure into a likely category such as synchronization, locator, data, environment, thread safety, backend delay, external dependency, or real product defect.
After classification, decide the immediate action. A critical customer journey may need same-day repair. A low-value duplicate scenario may be removed. A useful but unstable test may be quarantined briefly while the root cause is fixed. The important part is ownership. A flaky test without an owner usually remains flaky. A flaky test with an owner, evidence, category, and target fix date is much more likely to be resolved.
Preventing Flakiness During Test Design
The best way to handle flaky tests is to prevent them during design. Before writing automation code, ask whether the scenario is independent, whether the data can be created reliably, whether the UI elements have stable locators, whether the application has asynchronous behavior, and whether the validation checks a stable business outcome. These questions catch many problems before code is written.
Good BDD scenario design also reduces flakiness. Scenarios should validate one behavior, avoid unnecessary UI steps, and avoid depending on previous scenarios. If a scenario tries to cover login, search, checkout, payment, confirmation, email, and database validation in one long flow, it has many possible failure points. Smaller behavior-focused scenarios are easier to stabilize and debug.
Flaky Tests and Team Trust
Flaky tests are not only a technical problem. They are also a trust problem. Developers trust automated tests when failures are meaningful. Testers trust them when reports provide clear evidence. Release owners trust them when green builds mean real confidence. Flaky tests weaken that trust because they make every failure negotiable.
Restoring trust requires consistency. The team should not accept repeated random failures as normal. Flaky scenarios should be tracked, reviewed, fixed, removed, or quarantined with discipline. Over time, this changes the culture around automation. A stable suite becomes a reliable engineering tool instead of a source of pipeline noise.
Flaky Test Metrics
Metrics help teams understand whether flakiness is improving or getting worse. Useful metrics include flaky failure count, retry count, most unstable scenarios, failure rate by browser, failure rate by environment, average time to fix flaky tests, and number of quarantined scenarios. These metrics should be reviewed regularly, especially for critical regression and smoke suites.
Metrics should drive action, not blame. If most flaky failures come from synchronization, improve wait utilities and page readiness checks. If failures cluster around shared data, improve test data management. If one environment produces most failures, investigate infrastructure. Flaky-test metrics help the team invest effort where it will have the biggest stability impact.
Interview-Ready Summary
Flaky tests are automated tests that produce inconsistent results without changes to the application or test code. Common causes include synchronization problems, hardcoded waits, unstable locators, dynamic elements, shared test data, environment instability, browser differences, API delays, database state issues, race conditions, test dependencies, network problems, external systems, and thread-safety problems.
The best way to handle flaky tests is to identify and eliminate the root cause rather than relying on blind retries. Stable Cucumber frameworks use explicit waits, independent scenarios, unique test data, robust locators, scenario-scoped context, thread-safe driver management, reliable reporting, useful logs, and proper cleanup. Reducing flaky tests increases confidence in automation, improves CI/CD reliability, and speeds up software delivery.
Golden Rules
Treat every flaky test as a defect in the test, framework, data, application, or environment until proven otherwise. Use explicit waits, stable locators, and independent test data to minimize instability. Avoid Thread.sleep() and unnecessary retries as long-term solutions. Design tests to be independent, deterministic, and thread-safe.
Investigate recurring intermittent failures promptly to maintain trust in the automation suite. Use retries only as temporary protection while collecting evidence and fixing the underlying cause. The practical takeaway is clear: handling flaky tests is not about hiding failures. It is about restoring automation as a reliable signal for product quality.