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)
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
| Axis | Selects | Example |
|---|---|---|
self | The current node | //div[self::div] |
child | Direct children | //select/child::option |
parent | Direct parent | //option/parent::select |
descendant | All children, grand-children, ... | //body/descendant::input |
ancestor | All parents up to root | //a/ancestor::tr |
ancestor-or-self | Self + ancestors | — |
descendant-or-self | Self + descendants — // is shorthand | — |
following | All nodes after closing tag | //label/following::input[1] |
following-sibling | Siblings after | //label/following-sibling::input |
preceding | All nodes before opening tag | //input/preceding::label[1] |
preceding-sibling | Siblings before | — |
attribute | Attributes | //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')]
| Expression | Reads |
|---|---|
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
| Wrong | Correct / 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 " |
ends-with(), matches() (regex), lower-case() are XPath 2.0 — not available. Use contains() + normalize-space() as workarounds.50.26 Practice order for interviews
- Basic XPath by id / name / type
- Text-based XPath
contains()for partialstarts-with()for prefixnormalize-space()for whitespaceand/orconditions- Parent–child relations
following/precedingaxesancestoraxis for tables- Dropdown XPath + Selenium Select API
- Dynamic tables — row/column by content
- Disabled / hidden elements
- Alerts & modal popups
- iframe handling
- SVG with
local-name() - Shadow DOM limitations
- Upload / download elements
not()for negation- Invalid XPath debugging
- 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?
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() equality. normalize-space() trims and collapses internal whitespace. Always prefer it for human-readable text matching.What's the ancestor axis used for?
//a[normalize-space()='Alice']/ancestor::tr → the whole row.How do you locate the Nth element matching a pattern?
(//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 often fails. Use //*[local-name()='svg'] — matches regardless of namespace.Can XPath enter a Shadow DOM?
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?
//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?
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?
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?
getByRole/getByLabel abstract over both and stay stable across markup changes — prefer them, fall back to CSS, treat XPath as the escape hatch."