XPath for QA Interviews: Syntax, Examples and Common Questions
A concise XPath interview guide for QA engineers with practical HTML examples, axes, predicates, dynamic locators, common mistakes, and stable-selector advice.
In this guide
- 1.Use this sample HTML for the examples
- 2.Absolute and relative XPath
- 3.Understand /, //, @, and predicates
- 4.Match text carefully
- 5.Use contains() and starts-with() for deliberate partial matches
- 6.Combine conditions with and or
- 7.Navigate relationships with axes
- 8.Use indexing with the intended scope
- 9.Handle dynamic elements without guessing
- 10.Avoid common XPath mistakes
- 11.What is XPath syntax?
XPath selects nodes by navigating a document tree. In UI automation it is useful when a stable ID, accessible locator, or dedicated test attribute is unavailable and the relationship between elements is meaningful.
Interviewers usually want syntax plus judgment: can you write a precise locator, explain why it works, and avoid a brittle path tied to the current layout?
Use this sample HTML for the examples
<section id="checkout">
<h2>Payment</h2>
<form data-testid="payment-form">
<label for="email">Email</label>
<input id="email" name="email" type="email" />
<button class="btn primary" type="submit">Pay now</button>
</form>
<p class="status" data-state="pending">Waiting</p>
</section>In real automation, prefer a unique stable locator such as id, an accessible role/name supported by the tool, or a deliberate data-testid. Use XPath when it expresses a stable attribute or relationship more clearly than the alternatives.
Absolute and relative XPath
An absolute path starts at the document root and describes every level. A relative XPath can search from the current context and is usually easier to keep stable.
/html/body/main/section/form/button
//button[@type="submit"]Understand /, //, @, and predicates
| Syntax | Meaning | Example |
|---|---|---|
| / | Select a direct child step | //form/button |
| // | Select matching descendants from the current context | //section//input |
| @ | Refer to an attribute | //input[@name="email"] |
| [...] | Filter the selected node set | //button[@type="submit"] |
| * | Match any element node in the step | //*[@data-testid="payment-form"] |
// is convenient, but a broad search such as //* can be slow and ambiguous. Add a stable element name, attribute, or container when it improves precision.
Match text carefully
//button[text()="Pay now"]
//button[normalize-space(.)="Pay now"]
//h2[contains(normalize-space(.), "Payment")]text() targets direct text nodes, while . represents the string value of the current element including descendant text. normalize-space(.) is useful when harmless surrounding whitespace varies. Text locators may break under copy changes or localization, so use them only when the visible label is the intended contract.
Use contains() and starts-with() for deliberate partial matches
//button[contains(@class, "primary")]
//input[starts-with(@name, "email")]
//*[@data-state and starts-with(@data-state, "pend")]Partial matching helps with controlled prefixes or compound attributes, but it can match unintended values. For an HTML class token, a simple substring can confuse primary with primary-alt; a stable dedicated attribute is clearer when the application can provide one.
Combine conditions with and or
//input[@name="email" and @type="email"]
//button[@type="submit" or @data-action="submit-payment"]and narrows the same candidate to nodes meeting both conditions. or accepts either condition and should be used only when both forms are legitimately supported, not to hide unstable markup.
Use indexing with the intended scope
(//button[@type="submit"])[1]
//form[@data-testid="payment-form"]/button[1]Parentheses make [1] apply to the complete result set. Without them, [1] can mean the first matching child for each parent in that step. Indexing is fragile when order can change, so first look for a stable distinguishing attribute or container.
Handle dynamic elements without guessing
A dynamic locator should rely on the stable part of the contract: a prefix, semantic attribute, associated label, or stable container. Do not automatically strip a changing suffix if the remaining prefix matches several elements.
//section[@id="checkout"]//input[@name="email"]
//*[@data-testid="payment-form"]//button[@type="submit"]Avoid common XPath mistakes
- Do not copy a long absolute XPath from browser tools without understanding it.
- Do not use an index to hide the fact that a locator matches multiple unrelated elements.
- Do not assume
contains(@class, "x")performs exact class-token matching. - Do not depend on visible text when localization or routine copy edits are expected.
- Do not search the entire page when a stable component container can scope the locator.
- Do not replace stable IDs, accessible locators, or test attributes with XPath merely to demonstrate XPath.
- Always verify uniqueness and the intended target in the real rendered DOM.
What is XPath syntax?
XPath syntax describes a path through a document tree. A location step can specify an axis, a node test, and predicates. In //input[@name="email"], // searches descendants, input is the node test, and [@name="email"] filters by an attribute. I prefer stable IDs, accessible locators, or test attributes when available, and use XPath for stable attributes or element relationships.
//element[@attribute="value"]
//container//element[condition]
//label[normalize-space(.)="Name"]/following::input[1]