skip to content

The Page Factory

PageFactory replaces annotated WebElement fields with proxies that look the element up again on every call. Interviewers ask because that convenience hides both a cost and a caching trap.

on this pageshow

explore

questions

12

In Selenium's @FindBy annotation, what is the difference between the shorthand attributes and the how/using pair?

level: juniorimportance: must knowfreq 74%

answer

  1. Two spellings of the same locator
  2. One is equivalent, one is broader
  3. Check which strategies lack a shorthand
  4. How.ID_OR_NAME has no attribute
  5. Setting two strategies is rejected

basics

~10 s

There is no behavioural difference for the everyday strategies: @FindBy(id = "x") and @FindBy(how = How.ID, using = "x") build the same By. The pair exists for How.ID_OR_NAME, which has no shorthand attribute.

solid answer

~40 s

`@FindBy` accepts a locator either as a named shorthand attribute — `id`, `name`, `className`, `css`, `tagName`, `linkText`, `partialLinkText`, `xpath` — or as the `how`/`using` pair, where `how` is a `How` constant. For the eight everyday strategies the two are equivalent: `@FindBy(id = "track-search")` and `@FindBy(how = How.ID, using = "track-search")` both build `By.id("track-search")`. The pair still matters because `How.ID_OR_NAME`, which builds a `ByIdOrName` that tries the value as an id and then as a name, has no shorthand attribute. `how` defaults to `How.UNSET`, which delegates to `How.ID`, so `@FindBy(using = "now-playing")` alone means `By.id("now-playing")`. Setting two strategies on one annotation throws `IllegalArgumentException`.

code

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

public class TrackSearchPanel {

  @FindBy(id = "track-search")
  private WebElement searchShorthand;

  @FindBy(how = How.ID, using = "track-search")
  private WebElement searchLongForm;

  @FindBy(how = How.ID_OR_NAME, using = "playlistTitle")
  private WebElement playlistTitle;

  @FindBy(using = "now-playing")
  private WebElement nowPlaying;
}

go deeper

for a junior

Know both spellings on sight and be able to convert one into the other. Remember the attribute is css, not cssSelector, and that only one location strategy may be set on a single annotation.

for a middle

Explain the resolution order: the builder validates that at most one strategy is set, tries the shorthand attributes first, and only then falls back to how().buildBy(using()), with How.UNSET delegating to By.id.

for a senior

Be able to say why the long form survives, namely How.ID_OR_NAME having no shorthand, and to diagnose the IllegalArgumentException a duplicated strategy raises during page-object construction.

for a principal

Pick one spelling as a house convention and say why, since a page layer mixing both for no reason costs review attention that should go to the locators themselves.

## Two spellings, one annotation `@FindBy` in `org.openqa.selenium.support` accepts a locator in either of two forms, and the Java client's own documentation states the two are equivalent: - the **shorthand** form, one named attribute per strategy — `@FindBy(id = "track-search")`; - the **`how`/`using` pair**, a strategy constant plus its value — `@FindBy(how = How.ID, using = "track-search")`. Both are read by the same builder and both end up as `By.id("track-search")`. There is no runtime, performance or caching difference; the choice is a readability convention. ## The shorthand attributes and their How constants | Shorthand attribute | `How` constant | `By` built | |---|---|---| | `id` | `How.ID` | `By.id` | | `name` | `How.NAME` | `By.name` | | `className` | `How.CLASS_NAME` | `By.className` | | `css` | `How.CSS` | `By.cssSelector` | | `tagName` | `How.TAG_NAME` | `By.tagName` | | `linkText` | `How.LINK_TEXT` | `By.linkText` | | `partialLinkText` | `How.PARTIAL_LINK_TEXT` | `By.partialLinkText` | | `xpath` | `How.XPATH` | `By.xpath` | | *(none)* | `How.ID_OR_NAME` | `ByIdOrName` | | *(none)* | `How.UNSET` | delegates to `By.id` | Two rows in that table are the reason the long form still exists. `How.ID_OR_NAME` builds a `ByIdOrName`, which tries the value as an id and then as a name — and **there is no `idOrName` shorthand attribute**, so the pair is the only way to ask for it. `How.UNSET` is the default value of `how()`, and it delegates to `How.ID`; that is why `@FindBy(using = "now-playing")` with no `how` at all resolves to `By.id("now-playing")` rather than failing. Note the spelling of the CSS attribute: it is `css`, not `cssSelector`. The `By` factory method is `By.cssSelector`, the annotation attribute is not, and guessing wrong is a compile error rather than a test failure. ## How the builder resolves a field 1. `Annotations.buildBy()` finds which locator annotation the field carries and hands it to that annotation's `AbstractFindByBuilder`. 2. The builder validates the annotation: it collects every attribute that has been set to a non-empty value, counting a non-empty `using` as one of them. 3. It tries the shorthand attributes first, in a fixed order, and returns the first `By` it can build. 4. Only if no shorthand was set does it fall back to `how().buildBy(using())`. Step 4 is why you cannot mix the forms as a fallback pair: the shorthand always wins outright, so an annotation carrying both would silently ignore one — which is exactly why step 2 forbids it. ## The one-strategy rule If more than one strategy is set, the builder throws `IllegalArgumentException` reading "You must specify at most one location strategy", with the count and the offending values. That covers the combinations people reach for on a playlist editor: - `@FindBy(id = "track-search", name = "trackSearch")` — two shorthands, rejected. - `@FindBy(how = How.ID, using = "track-search", id = "track-search")` — the pair plus a shorthand, also rejected, because a non-empty `using` counts as a strategy. - Setting neither is legal and means "no shorthand", which then resolves through `How.UNSET`. The exception is raised while `PageFactory.initElements` decorates the page object, so a malformed annotation fails on construction of the page rather than on the first click. ## Where the annotation may be written `@FindBy` targets both fields and types. A type-level annotation compiles, but the Java client documents that it **is not processed by default**, so writing `@FindBy` on the `TrackSearchPanel` class itself changes nothing — the locator has to sit on the field that will hold the element. The same applies to `@FindBys` and `@FindAll`. `@CacheLookup` targets fields only, so the compiler stops that particular mistake for you. And whichever spelling you use, the annotation on its own does nothing at all: the locator is read, validated and turned into a `By` only when the page object is passed to `PageFactory.initElements`, which is what installs the value in the field. ## Which form to write - The shorthand is shorter, and on the eight everyday strategies it says exactly the same thing. - The pair is required for `How.ID_OR_NAME`. - The pair keeps the strategy in a uniform position, which helps when a base class or a code generator produces annotations mechanically. - Whichever you pick, the value is an annotation member and therefore a compile-time constant: a station id or a row index read at run time cannot be interpolated into either form. None of this changes which locator is a good idea for the markup in front of you — `@FindBy` only decides how the locator is spelled on the field.

  • What does @FindBy(how = How.ID, using = "x", id = "x") do?
    It throws `IllegalArgumentException` when the locator is built, with a message saying you must specify at most one location strategy. A non-empty `using` counts as a strategy just as `id` does, so the annotation declares two even though both name the same value.
  • Why is the CSS attribute named css rather than cssSelector?
    The annotation attribute is `css`; the factory method it maps to is `By.cssSelector`. They simply do not share a name. Writing `@FindBy(cssSelector = "...")` fails to compile because no such attribute exists on the annotation, which at least catches the mistake immediately.
  • When would you actually prefer the how/using pair?
    When you need `How.ID_OR_NAME`, which has no shorthand, and when annotations are produced mechanically or read reflectively, since the strategy always sits in the same attribute. Otherwise the shorthand says the same thing in less space and most page objects use it.

saying these in an interview costs you the question

  • Claiming the how/using pair is faster or cached differently
  • Writing cssSelector instead of css on the annotation
  • Expecting a second strategy to act as a fallback
  • Thinking how and using may be set independently of each other
  • Assuming How.UNSET means the locator is invalid
open as a page

In Selenium's PageFactory, how do @FindBys and @FindAll differ when they annotate the same page-object field?

level: middleimportance: must knowfreq 62%

basics

~10 s

@FindBys builds a ByChained: each locator is searched inside the elements the previous one matched. @FindAll builds a ByAll: every locator runs against the same context and the matches are merged into one union.

open as a page

In Selenium's PageFactory, how do you bind a List<WebElement> field, and what happens to a field left unannotated?

level: juniorimportance: should knowfreq 47%

basics

~20 s

A list field takes the same @FindBy and is resolved with findElements, but the annotation is required or the field stays null. An unannotated WebElement field is bound using its own name as an id, then a name.

open as a page

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

level: juniorimportance: should knowfreq 56%

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.

open as a page

Why do Selenium teams replace @FindBy proxy fields with By constants and driver.findElement at the call site?

level: middleimportance: should knowfreq 41%

basics

~20 s

A proxy field hides its By, so nothing downstream can reuse the locator and failures surface from inside the proxy. A By constant with findElement at the call site puts the locator back within reach.

open as a page

In Selenium's PageFactory, how often does a proxied List<WebElement> field query the DOM?

level: middleimportance: should knowfreq 38%

basics

~10 s

Once per List method call. The list proxy runs findElements before it forwards any method, so size() then get(0) is two queries, and the WebElements handed back are ordinary handles rather than proxies.

open as a page

In Selenium's PageFactory, what does adding @CacheLookup to a proxied field change inside the element locator?

level: middleimportance: should knowfreq 57%

basics

~20 s

It makes the locator keep the first element it finds and return that same instance from then on. Selenium stops sending a find command for that field, so every later call reuses one DOM node.

open as a page

In Selenium's PageFactory, what does initElements put into a WebElement field, and when is the browser actually queried?

level: middleimportance: should knowfreq 64%

basics

~20 s

It assigns a dynamic proxy object, not a found element. Selenium sends no find command while initElements runs; the proxy asks its ElementLocator for a fresh lookup each time a method is called on the field.

open as a page

A Selenium @CacheLookup page-object field throws StaleElementReferenceException on every call after a re-render — why does it never recover?

level: seniorimportance: should knowfreq 44%

basics

~10 s

@CacheLookup makes DefaultElementLocator store the WebElement it found the first time and return that same instance forever. Nothing in Selenium invalidates that cache, so once a re-render detaches the element every later call throws.

open as a page

In Selenium, what does AjaxElementLocatorFactory change about a page object's lookups, and where does its retry stop helping?

level: seniorimportance: should knowfreq 44%

basics

~20 s

It 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.

open as a page

Your Selenium page layer binds every element with @FindBy, but locators must now vary by station skin and row data. How do you decide what stays declarative?

level: principalimportance: should knowfreq 34%

basics

~20 s

Annotation values are compile-time constants, so no @FindBy locator can depend on runtime data. Keep declarative fields for the page's fixed structure and lists of repeated rows, and build By values in code for anything addressed by data.

open as a page

In Selenium, how do you decide whether a team's PageFactory customisation belongs in an ElementLocatorFactory or a FieldDecorator?

level: principalimportance: should knowfreq 36%

basics

~10 s

Decide by what has to change. An ElementLocatorFactory only changes how a field's element is found; a FieldDecorator changes which fields are replaced and with what, so it is the wider and riskier seam.

open as a page