To escape a single quote (apostrophe) in XPath, you don’t escape it at all: XPath 1.0, the version browsers and Selenium WebDriver use, has no escape character inside a string literal. Wrap the text in double quotes instead, //button[text()="We'll be back"]. If the text contains both an apostrophe and a double quote, build the string with concat(). In Java, Selenium’s own Quotes.escape() does this for you.
Key takeaways
- An apostrophe inside a single-quoted XPath literal ends the string early, so the whole expression is invalid and Selenium throws
InvalidSelectorException. - Wrap the literal in double quotes instead. In Java source that means
\"around it. - Use
concat()when the text contains both quote characters.Quotes.escape()in selenium-support generates it. \'and''do not work in browser XPath. Check for a curly apostrophe (’) in the page text too.
Why does an apostrophe break an XPath expression?
XPath 1.0 defines a string literal as any run of characters between two double quotes, or between two single quotes, and nothing else. There is no backslash escape and no doubled-quote escape. So in //button[text()='We'll be back'] the literal is 'We', and ll be back'] is a syntax error. The expression is invalid, not just unmatched.
In Selenium 4 that surfaces as an exception:
// Throws org.openqa.selenium.InvalidSelectorException
driver.findElement(By.xpath("//button[text()='We'll be back']"));
We ran this exact call with Selenium 4.51.0 against headless Chrome, and it threw InvalidSelectorException. findElements() throws the same way rather than returning an empty list, because the selector itself is rejected.
How do you write the XPath with double quotes in Java?
Swap the quote character around the literal. Java strings are already delimited by double quotes, so the inner ones need a backslash in the Java source. The backslash is Java syntax and never reaches the XPath engine:
WebElement button = driver.findElement(By.xpath("//button[text()=\"We'll be back\"]"));
List<WebElement> matches = driver.findElements(By.xpath("//button[contains(., \"We'll\")]"));
The same rule works for attributes, for example //input[@placeholder="Don't type here"].
When the visible text is split across child elements, as in <a><span>Today's</span> deals</a>, text() only sees the direct text node and the match fails. Compare the full string value instead, with normalize-space() or .:
driver.findElement(By.xpath("//a[normalize-space()=\"Today's deals\"]"));
Older versions of this article used @text. That attribute exists in Appium’s Android page source, not in HTML. On a web page, visible text is a text node, so use text(), . or normalize-space().
What if the text has both single and double quotes?
Neither quote character can wrap a string that contains both, so split the string and glue the pieces together with concat(). Each piece goes in whichever quote it does not contain:
She said "it's fine"
concat('She said "it', "'", 's fine"')
In Java you don’t have to write this by hand. org.openqa.selenium.support.ui.Quotes, part of the selenium-support module that ships with selenium-java, returns a valid literal for any input:
import org.openqa.selenium.support.ui.Quotes;
String label = "She said \"it's fine\"";
WebElement button = driver.findElement(
By.xpath("//button[normalize-space()=" + Quotes.escape(label) + "]"));
Quotes.escape() returns "We'll" for We'll, 'say "hi"' for say "hi", and a concat() expression when both characters are present. It adds the surrounding quotes itself, so don’t add your own. Selenium uses it internally for Select.selectByVisibleText(), which is why selecting a dropdown option such as Men's shoes works without any escaping on your side.
Escaping XPath quotes in Python
The Python bindings keep their equivalent private (Select._escape_string), so write a small helper:
def xpath_literal(s):
if "'" not in s:
return f"'{s}'"
if '"' not in s:
return f'"{s}"'
parts = s.split("'")
return "concat(" + ", \"'\", ".join(f"'{p}'" for p in parts) + ")"
driver.find_element(By.XPATH, f"//button[normalize-space()={xpath_literal(label)}]")
Selenium 4 removed the find_element_by_xpath() style helpers, so use find_element(By.XPATH, ...) as above.
What does not work
We checked each of these in Chrome’s own XPath engine (document.evaluate) and in libxml2’s xmllint. All three fail with a syntax error:
| Attempt | Why it fails |
|---|---|
'We\'ll be back' | XPath has no backslash escape. The backslash is an ordinary character. |
'We''ll be back' | Doubling the quote is an XPath 2.0 rule. Browsers implement XPath 1.0. |
'We'll be back' | The apostrophe closes the literal. |
' only helps when the XPath sits inside an XML attribute, as in XSLT, because the XML parser decodes it before XPath sees the string. A Selenium locator is a plain string, so the entity is passed through literally and matches nothing.
Why does the XPath still find nothing?
If the expression is valid but returns no element, check the apostrophe on the page. Many CMSs and design tools convert ' to the typographic right single quote ’ (U+2019), and "We'll" does not match We’ll. Copy the text from the DOM in DevTools rather than from the screen, or normalize it in the expression:
//button[translate(text(), '’', "'")="We'll be back"]
If the element is added after page load, the locator can be right and the lookup still too early. Wrap it in an explicit wait in Selenium WebDriver before you start rewriting the XPath.
For links, By.linkText("Today's deals") and By.partialLinkText(...) take the text as a plain Java string with no XPath quoting at all, which is often the easier route. The guide to clicking a link by its text covers both, and selecting a dropdown option by visible text shows the Select class mentioned above. A stable id or a dedicated test attribute avoids the text match, and the quoting problem, entirely.
After the functional tests pass
Selenium tells you a page works for one user. LoadFocus checks the other side: load testing runs the same pages and APIs with many concurrent users from 25+ cloud regions, and visual regression testing finds visual differences between two versions of a page.
Frequently Asked Questions
How do you escape a single quote in XPath?
You can’t escape it inside a single-quoted literal in XPath 1.0. Put the text in double quotes instead, for example //p[text()="It's here"]. If the text has both quote types, build it with concat().
Why does my XPath break on an apostrophe?
The apostrophe closes the string literal early when you have wrapped the text in single quotes. The rest of the expression is then invalid, so Selenium throws InvalidSelectorException rather than returning no match.
Does a backslash or a doubled quote work in Selenium XPath?
No. Browsers implement XPath 1.0, which has neither. A doubled quote ('') is valid only in XPath 2.0 and later, which browsers do not support.
What if the text has both quote characters?
Use concat() to join pieces, each wrapped in the quote it does not contain. In Java, Quotes.escape() from selenium-support builds the expression for you.
Why does the XPath still find nothing after the fix?
The page may use a curly apostrophe (’) rather than a straight one (’), the text may be split across child elements so text() misses it, or the element may not be loaded yet. Check the DOM text in DevTools and use normalize-space() and an explicit wait.
Sources
- XPath 1.0, section 3.7 Lexical Structure (the
Literalproduction): https://www.w3.org/TR/1999/REC-xpath-19991116/ (checked October 2026) - Selenium Java
Quotessource: https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/support/ui/Quotes.java (checked October 2026) - Selenium Python
Selectsource (_escape_string): https://github.com/SeleniumHQ/selenium/blob/trunk/py/selenium/webdriver/support/select.py (checked October 2026)