skip to content

Proximity Filters

Selenium can address an element by where it sits next to another one: above, below, to the left, to the right or near. It reads screen rectangles, so results move when the layout moves.

on this pageshow

explore

questions

4

In Selenium 4, what geometric rule does RelativeLocator's above() apply, and why do overlapping elements match no direction?

level: middleimportance: must knowfreq 62%

answer

  1. Screen geometry, not DOM ancestry
  2. getBoundingClientRect on anchor and candidates
  3. Edges are compared, not centres
  4. Strict comparison, so overlap disqualifies
  5. Candidate bottom against anchor top

basics

~20 s

Selenium compares bounding rectangles edge to edge: a candidate is above an anchor only when the candidate's bottom edge sits at or higher than the anchor's top edge. Any overlap fails all four directional tests.

solid answer

~40 s

`RelativeLocator.with(By)` builds a `RelativeBy`, and its directional filters compare `getBoundingClientRect()` boxes rather than DOM position. `above(anchor)` keeps a candidate only when the candidate's bottom (`top + height`) is at most the anchor's `top`; `below` mirrors it, and `toLeftOf`/`toRightOf` apply the same test to the horizontal edges. The comparison is strict and non-overlapping, so a badge that straddles the anchor's top edge by one pixel is neither above nor below it, and one that overlaps horizontally is neither left nor right — it silently drops out of every directional result. Selenium 4 also ships `straightAbove`, `straightBelow`, `straightLeftOf` and `straightRightOf`, which add the requirement that the two boxes overlap on the other axis, so "above" also means "in the same column".

code

java · 19 lines
java
import static org.openqa.selenium.support.locators.RelativeLocator.with;

import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class BidPanelDirections {

  public static List<WebElement> buttonsAboveTheBid(WebDriver driver) {
    WebElement currentBid = driver.findElement(By.cssSelector(".lot-card .current-bid"));
    return driver.findElements(with(By.tagName("button")).above(currentBid));
  }

  public static WebElement placeBidButton(WebDriver driver) {
    By currentBid = By.cssSelector(".lot-card .current-bid");
    return driver.findElement(with(By.tagName("button")).straightRightOf(currentBid));
  }
}

go deeper

for a junior

Recall that Selenium's relative locators describe an element by its position on screen relative to another element, and that above, below, toLeftOf and toRightOf are the four directional filters.

for a middle

Be ready to state the actual rule: rectangles from getBoundingClientRect are compared edge to edge, and the comparison is strict, so an overlapping element matches no direction at all.

for a senior

An interviewer expects you to predict a real panel's behaviour from its layout, including why a filter that matched yesterday returns nothing after a re-flow, and to reach for the straight variants when one axis is not enough.

for a principal

Own the consequence that a locator's meaning is now a claim about rendered boxes, so anything moving either box changes the result. Be able to say what that widens the blast radius of and what it does not.

## What `RelativeLocator.with(By)` builds `RelativeLocator` is a static factory in `org.openqa.selenium.support.locators`. Calling `RelativeLocator.with(By)` returns a **`RelativeBy`**, which is itself a `By`, so it drops straight into `driver.findElement` or `driver.findElements`. The `By` you hand to `with(...)` is the **root locator** and it produces the candidate set; every call chained after it appends a **filter** that narrows that set by where things are drawn rather than by where they sit in the document. - The root locator is deliberately coarse: `RelativeLocator.with(By.tagName("button"))` means "every button on the page", and the filters do the discriminating. - Filters combine with **AND**, so a chain can only ever shrink the candidate set, never grow it. - Every filter has two overloads: the anchor may be a `WebElement` you already found, or another `By` that the lookup resolves for you. On an art-auction bidding panel that pattern is the whole point. The **Place bid** button carries no id worth trusting, but the current-bid figure, the countdown and the reserve-met badge all do, so you address the button by where it sits beside one of them. ## The rule is an edge comparison on client bounding rectangles Selenium does not consult ancestors, siblings or source order here. The bundled `findElements.js` atom calls `getBoundingClientRect()` on the anchor and on each candidate and reduces each one to `left`, `top`, `width` and `height`. The four directional filters are then a single inequality each. | Java method | Filter kind in the payload | A candidate is kept when | |---|---|---| | `above(...)` | `above` | candidate `top + height` is at most anchor `top` | | `below(...)` | `below` | candidate `top` is at least anchor `top + height` | | `toLeftOf(...)` | `left` | candidate `left + width` is at most anchor `left` | | `toRightOf(...)` | `right` | candidate `left` is at least anchor `left + width` | Read `above` aloud: **the candidate's bottom edge must be at or higher than the anchor's top edge.** Not "its centre is higher up". Not "it comes first in the document". The two boxes must not share a single row of pixels. ## Why a partially overlapping element matches nothing That strictness is the surprise every team hits, and Selenium's own Javadoc on `RelativeLocator` calls it out. Picture a "Reserve met" badge nudged up and to the right so that it clips the corner of the current-bid figure: ```text +--------------+ | current bid |---------+ +--------------+ RESERVE | +----------------+ ``` - It is **not `above`** the bid figure: its bottom edge is lower than the figure's top edge. - It is **not `below`** the figure: its top edge is higher than the figure's bottom edge. - It is **not `toRightOf`** the figure: its left edge is still to the left of the figure's right edge. - It is **not `toLeftOf`** the figure either, for the mirror-image reason. So the badge belongs to no direction at all. Nothing throws and nothing warns; it simply drops out of every directional result, and `driver.findElement` then reports a `NoSuchElementException` whose message begins "Cannot locate an element using". One pixel of overlap is enough, because the comparison has no tolerance band. Only `near` admits overlapping boxes, because it tests rectangle intersection against an expanded anchor instead of an edge ordering. ## The `straight` variants supply the missing axis Because each plain filter constrains one axis only, `above(currentBid)` matches everything whose bottom edge clears the bid figure's top edge — the lot title, the countdown, the auction-house logo three columns away, all of it. Selenium 4 also ships `straightAbove`, `straightBelow`, `straightLeftOf` and `straightRightOf`, which keep the same edge test and additionally require the two boxes to overlap on the *other* axis. - `straightAbove(anchor)` keeps a candidate only if it also spans part of the anchor's horizontal extent: "in the same column, higher up". - `straightRightOf(anchor)` keeps a candidate only if it also spans part of the anchor's vertical extent: "on the same row, further right". On the bidding panel, `straightAbove(placeBidButton)` is the difference between "this lot's countdown" and "anything at all in the upper half of the page". ## What comes back, and in what order Matches are **sorted by proximity** before they are returned. The atom measures the straight-line distance between the centre point of the last filter's anchor and the centre point of each surviving match, nearest first, so `findElement` hands you the *closest* qualifying element rather than the first one in document order. That ordering is what makes a filter as loose as `above` usable on a panel holding a dozen buttons. ## Working a lookup through 1. Choose a root locator that honestly describes the candidate set — a tag name or a class, not a brittle absolute path. 2. Choose an anchor you can name reliably and that renders as a box of its own. 3. Pick the axis: `above`/`below` for vertical, `toLeftOf`/`toRightOf` for horizontal, and the matching `straight` variant when column or row alignment matters too. 4. If the result is empty, compare the two rectangles before suspecting anything else — an overlap of a single pixel, or a collapsed zero-size box, explains most misses.

  • If above() matches everything higher on the page, how do you narrow it to the same column?
    Use `straightAbove` instead. It keeps the same bottom-edge-versus-top-edge test and adds the requirement that the two rectangles overlap horizontally, so only elements sharing part of the anchor's horizontal extent survive. `straightBelow`, `straightLeftOf` and `straightRightOf` do the equivalent on the other axis. Chaining a second filter is the alternative, but the straight variant expresses column or row alignment directly.
  • Which relative filter still matches an element that overlaps the anchor?
    Only `near`. It does not order the two boxes along an axis; it grows the anchor's rectangle by a distance on all four sides and keeps any candidate whose rectangle intersects that region, so an overlapping candidate qualifies. The anchor itself is excluded from its own results. The four directional filters all reject any shared pixel.
  • Does the returned list come back in document order?
    No. The matches are sorted by proximity: the atom measures the straight-line distance between the centre point of the last filter's anchor and the centre point of each match, nearest first. So `findElement` on a relative locator returns the closest qualifying element, not the earliest one in the document.

It works like describing a seat in an auction room by saying "the chair whose back is entirely in front of the podium" — if the chair even slightly overlaps the podium's footprint, the description no longer picks it out at all.

saying these in an interview costs you the question

  • Says above() walks the DOM looking for preceding siblings
  • Assumes a partially overlapping element still counts as above
  • Thinks the directional filters compare element centre points
  • Believes above() only matches elements in the same column
  • Claims the comparison tolerates a few pixels of overlap
open as a page

In Selenium 4, what does RelativeLocator's near() match by default, and how are its results ordered?

level: middleimportance: must knowfreq 58%

basics

~10 s

Selenium's near() keeps any element whose rectangle overlaps the anchor's rectangle grown by 50 pixels on every side, excluding the anchor itself. Results come back sorted by centre-to-centre distance, closest first.

open as a page

In Selenium 4, where does a RelativeLocator lookup actually execute, and what does that placement cost?

level: principalimportance: must knowfreq 38%

basics

~20 s

It runs inside the page. Selenium injects a bundled JavaScript atom with one execute-script call, which resolves the locators and reads every rectangle. So matching semantics are pinned to your client version, not to the browser driver.

open as a page

A Selenium relative locator chaining below() and toRightOf() returns nothing after a layout change — what do you check?

level: seniorimportance: should knowfreq 41%

basics

~10 s

Bisect the chain: run the root locator alone, then with each filter added, to find which filter empties the set. Then compare the anchor and candidate rectangles, since the directional filters reject any overlap.

open as a page