XPath Deep-Dive Practice

50XPath Deep-Dive Practice (interview-focused)

What you will master here

  • Every XPath syntax pattern asked in interviews
  • Axes: ancestor, descendant, following, preceding, parent, child, sibling
  • Functions: contains(), starts-with(), normalize-space(), not(), text(), local-name()
  • Indexing, conditions, and / or, negative matching
  • Edge cases: SVG, Shadow DOM, iframe, dynamic IDs, junk spaces, broken links
  • How to test XPath in Chrome DevTools (F12 → Elements → Ctrl+F)
Practice groundOpen https://selectorshub.com/xpath-practice-page/ in your browser. Press F12 → Elements → Ctrl+F → paste any XPath below. The highlighted element shows what matches.

50.1 Input fields

By id

//input[@id='userId']
//input[@id='pass']

By label text using following axis

//label[normalize-space()='User Email']/following::input[1]
//label[normalize-space()='Password']/following::input[1]
//label[normalize-space()='Company']/following::input[1]
//label[normalize-space()='Mobile Number']/following::input[1]

Concepts: Attribute-based XPath · Label-based XPath · following axis · normalize-space()

50.2 Buttons

//button[normalize-space()='Submit']
//input[@type='submit']
//*[self::button or self::input][normalize-space()='Submit' or @value='Submit']

Concepts: button vs input[type=submit] · self:: axis · text-based XPath

50.3 Links

// Exact text
//a[normalize-space()='DownLoad Link']
//a[normalize-space()='SHub Youtube Channel']
//a[normalize-space()='Join Training']

// Partial (contains)
//a[contains(normalize-space(),'Youtube')]
//a[contains(normalize-space(),'Training')]

// All links
//a
//a[@href]

Concepts: link text · partial link text · contains() · normalize-space()

50.4 Dropdowns (native <select>)

// The dropdown
//select[@id='cars']
//select[@name='cars']

// Option by visible text
//select[@id='cars']/option[normalize-space()='Volvo']
//select[@id='cars']/option[normalize-space()='Audi']

// Option by value
//select[@id='cars']/option[@value='volvo']

Java (Selenium) selecting from native dropdown

WebElement carsDropdown = driver.findElement(By.xpath("//select[@id='cars']"));
Select select = new Select(carsDropdown);
select.selectByVisibleText("Audi");
select.selectByValue("volvo");
select.selectByIndex(2);

50.5 Tables — dynamic row/column access

// Whole table / rows / headers / cells
//table
//table//tr
//table//th
//table//td

// Row by content
//a[normalize-space()='Joe.Root']/ancestor::tr

// Specific cell relative to a known value
//a[normalize-space()='Joe.Root']/ancestor::tr/td[3]
//a[normalize-space()='John.Smith']/ancestor::tr/td[4]
//a[normalize-space()='Kevin.Mathews']/ancestor::tr/td[2]

Concepts: dynamic table handling · ancestor axis · row-based + column-based XPath. This pattern is almost always asked.

50.6 XPath axes — full list

AxisSelectsExample
selfThe current node//div[self::div]
childDirect children//select/child::option
parentDirect parent//option/parent::select
descendantAll children, grand-children, ...//body/descendant::input
ancestorAll parents up to root//a/ancestor::tr
ancestor-or-selfSelf + ancestors
descendant-or-selfSelf + descendants — // is shorthand
followingAll nodes after closing tag//label/following::input[1]
following-siblingSiblings after//label/following-sibling::input
precedingAll nodes before opening tag//input/preceding::label[1]
preceding-siblingSiblings before
attributeAttributes//input/attribute::id

50.7 Indexing repeated elements

// First "Company" field
(//label[normalize-space()='Company']/following::input[1])[1]

// Second "Company" field
(//label[normalize-space()='Company']/following::input[1])[2]

// All Company-related inputs
//label[normalize-space()='Company']/following::input

50.8 Disabled, hidden, non-interactable elements

// All disabled
//*[@disabled]
//input[@disabled]

// Not disabled
//input[not(@disabled)]

// Hidden via attribute
//*[@hidden]

// Hidden via inline style
//*[contains(@style,'display:none')]
//*[contains(@style,'visibility:hidden')]

In Selenium, XPath can find a disabled element, but sendKeys() on it throws ElementNotInteractableException.

50.9 Alerts, prompts, modals

// Buttons that trigger alerts
//*[normalize-space()='Click To Open Window Alert']
//*[normalize-space()='Click To Open Window Prompt Alert']
//*[normalize-space()='Open Modal']

// Modal close (×)
//*[normalize-space()='×']

Java alert handling

driver.findElement(By.xpath("//*[normalize-space()='Click To Open Window Alert']")).click();
Alert alert = driver.switchTo().alert();
System.out.println(alert.getText());
alert.accept();   // OK
// alert.dismiss();  // Cancel
// alert.sendKeys("answer"); alert.accept();  // for prompt

50.10 Downloads & uploads

// Download link
//a[normalize-space()='Click to Download PNG File']
//a[contains(normalize-space(),'Download PNG')]

// File upload input
//input[@type='file']
// Java upload
driver.findElement(By.xpath("//input[@type='file']"))
      .sendKeys("C:\\Users\\Yash\\Desktop\\file.png");

50.11 Payment form (label-based)

//*[normalize-space()='Name on Card']/following::input[1]
//*[normalize-space()='Card Number']/following::input[1]
//*[normalize-space()='Expiry Date']/following::input[1]
//*[normalize-space()='CVV']/following::input[1]

//*[normalize-space()='Pay ₹999']
//*[contains(normalize-space(),'Pay')]

50.12 contains() and starts-with() — dynamic matching

// contains — partial text/attribute
//a[contains(normalize-space(),'Youtube')]
//*[contains(normalize-space(),'Payment')]
//*[contains(@id,'user')]

// starts-with — prefix
//input[starts-with(@id,'user')]
//*[starts-with(normalize-space(),'Click')]
//a[starts-with(normalize-space(),'SHub')]

50.13 and / or / not

// AND
//input[@id='userId' and @type='email']
//select[@id='cars' and @name='cars']

// OR
//input[@id='userId' or @name='email']
//*[normalize-space()='Submit' or normalize-space()='Pay ₹999']

// NOT
//input[not(@disabled)]
//a[not(contains(@href,'youtube'))]
//select[@id='cars']/option[not(@value='volvo')]

50.14 text() vs . vs normalize-space()

// Exact direct text only
//*[text()='Checkout here']

// Trimmed text
//*[normalize-space()='Checkout here']

// Text including descendants (uses "." for string value of node)
//*[contains(.,'Checkout here')]
ExpressionReads
text()Only the direct text of the element (not children)
.String value — concatenation of all descendant text
normalize-space()String value with collapsed whitespace

50.15 Empty / junk-space links

// Link without text
//a[not(normalize-space())]
//a[@href and not(normalize-space())]

// Link with extra spaces — use normalize-space, not text()
//a[normalize-space()='SHub Youtube Channel']    // works
//a[text()='SHub Youtube Channel']               // fails if leading/trailing spaces

50.16 Broken link XPath

//a[normalize-space()='This is a broken link']
//a[contains(normalize-space(),'broken link')]

To validate broken, GET the href and check the HTTP status >= 400.

50.17 iframes

// Locate iframe
//iframe
(//iframe)[1]
//iframe[contains(@src,'iframe')]
//iframe[@name='card-frame']

Java — switch into iframe before using inner elements

WebElement frame = driver.findElement(By.xpath("(//iframe)[1]"));
driver.switchTo().frame(frame);
driver.findElement(By.xpath("//input[@id='inside']")).sendKeys("hi");
driver.switchTo().defaultContent();   // return to main

Playwright avoids the switch entirely: page.frameLocator('iframe').getByRole('button').click()

50.18 Shadow DOM

Normal XPath does not pierce Shadow DOM. Selenium 4 exposes shadow roots via getShadowRoot(); inside, use CSS selectors (XPath isn't supported on shadow roots).

WebElement host = driver.findElement(By.cssSelector("shadow-host"));
SearchContext root = host.getShadowRoot();
WebElement inside = root.findElement(By.cssSelector("input"));
inside.sendKeys("hi");

Playwright pierces shadow DOM transparently — getByRole and CSS work across shadow boundaries.

50.19 SVG elements — use local-name()

// Plain //svg often fails because of namespaces
//svg                  // unreliable

// Use local-name()
//*[local-name()='svg']
//*[local-name()='svg']//*[local-name()='path']
//*[local-name()='svg' and @iconid='editon']

50.20 Multilingual text

//*[normalize-space()='করোনা সংক্রমণ বাড়াচ্ছে']
//*[normalize-space()='也支持中文']

50.21 Modal popups

//*[normalize-space()='Open Modal']
//*[normalize-space()='Bottom Modal']
//*[normalize-space()='×']

50.22 Pseudo-elements (::before / ::after)

XPath cannot locate CSS pseudo-elements. Read computed style via JS:

JavascriptExecutor js = (JavascriptExecutor) driver;
String content = (String) js.executeScript(
  "return window.getComputedStyle(arguments[0], '::before').getPropertyValue('content');",
  element);

50.23 Canvas

//canvas

XPath can find the canvas element, but cannot locate shapes/text drawn inside it. Interact by coordinates; visually assert with screenshots.

50.24 Spinner / loader (wait until it disappears)

//*[contains(@class,'loader')]
//*[contains(@class,'spinner')]
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.invisibilityOfElementLocated(
  By.xpath("//*[contains(@class,'loader') or contains(@class,'spinner')]")));

50.25 Invalid XPath — fix the common mistakes

WrongCorrect / fix
//label[..]//following:input[..] (typo, smart quote)//label[..]/following::input[..]
//a[normalise-space()=...] (UK spelling)//a[normalize-space()=...]
//input[@id='pass']div//input[@id='pass']/following::div[1]
//label[ends-with(text(),'X')] (XPath 1.0 lacks ends-with)//label[contains(normalize-space(),'X')]
//svg[@iconid='editon']//*[local-name()='svg' and @iconid='editon']
Curly/smart quotes (' or ")Plain ASCII ' or "
Selenium uses XPath 1.0Functions like ends-with(), matches() (regex), lower-case() are XPath 2.0 — not available. Use contains() + normalize-space() as workarounds.

50.26 Practice order for interviews

  1. Basic XPath by id / name / type
  2. Text-based XPath
  3. contains() for partial
  4. starts-with() for prefix
  5. normalize-space() for whitespace
  6. and / or conditions
  7. Parent–child relations
  8. following / preceding axes
  9. ancestor axis for tables
  10. Dropdown XPath + Selenium Select API
  11. Dynamic tables — row/column by content
  12. Disabled / hidden elements
  13. Alerts & modal popups
  14. iframe handling
  15. SVG with local-name()
  16. Shadow DOM limitations
  17. Upload / download elements
  18. not() for negation
  19. Invalid XPath debugging
  20. Dynamic loader waits

50.27 The top 10 XPaths to memorise

//input[@id='userId']
//label[normalize-space()='User Email']/following::input[1]
//a[contains(normalize-space(),'Youtube')]
//select[@id='cars']/option[normalize-space()='Audi']
//a[normalize-space()='Joe.Root']/ancestor::tr/td[3]
//input[not(@disabled)]
//*[local-name()='svg']
(//iframe)[1]
//*[normalize-space()='Open Modal']
//input[starts-with(@id,'user')]

Module 42 — XPath Q&A

How do you test an XPath quickly?
Chrome DevTools → Elements tab → Ctrl+F → paste XPath. The matching element(s) highlight; counter shows how many match. Same in Firefox.
Difference between / and //?
/ selects a direct child. // selects any descendant at any depth. //div//span finds spans anywhere inside divs; /html/body/div walks the exact path.
Why prefer normalize-space() over text()?
Text with leading/trailing/repeated whitespace breaks text() equality. normalize-space() trims and collapses internal whitespace. Always prefer it for human-readable text matching.
What's the ancestor axis used for?
Climbing up the tree from a known element. Most common: find a table row that contains a specific cell. //a[normalize-space()='Alice']/ancestor::tr → the whole row.
How do you locate the Nth element matching a pattern?
Wrap the expression in parentheses and index it: (//button[normalize-space()='Edit'])[3]. The position predicate [3] applies to the outer set, not per-parent like //button[3].
Why does my XPath for an SVG icon return nothing?
SVG elements live in a different XML namespace. //svg often fails. Use //*[local-name()='svg'] — matches regardless of namespace.
Can XPath enter a Shadow DOM?
No. Selenium WebDriver requires getShadowRoot() and then CSS selectors inside. Playwright pierces shadow DOM transparently — getByRole and CSS work across boundaries.
How do you find a row in a dynamic table by content?
Anchor on a known cell value and climb to the row: //td[normalize-space()='alice@test.com']/ancestor::tr. From there, drill into specific columns: .../td[3] for the third cell.
Why does ends-with() fail in Selenium?
Selenium uses XPath 1.0; ends-with() is XPath 2.0. Workaround: substring(@id, string-length(@id) - 2) = 'foo' or just use contains() if the suffix is unique enough.
How do you locate a sibling of an element?
following-sibling:: or preceding-sibling::. //label[.='Email']/following-sibling::input = the input directly next to the label, in the same parent.
following vs following-sibling — difference?
following: ANY node after this element's closing tag, anywhere in the tree. following-sibling: only nodes with the SAME parent that come after. following is broader and slower; following-sibling is precise.
How do you find an element by partial id like user-12345 where the suffix is dynamic?
//input[starts-with(@id,'user-')] or //input[contains(@id,'user-')]. Don't bind to the full dynamic id.
How would you XPath-find a button that has both class and visible text constraints?
//button[contains(@class,'primary') and normalize-space()='Save']. Combine attribute and text predicates with and.
One-sentence pitch on XPath vs CSS vs Playwright locators?
"CSS is shorter for attribute / class queries; XPath shines for text and tree-walking (ancestor/sibling); but Playwright's getByRole/getByLabel abstract over both and stay stable across markup changes — prefer them, fall back to CSS, treat XPath as the escape hatch."