Use Selenium’s @FindBy annotation to declare how a Page Object locates a web element, then call PageFactory.initElements(driver, this) to initialize the page’s fields. PageFactory supplies a proxy that looks up the element when you use it; by default, it repeats the lookup on each method call.
Declare and initialize a Page Object
For a single element, declare a WebElement field and give @FindBy one locator strategy. Initialize the object with PageFactory, usually in its constructor:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPage {
@FindBy(id = "username")
private WebElement username;
@FindBy(css = "button[type='submit']")
private WebElement submitButton;
public LoginPage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
public void signIn(String user) {
username.sendKeys(user);
submitButton.click();
}
}
Use the page object after constructing it with the active driver:
LoginPage loginPage = new LoginPage(driver);
loginPage.signIn("sample-user");
The annotation marks a field with an alternative locating mechanism and is intended for use with PageFactory. Declaring the annotation alone does not initialize the field; without PageFactory decoration, ordinary Java code may encounter a null field. Selenium FindBy API · Selenium PageFactory API
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
Choose a locator strategy
The concise form uses a single annotation attribute. Selenium also supports the explicit how and using form; these express the same locator intent.
| Strategy | Example | Use when |
|---|---|---|
| ID | @FindBy(id = "username") |
The element has a suitable ID in the page markup. |
| Name | @FindBy(name = "email") |
The element has a suitable name attribute. |
| CSS selector | @FindBy(css = "input[type='email']") |
A CSS selector expresses the target clearly. |
| XPath | @FindBy(xpath = "//button[@type='submit']") |
The target requires an XPath expression. |
| Link text | @FindBy(linkText = "Continue") |
The target is a link with matching visible text. |
| Partial link text | @FindBy(partialLinkText = "Contin") |
A partial visible-text match is appropriate. |
| Class name | @FindBy(className = "primary-button") |
The target has a usable class name. |
| Tag name | @FindBy(tagName = "button") |
The tag identifies the intended element sufficiently. |
The corresponding explicit form looks like this:
import org.openqa.selenium.support.How;
@FindBy(how = How.ID, using = "username")
private WebElement username;
Which locator is best depends on the application’s actual DOM. Prefer a locator that communicates the intended target and remains meaningful as the markup changes; no strategy is universally most resilient.
Rank #2
Use lists and unannotated fields carefully
For repeated matches, use List<WebElement> and an explicit locator:
import java.util.List;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@FindBy(css = "ul.results > li")
private List<WebElement> results;
An unannotated field can be located using its Java field name as an ID or name. That default may be convenient for a single element, but an explicit locator makes the intended DOM match clearer. Prefer an explicit locator for list fields: Selenium’s project wiki has historical guidance that lists were decorated only when annotated and that the default ID/name behavior was poorly suited to lists; the wiki example was edited in 2015, so treat it as older guidance. Selenium project wiki: PageFactory
Recommended Free Tools
Rank #3
Understand lazy lookup and caching
PageFactory.initElements decorates element fields and lists with proxies. The proxy performs the lookup when a method is called on the field, rather than necessarily locating every element when the page object is constructed. By default, each method call triggers a lookup. This can be useful when the page changes between interactions, but it also means that repeated calls can perform repeated lookups. PageFactory API
@CacheLookup requests that the located element be reused from cache on later calls. Use it only when the element is stable enough that retaining the cached reference is appropriate; a cached element can be unsuitable if the page replaces or refreshes that element. Selenium CacheLookup API
Rank #4
Common errors and fixes
- Field is null: the annotation does not initialize itself. Ensure the page object is created or decorated with
PageFactory.initElements(driver, pageObject)before using the field. - Element cannot be found: confirm that the locator matches the current DOM and that the relevant page state has loaded before interacting. Selenium supports the listed locator strategies, but the correct one depends on the markup.
- Unexpected default match: an unannotated field uses its field name as an ID or name locator. Add an explicit
@FindBywhen that fallback is not the intended target. - Invalid combination of annotations: keep to one recognized locator annotation per field. The Selenium annotations API documents an
IllegalArgumentExceptionwhen more than one of@FindBy,@FindBys, and@FindAllis present on the same field. Selenium Annotations API - Cached reference no longer works: if the application replaces an element during navigation or an update, reconsider
@CacheLookupso the proxy can perform a fresh lookup.
Or skip the browser setup
If the goal is to capture a page rather than interact with it through Selenium, ScreenshotNeo offers a one-request screenshot API. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides screenshot tools for AI agents, including Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Learn about ScreenshotNeo or sign up free for 1,000 screenshots a month with no card.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Frequently Asked Questions
Can I put @FindBy on a class instead of a field?
The annotation API permits type-level use, but type-level annotations are not processed by default. For the usual PageFactory workflow, put it on the element field.
Best Value
Can @FindBy locate more than one element?
Yes. Declare a `List
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




