skip to content

In Selenium's PageFactory, why does a missing element fail at the first method call, not at initElements?

level: juniorimportance: should knowfreq 56%

answer

  1. Nothing has talked to the browser yet
  2. The field holds a stand-in
  3. Reflection, not a find command
  4. The handler locates before it forwards
  5. Construction cannot know the element is gone

basics

~20 s

PageFactory.initElements only installs a lazy proxy in each field; it never contacts the browser. The proxy runs the real lookup on every method call, so a missing element raises NoSuchElementException at the use site instead.

solid answer

~40 s

`PageFactory.initElements` is pure reflection. It walks the page object's declared fields and, for each `WebElement` or annotated `List<WebElement>` field, builds an `ElementLocator` and assigns a `java.lang.reflect.Proxy` — no `findElement` command is ever sent. The lookup lives in `LocatingElementHandler.invoke`, which calls `locator.findElement()` first and only then forwards your method to the real element, so the round trip happens on `pickupWindow.click()`, not on construction. That is why a page object for a laundry pickup scheduler constructs cleanly on a blank page and a typo in `@FindBy(id = "pickup-window")` only surfaces at first use. The field is never `null`, so a null check proves nothing. One thing does fail early: an invalid annotation combination throws `IllegalArgumentException` while the locator is built.

code

java · 18 lines
java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class PickupSchedulerPage {

  @FindBy(id = "pickup-window")
  private WebElement pickupWindow;

  public PickupSchedulerPage(WebDriver driver) {
    PageFactory.initElements(driver, this);
  }

  public String windowLabel() {
    return pickupWindow.getText();
  }
}

go deeper

for a junior

Be ready to say that initElements only assigns proxies and that the real lookup happens on the first method call. Knowing the field is never null is enough at this level.

for a middle

Explain the mechanics: DefaultFieldDecorator builds a Proxy, and LocatingElementHandler.invoke calls findElement before forwarding each method. Be able to name what does still fail early, such as an invalid annotation combination.

for a senior

Show how the deferral shapes diagnosis in a real suite: proxy frames in the stack trace, a typo and an absent element looking identical, and where you place the first call so the failure message stays readable.

for a principal

Own the consequence for a harness: if construction proves nothing, something else has to establish that the browser is on the right page, and you should be able to say what that is and who pays for it.

## What `PageFactory.initElements` actually does `PageFactory.initElements(SearchContext, Object)` is **pure reflection over your own class** — it never sends a command to the browser. It starts at `page.getClass()` and walks up the superclass chain to `Object`, and for every declared field it asks a `FieldDecorator` for a replacement value. The default is `DefaultFieldDecorator`, whose `decorate` returns `null` — meaning "leave this field alone" — for anything that is not a `WebElement` or a `List<WebElement>` carrying `@FindBy`, `@FindBys` or `@FindAll`. For the fields it accepts, it builds an `ElementLocator` from the annotations and hands back a `java.lang.reflect.Proxy` implementing `WebElement`, `WrapsElement` and `Locatable`. That proxy object is what gets assigned to your field. So on a page object for a laundry pickup scheduler, once `PageFactory.initElements(driver, this)` has run, a `pickupWindow` field holds a **stand-in**, not an element. No `findElement` has been issued. The call succeeds even with the browser parked on `about:blank`. ## Where the lookup really happens The lookup lives in the proxy's invocation handler, `LocatingElementHandler`. Its `invoke` does exactly two things, in this order: 1. call `locator.findElement()`, which is the real `searchContext.findElement(by)` round trip to the browser; 2. reflectively invoke the method you actually asked for on the element that came back, unwrapping the `InvocationTargetException` so you see the real cause rather than a reflection wrapper. That ordering is the whole answer. Every method on the field — `click()`, `getText()`, `isDisplayed()`, `sendKeys(...)` — re-runs the location step first, so the exception is raised from the **call site**, not from construction. ## What fails early and what fails late Not everything is deferred. Annotation *validity* is checked while the locator is being built, and that happens inside `initElements`; element *existence* is not checked until first use. | Moment | What runs | What can throw there | |---|---|---| | `PageFactory.initElements(...)` | field reflection, `Annotations.buildBy()`, one `Proxy` per accepted field | `IllegalArgumentException` for `@FindBy` alongside `@FindBys`, or two location strategies on one `@FindBy` | | first method call on the field | `locator.findElement()`, then the real command | `NoSuchElementException`, `StaleElementReferenceException`, anything the command itself raises | The practical read: a **structurally** wrong annotation blows up immediately, while a **factually** wrong one — `@FindBy(id = "pickup-window")` when the markup actually says `pickup_window` — sails through construction and waits for you. ## The `toString` special case `LocatingElementHandler` wraps only the lookup in a `try`, and it catches `NoSuchElementException` for exactly one method. If the method being invoked is `toString`, it swallows the exception and returns `"Proxy element for: "` followed by the locator, which prints as something like `DefaultElementLocator 'By.id: pickup-window'`. Every other method rethrows. - Printing the field in a log line, or expanding it in a debugger, therefore **never** reproduces the failure — which is why "it looked fine when I inspected it" is such a common and misleading report. - That same string is the cheapest way to see which locator a field was really built from, including the `ByIdOrName` fallback used when a field carries no annotation at all. ## What the deferral costs on the pickup scheduler - The field is **never** `null`, so an `assertNotNull(page.pickupWindow)` passes on a completely wrong page and proves nothing about the scheduler being loaded. - Constructing the page object is not evidence that the browser is anywhere near the scheduler; it is evidence only that the class compiled and its annotations parsed. - The stack trace of the eventual failure runs through a `jdk.proxy` class and `LocatingElementHandler.invoke`, so the first frame naming *your* code is the page-object method that used the field, not the field declaration. - A locator typo and a genuinely absent element are indistinguishable from the exception alone: both arrive as `NoSuchElementException` raised from the same place. ## Living with a failure that arrives late 1. Treat the first method call on a field as the real assertion that the element exists, and place that call where the failure message will still make sense to whoever reads the report. 2. When you need to know which locator failed, print the field and let the `toString` path give you the `By` for free. 3. Do not add null guards around proxy fields — they can never fire, and they read as if somebody believed the field might be empty. 4. Remember `List<WebElement>` fields defer in the same way but fail differently: `findElements` returns an empty list rather than throwing, so an absent list is silent where an absent element is loud.

  • Does anything at all fail while PageFactory.initElements is running?
    Yes, but only structural problems. Building the locator calls `Annotations.buildBy()`, which rejects `@FindBy` together with `@FindBys` or `@FindAll`, and rejects two location strategies on one `@FindBy`, throwing `IllegalArgumentException`. Instantiating the page class can also fail if it has neither a `WebDriver` constructor nor a no-arg one. Nothing checks whether any element exists.
  • Why does printing the field in a log never reproduce the NoSuchElementException?
    `LocatingElementHandler` catches `NoSuchElementException` around the lookup and, if the invoked method is `toString`, returns `"Proxy element for: "` plus the locator rather than rethrowing. Every other method rethrows. So logging or inspecting the field prints something like `DefaultElementLocator 'By.id: pickup-window'` while `click()` on the same field fails.
  • How does a List<WebElement> field behave differently when its elements are missing?
    It stays quiet. The list proxy calls `locator.findElements()`, which returns an empty list instead of throwing, so a missing slot list produces `size() == 0` rather than an exception. The failure then shows up later as an index error or a silently skipped loop, which is harder to read than a `NoSuchElementException`.

saying these in an interview costs you the question

  • Says initElements queries the browser for every annotated field
  • Thinks a wrong locator is caught when the page object is constructed
  • Adds a null check on a proxy field that can never be null
  • Treats successful construction as proof the right page is loaded
  • Claims the proxy returns null when the element is absent