skip to content

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%

answer

  1. Ask what the annotation can hold
  2. Constants only, decided at compile time
  3. Lists absorb most dynamic cases
  4. Draw one stated boundary per page
  5. A custom annotation extends the vocabulary

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.

solid answer

~50 s

The deciding constraint is that `@FindBy` attributes are annotation members and therefore compile-time constants: a station id from configuration or a row index cannot be interpolated into one, and each field carries exactly one fixed `By`. So the line to draw is between the page's stable skeleton, which stays declarative, and anything addressed by data, which is built as a `By` at run time and passed to `findElement`. A `List<WebElement>` field bound to `.track-row` removes most of the pressure for dynamic locators, because the row is then selected in Java rather than in the selector. Keep both styles inside the page object so tests never see a selector, state the rule in one sentence, and apply it per page. If a house vocabulary is worth the cost, a custom annotation registered with `@PageFactoryFinder` extends the declarative surface rather than abandoning it.

go deeper

for a junior

Remember that an annotation value must be a constant, so a locator containing a variable will not compile. Ask a senior how your team addresses rows that differ per run.

for a middle

Explain the mechanics of the constraint and the standard way around it: bind repeated structure as a list field and select the row in Java rather than encoding the value in a selector.

for a senior

Show that you can split a real page: which elements stay annotated fields, which become methods that construct a By, and how the page object keeps selectors out of the tests either way.

for a principal

Own the boundary as a stated convention, and weigh the second-order costs: a custom annotation seam only your team knows, and a declarative style that does not carry to teams using another Selenium binding.

## The constraint you are designing around Every attribute of `@FindBy`, `@FindBys` and `@FindAll` is an annotation member, and Java requires annotation members to be **compile-time constants**. That single fact defines what the declarative layer can and cannot do: - A locator cannot be computed. `@FindBy(xpath = "//tr[" + rowIndex + "]")` does not compile unless every part is a constant. - A locator cannot be read from configuration — a station id, a base URL segment, a locale-specific label. - One field carries exactly one `By`, fixed for the life of the class. There is no parameterisation and no per-instance variation. - `@CacheLookup` is likewise a declaration-time decision: whether a field caches is written into the source, not chosen at run time. So the real question a lead answers is not "annotations or not" but **where the boundary between the declarative fields and the code that builds locators runs**, and how it is enforced. ## What the declarative layer is genuinely good at - The **page skeleton** — the controls that exist once and are addressed the same way every run: the playlist editor's search box, its Save control, its on-air panel. - **Repeated structure** as a list. `@FindBy(className = "track-row") List<WebElement> trackRows` binds every row, and the row you want is then chosen in Java, by index or by a predicate over the rows' text. This is the move that removes most of the pressure for a dynamic locator. - **Containment** through `@FindBys`, so a locator that is only unique inside the on-air queue does not need a hand-built compound XPath. - **Documentation value**: the locator sits next to the field name, so a reader sees what `trackSearch` means without following a call chain. ## What has to stay imperative | Requirement | Declarative field | Locator built in code | |---|---|---| | Locator known when the class compiles | yes | yes | | Locator depends on a runtime value | impossible | natural | | One locator reused with different values | one field per value | one method, many arguments | | Where the locator is visible | on the field declaration | at the call site | | How it is resolved | `PageFactory.initElements` proxy | direct `findElement` call | Anything in the right-hand column — a row addressed by the track title a test just uploaded, a sponsor slot keyed by a scheduling id, a control whose selector differs per white-label skin — is built as a `By` at run time and passed to `findElement`. The page object still owns it; it simply exposes a method that takes the value and returns the element or performs the step, instead of a field. ## Drawing and holding the line 1. Decide the rule in one sentence the team can repeat, for example: *fields for anything the page always has, methods for anything addressed by data.* 2. Keep the constructed locators inside the page object, as private `By`-returning helpers, so tests never see a raw selector regardless of which side of the line an element falls on. 3. Do not let a class straddle the line invisibly — a page whose elements are half annotated fields and half hand-built lookups, with no stated reason, is harder to read than either style alone. 4. Revisit per page, not per suite. A settings form is almost entirely declarative; a scheduling grid is almost entirely computed. ## Extending the vocabulary, and what it costs Selenium's Java support library lets a team add its own annotation rather than abandon the style: an annotation meta-annotated with `@PageFactoryFinder`, naming a class that extends `AbstractFindByBuilder`, is picked up by `Annotations.buildBy()` exactly like the built-in three, and its `buildIt` method returns whatever `By` the team wants. That buys a house vocabulary — a single annotation for the station's own attribute convention — at the price of a seam every new joiner must learn and every IDE and search tool treats as unfamiliar. The other commitment worth naming out loud: in **Selenium 4** this whole surface — `PageFactory`, `@FindBy`, `How`, `@CacheLookup` — is part of the **Java** client only. A page layer standardised on annotated fields does not transfer to a team writing its tests in another binding, which for a polyglot organisation is a stronger argument than any of the mechanical ones above.

  • How do you address one specific playlist row without a dynamic locator?
    Bind the rows as a `List<WebElement>` with a single `@FindBy`, then choose in Java — by index, or by filtering on each row's text. The selection logic becomes ordinary code you can read and test, and the page object exposes a method taking the track title rather than a selector.
  • What does a custom annotation registered with @PageFactoryFinder actually give you?
    It meta-annotates your own annotation with the builder class that turns it into a `By`, so `Annotations.buildBy()` treats it like the built-in three. You get a house vocabulary matching your markup convention, at the cost of a seam every joiner must learn and no tool recognises.
  • Does standardising on @FindBy constrain the wider organisation?
    Yes. In Selenium 4 `PageFactory`, `@FindBy` and `How` are part of the Java client only, so a page layer built on annotated fields is a Java-specific convention. A team writing tests in another binding has to express the same page layer a different way.

saying these in an interview costs you the question

  • Believing a variable can be interpolated into a @FindBy value
  • Building one annotated field per data value
  • Reassigning an annotated field to redirect its locator
  • Treating dynamic locators as a reason to drop page objects
  • Assuming the annotation surface exists in every Selenium binding