In Selenium, how do you decide whether a team's PageFactory customisation belongs in an ElementLocatorFactory or a FieldDecorator?
answer
- Two interfaces, one method each
- Which stage of the pipeline you replace
- One changes finding, the other changes replacing
- Only one seam sees non-WebElement fields
- createLocator versus decorate as the hook
basics
~10 sDecide 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.
solid answer
~40 s`PageFactory.initElements(ElementLocatorFactory, Object)` wraps your factory in the stock `DefaultFieldDecorator`, so proxying, `WebElement` and `List<WebElement>` handling and the annotation checks all stay Selenium's — you only replace the `ElementLocator` that resolves each field, which is exactly where `AjaxElementLocatorFactory` plugs in. `PageFactory.initElements(FieldDecorator, Object)` replaces the whole decoration step: your `decorate(ClassLoader, Field)` decides field by field whether to return a substitute at all, so it is the only seam that can support a new field type or an object that is not a proxy. I default to the locator seam because its blast radius is one method and the semantics the team already reasons about are unchanged, and I reach for a decorator only when the requirement is genuinely about which fields get decorated. Extending `DefaultFieldDecorator` keeps most of the stock behaviour.
code
java · 19 linesimport java.lang.reflect.Field;
import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
public class DispatchBoardFieldDecorator extends DefaultFieldDecorator {
public DispatchBoardFieldDecorator(ElementLocatorFactory factory) {
super(factory);
}
@Override
public Object decorate(ClassLoader loader, Field field) {
Object value = super.decorate(loader, field);
if (value != null) {
System.out.println("Proxied dispatch-board field: " + field.getName());
}
return value;
}
}go deeper
Recognise that PageFactory has more than one initElements overload and that passing something other than the driver is how a team customises page-object fields.
Explain that createLocator controls how each field is found while decorate controls whether a field is replaced at all, and that the overloads nest one inside the other.
Show you can size the change: pick the narrowest seam that satisfies the requirement, and say what stock behaviour you inherit or take on by choosing each one.
Own the ownership question. A seam every page object passes through becomes framework policy, so argue for opt-in customisation until enough of the suite wants it to justify a default.
## One pipeline with three entry points `PageFactory` exposes four `initElements` overloads, and three of them take an already-constructed page object. They are not alternatives — each is a stage of the same pipeline, and picking one is choosing **how far down that pipeline you take over**: 1. `initElements(SearchContext, Object)` — you supply only *where* to search. Selenium builds a `DefaultElementLocatorFactory`, then a `DefaultFieldDecorator`. 2. `initElements(ElementLocatorFactory, Object)` — you supply *how each field is located*. Selenium still builds the stock `DefaultFieldDecorator` around your factory. 3. `initElements(FieldDecorator, Object)` — you supply *what happens to each field*. Nothing of the stock behaviour survives unless you inherit it. Everything below the entry point you choose is still Selenium's code, and that is the whole basis for the decision. ## What an `ElementLocatorFactory` can reach `ElementLocatorFactory` has one method, `ElementLocator createLocator(Field field)`, and it is called once per decoratable field. Inside it you can: - Return a different `ElementLocator` implementation — this is exactly how `AjaxElementLocatorFactory` adds polling to a dispatch board's fields. - Vary behaviour per field by inspecting the `Field` object: its name, its declaring class, its own annotations. - Return `null`, which the stock decorator propagates so that `PageFactory` skips the field entirely and leaves its existing value in place. The javadoc on `initElements(ElementLocatorFactory, Object)` states this contract explicitly. What it **cannot** reach is the decoration rule itself. `DefaultFieldDecorator.decorate` bails out before ever consulting your factory unless the field is a `WebElement` or an annotated `List<WebElement>`, so no locator factory can bring a new field type into play. ## What a `FieldDecorator` owns `FieldDecorator` also has one method — `Object decorate(ClassLoader loader, Field field)` — but it sits above the type check. Implementing it puts you in charge of: - **Which fields are replaced at all**, including types Selenium would ignore, such as a dispatch-board component wrapper class. - **What object replaces them.** The return value only has to be assignable to the field; a proxy is a convention, not a requirement. - **The proxy's interface set**, if you do build one — `DefaultFieldDecorator.proxyForLocator` and `proxyForListLocator` are `protected` precisely so a subclass can override them. The cost is that you now own behaviour you previously inherited: the `WebElement` and `List<WebElement>` handling, the annotation checks, and the two invocation handlers. ## Choosing between them | | `ElementLocatorFactory` seam | `FieldDecorator` seam | |---|---|---| | the one method | `createLocator(Field)` | `decorate(ClassLoader, Field)` | | you control | how a field's element is found | whether and with what a field is replaced | | you inherit | all proxying and type handling | nothing, unless you extend `DefaultFieldDecorator` | | new field types | impossible | the only way | | blast radius | one method per field | every field on every page object | | stock example | `AjaxElementLocatorFactory` | `DefaultFieldDecorator` | My default is the **locator seam**, for three reasons worth saying out loud in an interview: the surface you own is one method, the proxy semantics your team already reasons about stay identical, and a mistake shows up as a bad lookup rather than as a field that silently stayed null. I move to a **decorator seam** only when the requirement is genuinely about *which* fields get decorated or *what* they become — a custom field type, a wrapper object, or a rule that some fields must be left alone regardless of their type. ## The middle path most teams actually want Extending `DefaultFieldDecorator` and overriding one method is usually the honest answer, because it gives you the decorator seam's reach without discarding the stock behaviour: - Override `decorate` to add a rule, then call `super.decorate(loader, field)` for everything else. - Override `isDecoratableList` to accept a different generic element type. - Override `proxyForLocator` to add interfaces to the generated proxy. ## The organisational question underneath Whichever seam you pick becomes a thing every page object in the suite passes through, so the real decision is about **ownership**, not code. A locator factory is a small, testable object that a single page object can opt into by calling a different `initElements` overload; a field decorator is closer to a framework-wide policy, because a page object that does not use it behaves differently from one that does. If only two of forty dispatch-board page objects need the customisation, prefer the seam that lets those two opt in, and resist making it the default until enough of the suite wants it that consistency is worth more than the flexibility you are giving up.
- What happens if your ElementLocatorFactory returns null for a field?`DefaultFieldDecorator.decorate` returns null for that field, and `PageFactory` then skips the assignment entirely, so the field keeps whatever value it already had — usually null. The javadoc on `initElements(ElementLocatorFactory, Object)` states this contract explicitly. It is the cheapest way to exclude a field from decoration without writing a decorator.
- Which seam do you need to support a field type that is not WebElement or List<WebElement>?A `FieldDecorator`. `DefaultFieldDecorator.decorate` returns null for any field whose type is not assignable to `WebElement` and is not a `List<WebElement>` carrying `@FindBy`, `@FindBys` or `@FindAll`, so no locator factory can reach such a field. Only your own `decorate` implementation can return a substitute for another type.
- Is initElements(SearchContext, Object) a separate mechanism from the other two overloads?No. It builds a `DefaultElementLocatorFactory` over that context and calls `initElements(ElementLocatorFactory, Object)`, which builds a `DefaultFieldDecorator` and calls `initElements(FieldDecorator, Object)`. The three are one pipeline with three entry points, so choosing an overload is choosing how far down that pipeline you take over.
saying these in an interview costs you the question
- Thinks the three initElements overloads are unrelated mechanisms
- Believes an ElementLocatorFactory can decorate a non-WebElement field
- Says a custom decorator must reimplement the proxy from scratch
- Assumes a factory returning null leaves the field proxied anyway
- Thinks AjaxElementLocatorFactory is a field decorator