In Selenium, what does AjaxElementLocatorFactory change about a page object's lookups, and where does its retry stop helping?
answer
- Every field gets the same number
- The locator loops instead of failing once
- Roughly four attempts per second by default
- Presence in the DOM ends the loop
- An empty list is not a failure
basics
~20 sIt gives every proxied field a built-in retry: the locator re-runs the find roughly every 250 milliseconds until your timeout. The retry only proves the node is in the DOM, and a list lookup returns empty instead of failing.
solid answer
~40 s`new AjaxElementLocatorFactory(driver, 10)` passed to `PageFactory.initElements` makes every field's locator an `AjaxElementLocator` rather than a `DefaultElementLocator`. Its `findElement()` polls — `sleepFor()` returns 250 milliseconds by default — until the element is in the DOM or the timeout expires, then throws `NoSuchElementException` whose message begins `Timed out after N seconds`. Two things surprise people on a taxi-dispatch board. First, `isElementUsable(WebElement)` returns `true` for anything present in the DOM, so a hidden surge banner satisfies the retry; you override that method to demand `isDisplayed()`. Second, `findElements()` polls until the list is non-empty and, on expiry, returns an empty list rather than throwing — so a check that no rides are waiting burns the full timeout and then passes. In Selenium 4 the timeout is per field lookup and applies to every field on the page object.
code
java · 19 linesimport org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.AjaxElementLocatorFactory;
public class DispatchBoardPage {
@FindBy(css = "[data-role='surge-banner']")
private WebElement surgeBanner;
public DispatchBoardPage(WebDriver driver) {
PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
}
public String surgeText() {
return surgeBanner.getText();
}
}go deeper
Know that a page object can be given a timeout when it is built, so its fields retry a failed lookup instead of giving up on the first attempt.
Explain that AjaxElementLocatorFactory swaps in AjaxElementLocator, which polls roughly every 250 milliseconds and reports failure as a NoSuchElementException naming the timeout.
Show the operating consequences: an absence check burns the whole timeout and still returns an empty list, and a page object of slow fields can spend the timeout once per field.
Own the framing. This is a blanket per-field retry with one number for a whole page object and no way to express different tolerances, so be ready to say what the mechanism cannot cover.
## Wiring it in `AjaxElementLocatorFactory` is an `ElementLocatorFactory`, so it is passed through the `PageFactory.initElements(ElementLocatorFactory, Object)` overload rather than the usual context one. Its constructor takes exactly two things — `AjaxElementLocatorFactory(SearchContext searchContext, int timeOutInSeconds)` — and its `createLocator(Field)` returns a new `AjaxElementLocator` for every field of the page object, each carrying that same timeout. On a taxi-dispatch board, `new AjaxElementLocatorFactory(driver, 10)` therefore hands a ten-second retry to the surge banner, the assign button and the waiting-ride list alike; the number is **per lookup, not per page object**, and it is not tunable per field from the constructor. This is Selenium 4 behaviour, in `org.openqa.selenium.support.pagefactory`. ## How the poll actually runs `AjaxElementLocator` extends `DefaultElementLocator`, so it inherits the `By`, the caching flag and the plain find; what it adds is a retry loop built on `SlowLoadableComponent` and a `java.time.Clock`: 1. `findElement()` creates an internal slow-loading wrapper with `Duration.ofSeconds(timeOutInSeconds)`. 2. Each cycle calls the parent's `findElement()` — a real find command — and then `isElementUsable(element)`. 3. A failure throws internally, the loop sleeps `sleepFor()` milliseconds, and tries again. 4. On success the located element is returned; on expiry the loop's error is converted into a `NoSuchElementException` whose message begins `Timed out after N seconds`. Two details are worth memorising because interviewers ask for them: - **`sleepFor()` returns 250 by default** and is `protected`, so a subclass can slow or speed the poll. - The **retry re-runs the locator**, not a cached handle, which is why the class javadoc explicitly recommends avoiding XPath here: an expensive expression is re-evaluated on every cycle for the whole timeout. ## Present is not the same as usable `isElementUsable(WebElement element)` returns `true` unconditionally in the base class. The consequence is precise and often missed: the poll ends the instant the node exists in the **DOM**, whether or not it is rendered, visible, enabled or on screen. A dispatch board that ships its surge banner in the initial HTML with `display: none` satisfies the retry immediately, and the very next call on that field acts on a hidden element. The class documents the fix in its own javadoc — override the method: ```java ElementLocator locator = new AjaxElementLocator(driver, field, 10) { @Override protected boolean isElementUsable(WebElement element) { return element.isDisplayed(); } }; ``` Because the constructor cannot express this, using it means writing your own `ElementLocatorFactory` that returns such locators. ## Element and list time out differently | | `findElement()` | `findElements()` | |---|---|---| | loop ends early when | one element is found and usable | the list is non-empty and every element is usable | | behaviour on expiry | throws `NoSuchElementException` | returns an empty `ArrayList` | | message you get | `Timed out after N seconds` plus the cause | none — the caller just sees zero matches | | a "nothing is there" check | fails loudly | passes, after burning the full timeout | That last row is the single most useful thing to know about this factory in production. A test asserting that no rides are waiting does not fail — it waits the whole ten seconds, gets an empty list, and reports green. The suite gets slower without getting more informative. ## What it costs on a real board - The timeout is spent **per field, on that field's first use**, so a page object touching five slow fields can spend five timeouts in one method. - The retry is **blanket**: every field on the page object gets it, including ones that are always present and never needed it. - The poll issues a find command roughly every 250 milliseconds for the duration, which is real traffic against the browser or a remote end. - Negative assertions are systematically penalised, as the table above shows. - The wait is invisible in the test's own code: the only evidence a field polled for ten seconds is the wall-clock time of the method that touched it. - Caching still applies — `@CacheLookup` on a field means the inherited caching in `DefaultElementLocator` short-circuits the retry entirely once a value is stored. ## The judgment an interviewer is listening for The factory is a genuinely useful default for a page object whose fields arrive asynchronously, and it costs one line at construction. What it cannot do is express *different* tolerances for different fields, distinguish presence from readiness without a subclass, or make an absence check cheap. A strong answer names the mechanism accurately — a per-field, 250-millisecond poll over the same locator, ending on DOM presence — and then says where its shape stops matching what the page object actually needs.
- How would you make AjaxElementLocatorFactory wait for visibility rather than DOM presence?You cannot do it through the constructor. Override `AjaxElementLocator.isElementUsable(WebElement)` to return `element.isDisplayed()` and hand `PageFactory` your own `ElementLocatorFactory` that creates those locators. The base implementation returns `true` unconditionally, which is why the poll ends the moment the node exists in the DOM.
- Why does the Selenium javadoc warn against XPath with AjaxElementLocator?Because the locator polls: each cycle re-runs the same `By` against the page, so an expensive expression is paid repeatedly rather than once. The class documentation says users should avoid locating elements by XPath for exactly that reason. The cost scales with the timeout you configured and with how often the field is first touched.
- Does the timeout you pass apply per field or per page object?Per lookup. `createLocator(Field)` builds one `AjaxElementLocator` per field, each carrying the same `timeOutInSeconds`. A page object with five slow fields can therefore spend five times the timeout inside one method, because the polls run one after another as each field is first used.
saying these in an interview costs you the question
- Says the factory waits once at initElements instead of per lookup
- Thinks the poll checks visibility rather than presence in the DOM
- Believes a list field throws when its retry times out
- Assumes the timeout is one shared budget across all fields
- Thinks passing the factory changes the proxy rather than the locator