In Selenium's @FindBy annotation, what is the difference between the shorthand attributes and the how/using pair?
answer
- Two spellings of the same locator
- One is equivalent, one is broader
- Check which strategies lack a shorthand
- How.ID_OR_NAME has no attribute
- Setting two strategies is rejected
basics
~10 sThere 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 linesimport 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
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.
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.
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.
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