WebDriverWait
WebDriverWait is Selenium's primary explicit-wait utility that repeatedly evaluates a specific condition until it is satisfied or a timeout occurs. It is the de facto standard for synchronizing Selenium tests with modern, asynchronous web applications.
Understanding WebDriverWait in Selenium
WebDriverWait is one of the most important synchronization utilities in Selenium WebDriver because it helps automation code move at the speed of the application instead of the speed of the test script. Modern web pages rarely become ready in one simple step. A page may load its HTML first, then fetch data from an API, then render components through JavaScript, then enable a button only after validation, and finally remove a loading overlay. If Selenium tries to click, type, or read too early, the test fails even though the application may work correctly for a real user. WebDriverWait solves this problem by waiting for a specific condition before the test continues.
The key idea behind WebDriverWait is simple: do not wait for a fixed amount of time; wait for the state you actually need. If the next action is to type into a username field, the field should be visible and ready. If the next action is to click a login button, the button should be clickable. If the next verification is a success message, the text should appear on the page. This condition-based style makes tests faster when the application is fast and more reliable when the application is temporarily slow.
In real Selenium frameworks, WebDriverWait is usually wrapped inside utility methods or Page Object Model actions. Test methods should not be filled with repeated wait code. Instead, page methods such as login, searchForProduct, submitOrder, openReport, or switchToPaymentFrame should contain the waits needed for those actions. This keeps tests readable and ensures that synchronization is handled consistently across the automation suite.
Why WebDriverWait Matters in Modern Web Automation
Applications Are Asynchronous
Older web pages often loaded as full-page responses. Once the page loaded, most elements were available. Modern applications are different. They use AJAX calls, client-side routing, component rendering, lazy loading, animations, overlays, and background data refreshes. A page may look almost ready while important elements are still disabled or hidden. Selenium does not automatically understand the business meaning of readiness. It only executes commands against the browser. WebDriverWait lets the automation code express what readiness means for a particular step.
For example, a dashboard may show a table immediately, but the table rows may arrive after an API call. A test that checks row count without waiting may fail with zero rows. A checkout page may show the Pay button, but the button may remain disabled until payment details are validated. A test that clicks immediately may fail with an element not interactable error. WebDriverWait handles these cases by polling until the expected state becomes true.
Flaky Tests Usually Come from Timing Gaps
Many Selenium failures are not caused by incorrect assertions or broken application logic. They are caused by timing gaps between the test and the browser. The same test may pass locally and fail in CI because the CI machine is slower. It may pass in Chrome and fail in another browser because rendering timing differs. It may pass on one network and fail on another because backend responses take longer. WebDriverWait reduces this flakiness by allowing the test to wait intelligently for the browser state rather than assuming a fixed speed.
This is why experienced automation engineers consider WebDriverWait a core framework feature, not an optional convenience. Without it, a Selenium suite tends to become unstable as the application grows. With it, tests can remain stable even when pages are dynamic, data is loaded asynchronously, and components re-render during user interactions.
How WebDriverWait Works Internally
Timeout, Polling, and Condition Evaluation
WebDriverWait works by repeatedly evaluating a condition until that condition returns a successful result or the timeout expires. The timeout is the maximum amount of time Selenium is allowed to wait. Polling is the repeated checking that happens during that timeout. The condition is the rule that decides whether the test can continue. If the condition is met before the timeout, WebDriverWait stops immediately and returns the result. It does not keep waiting unnecessarily.
This makes WebDriverWait different from Thread.sleep. Thread.sleep always pauses for the full duration. If a test sleeps for ten seconds, it waits ten seconds even if the element became ready after one second. WebDriverWait can continue as soon as the condition is satisfied. This gives the framework both reliability and efficiency. The test gets protection against slow loading without paying a fixed delay every time.
ExpectedConditions and Custom Conditions
Selenium provides many built-in conditions through ExpectedConditions. These cover common needs such as waiting for an element to be present, visible, clickable, selected, invisible, or updated with specific text. They also cover browser context changes such as alerts, frames, windows, titles, and URLs. Built-in conditions are useful because they are readable and express common UI readiness states clearly.
WebDriverWait also supports custom conditions. In Java, this is often written as a lambda expression. A custom condition is useful when the built-in conditions are not specific enough. For example, a framework may wait until a product list contains more than five items, a button becomes enabled after validation, a JavaScript ready state becomes complete, or a custom loading flag disappears from the DOM. Custom conditions allow WebDriverWait to match the actual behavior of the application.
Common WebDriverWait Conditions
Presence, Visibility, and Clickability
Presence means the element exists in the DOM. It may still be hidden, covered, disabled, or outside the visible area. Presence is useful when the test only needs to confirm that an element has been added to the page or when a later operation will handle visibility separately. Visibility means the element is present and displayed to the user. It is a better choice before reading visible text or typing into fields. Clickability means the element is visible and enabled so Selenium can attempt a click.
Choosing between these conditions is one of the most important WebDriverWait skills. Waiting for presence when the test needs clickability is a common mistake. The element may exist in the DOM while a loading overlay still blocks it. In that case, the wait passes but the click fails. A stable test waits for the exact condition required by the next action. If the next step is a click, wait for clickability. If the next step is reading displayed text, wait for visibility. If the next step is confirming DOM creation, presence may be enough.
Text, Attribute, and State Changes
Many applications update text and attributes after user actions. A status may change from Pending to Success. A button may change from disabled to enabled. A class name may change after selection. A field may receive a generated value. WebDriverWait can wait for these state changes directly instead of waiting for the whole page or sleeping blindly. This makes tests more precise and easier to debug.
Waiting for text is useful after form submissions, search results, notifications, progress updates, and workflow transitions. Waiting for attributes is useful when UI state is represented through disabled, aria-expanded, aria-selected, class, value, or data attributes. These waits are often closer to the application's real behavior than a generic visibility wait. They help the test verify that the page has reached the correct state, not merely that an element exists.
Alerts, Frames, Windows, Titles, and URLs
WebDriverWait is not limited to elements. It can also wait for browser-level changes. When an action opens an alert, the test should wait for the alert before accepting or dismissing it. When an iframe loads dynamically, the test should wait for the frame and switch to it safely. When a click opens a new tab or window, the test can wait for the number of windows to change. When navigation happens, the test can wait for the URL or title to contain the expected value.
These conditions are especially useful in payment pages, authentication flows, document previews, admin panels, and single-page applications. A test that switches context too early often fails with confusing errors. Waiting for the correct context first makes the next step predictable. Good Selenium synchronization includes not only element readiness but also browser context readiness.
WebDriverWait vs Other Waiting Approaches
WebDriverWait vs Implicit Wait
Implicit wait is a global setting that tells Selenium how long to wait when locating elements. It applies to element lookup. It does not know whether an element is visible, clickable, selected, updated with text, or free from overlays. WebDriverWait is more specific because it waits for a defined condition. That is why WebDriverWait is preferred in robust automation frameworks.
Mixing implicit waits and WebDriverWait is often discouraged because it can create confusing timing behavior. An explicit wait condition may call findElement internally, and the implicit wait can add extra delay to each polling attempt. This can make timeouts harder to predict. A cleaner framework usually keeps implicit wait low or disabled and relies on explicit waits where synchronization is actually needed.
WebDriverWait vs Thread.sleep
Thread.sleep is a fixed pause. It does not check the page. It does not understand whether the element is ready. It either waits too long when the page is fast or not long enough when the page is slow. WebDriverWait is condition-based. It keeps checking until the expected state appears or the timeout expires. This makes it a much better fit for dynamic applications.
Thread.sleep may still appear in temporary debugging or rare demonstration code, but it should not be the foundation of a Selenium framework. If a test requires a pause because an animation or system process has no observable signal, the better solution is usually to find a reliable signal. That signal may be an overlay disappearing, an attribute changing, a network-driven message appearing, or a backend status becoming visible in the UI. WebDriverWait works best when the framework waits for real application state.
Using WebDriverWait in Page Object Model
Keep Synchronization Inside Page Methods
Page Object Model works best when page classes hide implementation details from test classes. Locators, waits, and browser interactions belong inside page methods. A test should describe the user flow, not the timing mechanics behind every element. For example, a test can call loginPage.enterUsername, loginPage.enterPassword, and loginPage.submitLogin. Inside those methods, the page object can wait for fields to be visible, the button to be clickable, and the dashboard to appear after submission.
This design reduces duplication. If every test writes its own WebDriverWait for the same login button, any UI change requires many edits. If the wait is centralized in the page object, the change happens once. It also improves consistency. All tests wait for the same readiness condition before using the same element. This makes the framework easier to maintain and easier to troubleshoot.
Create Reusable Wait Utilities
Large frameworks usually create helper methods such as waitForVisible, waitForClickable, waitForText, waitForInvisibility, waitForUrlContains, waitForFrame, and waitForAlert. These helpers reduce boilerplate and make page methods easier to read. They also allow the team to standardize timeout values, polling behavior, ignored exceptions, logging, and error messages in one place.
A reusable utility should not hide the meaning of the wait. Method names should be clear and specific. A helper named waitForElement is too vague because it does not explain whether it waits for presence, visibility, or clickability. A helper named waitForClickable is clearer because it describes the exact state required. Clear names help new automation engineers use the framework correctly.
Handling Stale Elements with WebDriverWait
Why Stale Elements Happen
StaleElementReferenceException occurs when Selenium has a reference to an element that is no longer attached to the current DOM. This happens frequently in applications built with React, Angular, Vue, and other dynamic UI frameworks. A component may render, disappear, and render again after a state update. The element may look the same to the user, but internally the browser has replaced the DOM node. Selenium's old reference is no longer valid.
WebDriverWait helps when conditions locate the element fresh during polling. Waiting with a By locator is often safer than storing a WebElement too early. For example, waiting for visibilityOfElementLocated can find the latest matching element when the DOM settles. A framework can also configure waits to ignore stale element exceptions during polling when a re-render is expected. This allows the wait to continue until the stable version of the element is available.
Do Not Catch Stale Exceptions Randomly
A common bad practice is to catch StaleElementReferenceException everywhere and retry actions blindly. This may hide real problems and make tests harder to understand. A better approach is to design waits around the expected page transition. If clicking Save re-renders the result table, wait for the old spinner to disappear and the new table state to become visible. If selecting a dropdown option replaces part of the form, wait for the new field or new attribute value. WebDriverWait should describe the expected stable state rather than simply retrying without context.
Timeouts, Polling, and Diagnostics
Choosing Practical Timeout Values
Timeout values should reflect real application behavior. Very short timeouts can cause false failures in CI or slower environments. Very long timeouts make failures slow and can hide performance issues. Many teams use a normal timeout for common UI readiness, a short timeout for optional checks, and a longer timeout for known slow operations such as report generation or heavy data loading. The important point is to choose timeouts intentionally, not randomly.
Polling frequency also matters. WebDriverWait checks conditions repeatedly, and the default polling interval is usually good for most cases. For special cases, FluentWait can customize polling and ignored exceptions. The goal is not to poll as fast as possible. The goal is to check often enough to be responsive without creating unnecessary browser pressure or noisy logs.
Make Timeout Failures Easy to Investigate
A WebDriverWait timeout should provide useful information. A message like timed out after ten seconds is less helpful than a message that says the checkout button did not become clickable after ten seconds on the payment page. Good frameworks include the locator, expected condition, page name, action name, current URL, and screenshot path in failure logs. This turns a timeout from a vague failure into a useful debugging signal.
Diagnostics are especially important in CI because nobody watches the browser during execution. Screenshots may reveal that a loading overlay was still present, an error banner appeared, the user was redirected to a login page, or the expected data did not load. Browser console logs and network details can provide even more context. WebDriverWait improves stability, but good reporting makes wait failures actionable.
WebDriverWait in Continuous Integration
Why CI Exposes Weak Synchronization
A Selenium test that looks stable on a developer laptop can still fail in a continuous integration environment. CI machines often run with fewer resources, headless browsers, shared runners, remote grids, and parallel test execution. Pages may render more slowly, network calls may take longer, and JavaScript execution may be less predictable. These differences do not necessarily mean the application is broken. They often reveal that the automation code was depending on local timing rather than explicit application state.
WebDriverWait is critical in CI because it gives the test a controlled way to adapt to the environment. If a button becomes clickable after one second locally and after five seconds in CI, a properly written wait handles both cases without changing the test. If the button never becomes clickable, the wait fails with a meaningful timeout. This behavior is much better than using a fixed sleep, which either slows down every run or still fails when the environment is slower than expected.
Separating Synchronization Problems from Real Defects
Good use of WebDriverWait helps teams decide whether a failure is an automation issue or a product issue. If a wait for visibility fails because the element is not in the DOM, the locator may be wrong, the page may not have navigated correctly, or the application may not have returned the expected component. If a wait for clickability fails while the element is visible, an overlay, disabled state, validation rule, or layout problem may be blocking interaction. If a wait for text fails, the application may have shown a different message or failed to complete the requested action.
This diagnostic value is one reason WebDriverWait should be used with precise conditions. A generic wait makes failures vague. A precise wait explains the expected state. When the report says that the success toast did not become visible after saving a customer record, the team has a clear starting point. When the report only says that the test timed out, investigation takes longer. Well-written waits turn synchronization into useful evidence.
Designing Waits for Parallel Execution
Parallel execution adds another layer of complexity. Multiple tests may run at the same time, each with its own browser session, data, and timing. WebDriverWait should be tied to the correct WebDriver instance and should not rely on shared mutable state. Wait utilities must be thread-safe when tests run in parallel. A static wait object shared across drivers can cause confusing failures because one test may accidentally wait against the wrong browser session.
The safer approach is to create wait helpers that use the driver owned by the current page object or test context. Each browser session should have its own WebDriverWait behavior. This keeps waits isolated and makes parallel execution more predictable. WebDriverWait cannot fix poor test data isolation or shared browser state, but it works very well when the framework is designed with clean session boundaries.
Best Practices for WebDriverWait
Match the Wait to the Next Action
The most important rule is to wait for the condition required by the next operation. Before clicking, wait for clickability. Before typing, wait for visibility and enabled state if needed. Before reading text, wait for text to be present or for the element to be visible. Before switching to a frame, wait for the frame to be available. Before continuing after a save action, wait for the success message, URL change, or spinner disappearance that represents completion.
This keeps tests aligned with user behavior. A real user does not interact with hidden elements or disabled buttons. A real user waits for the page to show a result or confirmation. WebDriverWait lets the automation framework model that behavior. When waits match user-visible readiness, tests become easier to understand and less likely to fail randomly.
Avoid Overusing Generic Waits
Generic waits can make a framework look simple while hiding weak synchronization. A method that waits for the page to load after every action may not solve real problems in single-page applications, where the document ready state may be complete while data is still loading. A method that waits for any element to appear does not guarantee that the next action is safe. Specific waits are better because they describe the exact state the page must reach.
Overusing waits can also slow the suite and make failures unclear. Every wait should have a reason. If a test waits for a loader to disappear, the loader should be related to the action just performed. If a test waits for a table row, that row should be the result being verified. Well-designed waits are intentional, not decorative. They make the test more precise instead of simply adding delay.
Interview Perspective on WebDriverWait
Short Interview Answer
WebDriverWait is Selenium's explicit wait utility. It waits for a specific condition, such as visibility, clickability, alert presence, frame availability, or text update, until the condition is met or the timeout expires.
Detailed Interview Answer
In Selenium WebDriver, WebDriverWait is used to synchronize automation scripts with dynamic web applications. It repeatedly polls the browser for a condition and proceeds as soon as that condition becomes true. It is more precise than implicit wait and better than Thread.sleep because it is condition-based, efficient, and scoped to the specific action or verification. In real frameworks, it is usually wrapped in reusable wait utilities and used inside Page Object Model methods.
Real Project Explanation
In a real project, I would use WebDriverWait for actions such as waiting for a login button to become clickable, waiting for a loading spinner to disappear after search, waiting for a success message after form submission, waiting for a payment iframe before entering card details, and waiting for a URL change after navigation. I would keep those waits inside page objects or shared utilities so test cases remain clean and synchronization remains consistent.
The key takeaway is that WebDriverWait is the standard way to handle explicit waits in Selenium. It improves reliability by waiting for meaningful browser and application states instead of fixed time delays. When combined with strong locators, Page Object Model design, useful diagnostics, and disciplined timeout choices, WebDriverWait removes a large portion of Selenium flakiness and makes automation suites easier to maintain.
In Selenium, almost all robust frameworks rely on WebDriverWait (often wrapped by utilities) to eliminate timing-related flakiness.
1. What Is WebDriverWait
Definition:
WebDriverWait is a specialized explicit wait that:
- Polls the DOM at regular intervals
- Evaluates a condition (via ExpectedConditions or a custom lambda)
- Proceeds immediately when the condition is met
- Throws TimeoutException if the condition is not met within the timeout
Key idea: Wait for a condition, not for time.
2. Why WebDriverWait Is Essential
Modern UIs exhibit:
- AJAX requests
- Dynamic enable/disable states
- Loading overlays and spinners
- Virtual DOM re-renders (React/Angular)
WebDriverWait ensures Selenium interacts only when the UI is ready, preventing:
- ElementNotInteractableException
- ElementClickInterceptedException
- StaleElementReferenceException (many cases)
- Random, environment-specific failures
3. How WebDriverWait Works (Conceptual)
- Start timer
- Poll the DOM at intervals
- Evaluate the wait condition
- If condition is true means proceed immediately
- If timeout expires means throw TimeoutException
This makes waits efficient (no unnecessary sleeping).
4. Scope and Precision
- Scoped: Applies to a specific condition and element
- Precise: You choose what to wait for (visibility, clickability, text, attribute)
- Non-global: Does not affect other element lookups
Rule: Use WebDriverWait exactly where synchronization is required.
5. Common Conditions Used with WebDriverWait
Typical conditions include waiting for:
- Element presence in DOM
- Element visibility
- Element clickability
- Text to be present
- Attribute value to change
- Alert presence
- Frame/window availability
Choosing the correct condition is as important as the wait itself.
6. WebDriverWait vs Implicit Wait
| Aspect | WebDriverWait | Implicit Wait |
|---|---|---|
| Scope | Specific | Global |
| Type | Condition-based | Time-based |
| Visibility/clickability | Valid: | Invalid: |
| Control | Fine-grained | Coarse |
| Recommended | Valid: Yes | Invalid: Limited |
Enterprise rule:
Do not mix implicit waits with WebDriverWait.
7. Where to Use WebDriverWait (Real Projects)
Use WebDriverWait when:
- Clicking a button after validation/AJAX
- Waiting for spinners/overlays to disappear
- Reading messages that appear after actions
- Switching frames/windows dynamically
- Handling elements that re-render
8. Best Practices (Very Important)
- Prefer short, realistic timeouts (e.g., 5-15 seconds)
- Match the condition to the action (visibility vs clickability)
- Place waits inside page methods (POM)
- Centralize common waits in utilities
- Avoid waiting for presence when you need interaction
9. Common Mistakes
- Waiting for presence when element must be clickable
- Using large timeouts everywhere "just in case"
- Adding waits in test code instead of page code
- Mixing implicit and explicit waits
- Treating waits as validation (they are not)
10. Framework-Level Usage (POM)
In mature frameworks:
- WebDriverWait is wrapped by helper methods
- Page methods expose business actions
- Tests remain clean and readable
This separation keeps synchronization consistent and maintainable.
11. Interview Perspective
Short Answer
WebDriverWait is an explicit wait that waits for a specific condition to be met before proceeding.
Real-Time Answer
In Selenium, WebDriverWait is used to synchronize tests with dynamic UI behavior by waiting for conditions like visibility or clickability of elements. It is preferred over implicit waits because it is precise, condition-based, and avoids unnecessary delays.
12. Key Takeaway
- WebDriverWait is the core synchronization tool
- Condition-based and efficient
- Prevents most flaky failures
- Use it intentionally and centrally
If you master WebDriverWait, you eliminate most Selenium instability.
13. Practical WebDriverWait Examples (Interview Ready)
Valid: 1. Wait for Visibility of Element
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement user = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("username"))
);
user.sendKeys("admin");
Valid: 2. Wait for Clickable Element
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.elementToBeClickable(By.id("loginBtn"))).click();
Valid: 3. Wait for Presence in DOM
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".dashboard")));
Valid: 4. Wait for Invisibility of Spinner
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("spinner")));
Valid: 5. Wait for Text to Be Present
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.id("status"), "Success"));
Valid: 6. Wait for Exact Text
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.textToBe(By.id("orderStatus"), "Delivered"));
Valid: 7. Wait for Attribute Value
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.attributeToBe(
By.id("submitBtn"), "disabled", "false"));
Valid: 8. Wait for Frame and Switch
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("paymentFrame"));
driver.findElement(By.id("cardNumber")).sendKeys("4111111111111111");
Valid: 9. Wait for Alert
driver.findElement(By.id("deleteBtn")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();
Valid: 10. Wait for URL Contains
driver.findElement(By.id("goDashboard")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.urlContains("dashboard"));
Valid: 11. Wait for URL to Be Exact
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.urlToBe("https://example.com/home"));
Valid: 12. Wait for Title Contains
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.titleContains("Home"));
Valid: 13. Wait for Title Exact Match
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.titleIs("Login Page"));
Valid: 14. Wait for Number of Windows
driver.findElement(By.id("openTab")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.numberOfWindowsToBe(2));
Valid: 15. Wait for Checkbox to Be Selected
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.elementToBeSelected(By.id("terms")));
Valid: 16. Wait for All Elements Visible
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
List<WebElement> products = wait.until(
ExpectedConditions.visibilityOfAllElementsLocatedBy(By.className("product"))
);
Valid: 17. Wait for Presence of Multiple Elements
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
List<WebElement> rows = wait.until(
ExpectedConditions.presenceOfAllElementsLocatedBy(By.tagName("tr"))
);
Valid: 18. Custom Wait - List Size Greater Than 5
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(d -> d.findElements(By.className("item")).size() > 5);
Valid: 19. Custom Wait - Button Enabled
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
By checkout = By.id("checkoutBtn");
wait.until(d -> d.findElement(checkout).isEnabled());
d.findElement(checkout).click();
Valid: 20. Wait for DOM Ready (JavaScript Condition)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(d -> ((JavascriptExecutor) d)
.executeScript("return document.readyState")
.equals("complete"));
🚀 Framework-Level Best Practice
Create reusable utility:
public WebElement waitForVisible(By locator, int seconds) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(seconds));
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
Usage:
waitForVisible(By.id("loginBtn"), 10).click();