In Selenium, what does a component-scoped page object hold so its lookups stay inside one widget?
answer
- One field decides the whole scope
- The class is handed a node
- An element can find, like a driver
- Store the root, not the driver
- Every lookup starts at that root
basics
~10 sIt holds the widget's root element. WebElement implements SearchContext, so the class searches through that root on every lookup and can never match a node that lives outside the widget it models.
solid answer
~40 sIt holds a root `WebElement` — the node the widget renders into — and uses it as its search context. `WebElement` extends `SearchContext`, the interface declaring `findElement(By)` and `findElements(By)`, so an element can be searched from exactly as a driver can. Every accessor on the class then goes through that root: `root.findElement(By.cssSelector(".doses-on-hand"))` on a vaccine lot card, or the equivalent `@FindBy` fields decorated by `PageFactory.initElements(root, this)`. The class owns no URL and does not navigate; it is a facade over one rendered widget. Because the root bounds it, a ledger showing five lot cards is modelled by five instances, each unable to see the others.
go deeper
Recall the one-line answer: the component holds the widget's root element and searches through it. Be able to name WebElement and SearchContext without hesitating.
Explain why it works — SearchContext declares findElement and findElements, and WebElement extends it — and show both spellings, explicit root.findElement calls and PageFactory over the root.
Demonstrate the judgment of where the root comes from and who owns it: how instances are created per rendered widget, and how nested components chain their scopes without leaking the root to tests.
Own the modelling convention across a suite: decide whether reusable widgets get their own classes, and how teams keep components from quietly acquiring a driver field and drifting back to document-wide lookups.
## The single field that defines a component object A component-scoped page object is an ordinary class with one distinguishing feature: it is handed the **root element** of the widget it models and keeps it. In Java that field is a `WebElement`: ```java public class LotCard { private final WebElement root; public LotCard(WebElement root) { this.root = root; } } ``` Nothing else about the class is special. It has no URL, it does not navigate, and it does not own the driver. It is a small facade over one rendered widget — one vaccine lot card on a stock ledger — and every element it needs is looked up from that root. ## Why an element can stand in for the driver The type that makes this work is `SearchContext`, declared with exactly two methods, `findElement(By)` and `findElements(By)`. `WebElement` extends it (`public interface WebElement extends SearchContext, TakesScreenshot`), so an element can be searched from in the same way a driver can. Every helper in the Selenium 4 Java bindings that needs "something to find with" is typed against `SearchContext`, which is why the root element slots straight in. ## What the class actually does - It keeps **exactly one** root and treats it as the boundary of the object. - Each accessor searches from that root, for example `root.findElement(By.cssSelector(".doses-on-hand"))`. - It exposes **domain methods**, not elements: `dosesOnHand()`, `expiry()`, `discard()` rather than a getter returning a raw `WebElement`. - It never reaches back to the driver for a node inside its own widget, because that would leave the scope it exists to enforce. - It may hold nested components, each constructed over a root found through the parent's root. - Multiple instances coexist happily: five lot cards give five objects, each with its own root and no shared state. ## Two ways to spell the same idea | | hand-rolled lookups | `PageFactory` over the root | |---|---|---| | where the root is used | in every accessor, explicitly | once, in `PageFactory.initElements(root, this)` | | how elements are declared | `By` constants in the class | `@FindBy` on `WebElement` fields | | when the lookup happens | when the accessor runs | when the proxied field is first used | | what the class still needs | the root, always | the root, plus the annotations | Both are element-scoped and both are legitimate. The hand-rolled version is explicit and easy to read; the `PageFactory` version removes the repeated `root.findElement(...)` at the cost of a layer of proxies. Either way, the root is the mechanism. ## Constructing one over a vaccine-stock ledger 1. Find the widget's root from the driver, for example `driver.findElement(By.cssSelector("div.lot-card[data-lot='B-4417']"))`. 2. Pass that element into the component's constructor. 3. Call only the component's own methods from the test — never `driver.findElement` for something inside the card. 4. When the ledger renders many cards, do this once per card, keeping one component instance per root. ## Where the scoping quietly gets lost - **Storing the driver too, and using it.** A class that keeps both a root and a driver invites an accessor that searches from the driver, which is document-wide again and defeats the object. - **Passing a locator instead of an element.** Handing the class a `By` for the card and re-resolving it per call is not the same thing: the class then repeats the ambiguous search rather than holding a resolved node. - **Modelling the page, not the widget.** If the class has a `navigate()` method or asserts on a page title, it is a page object wearing a component's name. - **Leaking the root outward.** Returning the root to the test invites the test to search from it directly and the abstraction stops being a boundary. The reason interviewers ask this at the screening level is that the answer is one sentence long and it is a reliable tell. A candidate who says "the widget's root element, and every lookup goes through it" has understood that in Selenium a search always starts somewhere and that the starting point is a choice. A candidate who reaches for the driver in every method has not yet noticed that `findElement` exists on two types, and will write suites where a page with repeated widgets reads plausible values off the wrong one.
- What goes wrong if the component keeps the driver as well and uses it in one accessor?That accessor is document-wide again. On a ledger with many lot cards it returns the first matching node anywhere on the page, so one method reads a different card from all the others — and nothing throws, so the test simply asserts on the wrong lot's numbers.
- Why pass a located element rather than the By locator of the widget?A locator is re-resolved on every use, so the class repeats the ambiguous search it was meant to escape and different accessors can land on different cards. A located root is one fixed node: the component's methods are then guaranteed to be talking about the same widget.
- How does a component object expose a widget nested inside it?By finding the child's root through its own root and constructing the nested component over that element. The scopes chain, so the inner object can only reach nodes inside the outer widget, which is what makes deeply repeated structures safe to model.
The root element is a hood over a torch: the class can only light up what is inside the box it was pointed at, so it can never read a figure off the lot card sitting next to it.
saying these in an interview costs you the question
- Stores the WebDriver in the component and re-finds elements from the document.
- Thinks a component needs a URL, so models the page rather than the widget.
- Passes the widget's By locator around instead of the located root element.
- Believes only a driver can find elements, so re-queries the root every call.
- Returns raw elements to the test instead of exposing domain methods.