398

I'm trying to test a complicated JavaScript interface with Selenium (using the Python interface, and across multiple browsers). I have a number of buttons of the form:

<div>My Button</div>

I'd like to be able to search for buttons based on "My Button" (or non-case-sensitive, partial matches such as "my button" or "button").

I'm finding this amazingly difficult, to the extent to which I feel like I'm missing something obvious. The best thing I have so far is:

driver.find_elements_by_xpath('//div[contains(text(), "' + text + '")]')

This is case-sensitive, however. The other thing I've tried is iterating through all the divs on the page, and checking the element.text property. However, every time you get a situation of the form:

<div class="outer"><div class="inner">My Button</div></div>

div.outer also has "My Button" as the text. To fix that, I've tried looking to see if div.outer is the parent of div.inner, but I couldn't figure out how to do that (element.get_element_by_xpath('..') returns an element's parent, but it tests not equal to div.outer).

Also, iterating through all the elements on the page seems to be really slow, at least using the Chrome webdriver.

Ideas?


I asked (and answered) a more specific version here: How can I get text of an element in Selenium WebDriver, without including child element text?

1

13 Answers 13

503

Try the following:

driver.find_elements_by_xpath("//*[contains(text(), 'My Button')]")
7
  • 7
    Thank you for the reply, it was 50% of what I needed (got me started). The form I arrived to is this "(//*[contains(text(), '" + text + "')] | //*[@value='" + text + "'])" it will search for given text not only inside element nodes, but also inside input elements whose text was set via 'value' attribute i.e. <button value="My Button" /> . Though do note, the value must be strict match, not just contain the text. Commented Oct 11, 2014 at 20:37
  • 17
    Also worth mentioning for other search engine visitors: if you're looking for a link, there are find_element(s)_by_link_text and find_element(s)_by_partial_link_text methods Commented Nov 17, 2014 at 23:45
  • 4
    What if the text is dynamic? That is, might contain quotes. Wouldn't that break this solution?
    – IcedDante
    Commented Jul 8, 2015 at 17:59
  • 4
    Searching for certain names seems to break this. Take the following for an example: "//*[contains(text(), '"+username+"')]" if username = "O'Reilly"; then the xpath would become invalid. Is there a way around this? Commented Jun 7, 2016 at 19:37
  • 3
    It doesn't seem to work when the target text has multiple lines.
    – Shawn
    Commented Nov 20, 2016 at 18:25
61

In the HTML which you have provided:

<div>My Button</div>

The text My Button is the innerHTML and have no whitespaces around it so you can easily use text() as follows:

my_element = driver.find_element_by_xpath("//div[text()='My Button']")

Note: text() selects all text node children of the context node


Text with leading/trailing spaces

In case the relevant text containing whitespaces either in the beginning:

<div>   My Button</div>

or at the end:

<div>My Button   </div>

or at both the ends:

<div> My Button </div>

In these cases you have two options:

  • You can use contains() function which determines whether the first argument string contains the second argument string and returns boolean true or false as follows:

    my_element = driver.find_element_by_xpath("//div[contains(., 'My Button')]")
    
  • You can use normalize-space() function which strips leading and trailing white-space from a string, replaces sequences of whitespace characters by a single space, and returns the resulting string as follows:

    driver.find_element_by_xpath("//div[normalize-space()='My Button']")
    

XPath expression for variable text

In case the text is a variable, you can use:

foo= "foo_bar"
my_element = driver.find_element_by_xpath("//div[.='" + foo + "']")
2
  • 1
    you can also use * as a wildcard to select any type of element, e.g if you were using find_elements_by_xpath
    – 9 Guy
    Commented Dec 23, 2020 at 18:50
  • 1
    @9Guy yes, that's true. Commented Dec 23, 2020 at 19:05
43

You could try an XPath expression like:

'//div[contains(text(), "{0}") and @class="inner"]'.format(text)
6
  • Thanks... so that helps distinguish inner from outer, but that actually works fine with xpath, I was only having that problem iterating through all the divs. My problem with xpath is I can't figure out how to make it case-insensitive?
    – josh
    Commented Sep 7, 2012 at 19:33
  • 2
    xpath 2.0 has a lower-case function, so this should work: '//div[contains(lower-case(text()), "{0}")]'.format(text)
    – andrean
    Commented Sep 7, 2012 at 20:18
  • thanks! although, my understanding is that xpath 2.0 isn't supported across the major browsers...
    – josh
    Commented Sep 7, 2012 at 21:05
  • selenium evaluates xpath expressions directly with the browser's own methods, so it depends which browser are you using with selenium. generally only ie 6,7 and 8 should not support xpath 2.0.
    – andrean
    Commented Sep 7, 2012 at 21:14
  • .format isn't recognized in my eclipse. it gives and error. any idea, why?
    – anujin
    Commented Jul 26, 2013 at 7:15
33

//* will be looking for any HTML tag. Where if some text is common for Button and div tag and if //* is categories it will not work as expected. If you need to select any specific then You can get it by declaring HTML Element tag. Like:

driver.find_element_by_xpath("//div[contains(text(),'Add User')]")
driver.find_element_by_xpath("//button[contains(text(),'Add User')]")
1
  • 1
    Thanks! This version worked for me but the latest version of Selenium uses: driver: driver.FindElement(By.XPath...
    – Nomad77
    Commented May 13, 2023 at 16:30
17

You can also use it with Page Object Pattern, e.g:

Try this code:

@FindBy(xpath = "//*[contains(text(), 'Best Choice')]")
WebElement buttonBestChoice;
9

Interestingly virtually all answers revolve around XPath's function contains(), neglecting the fact it is case sensitive - contrary to the OP's ask.

If you need case insensitivity, that is achievable in XPath 1.0 (the version contemporary browsers support), though it's not pretty - by using the translate() function. It substitutes a source character to its desired form, by using a translation table.

Constructing a table of all upper case characters will effectively transform the node's text to its lower() form - allowing case-insensitive matching (here's just the prerogative):

[
  contains(
    translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),
    'my button'
  )
]
# will match a source text like "mY bUTTon"

The full Python call:

driver.find_elements_by_xpath("//*[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZЙ', 'abcdefghijklmnopqrstuvwxyzй'), 'my button')]")

Naturally this approach has its drawbacks - as given, it'll work only for Latin text; if you want to cover Unicode characters - you'll have to add them to the translation table. I've done that in the sample above - the last character is the Cyrillic symbol "Й".


And if we lived in a world where browsers supported XPath 2.0 and up (🤞, but not happening any time soon ☹️), we could having used the functions lower-case() (yet, not fully locale-aware), and matches (for regex searches, with the case-insensitive ('i') flag).

6

Simply use This:

driver.find_elements_by_xpath('//*[text() = "My Button"]')
3

Similar problem: Find <button>Advanced...</button>

Maybe this will give you some ideas (please transfer the concept from Java to Python):

wait.until(ExpectedConditions.elementToBeClickable(//
    driver.findElements(By.tagName("button")).stream().filter(i -> i.getText().equals("Advanced...")).findFirst().get())).click();
3

In Selenium 4 find_element_by_xpath is deprecated. See the docs, https://selenium-python.readthedocs.io/locating-elements.html

Here are two options:

from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
service = Service('D:\\Path\\to\\geckodriver.exe')
driver = webdriver.Firefox(service=service)

element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//*[contains(text(), 'My Button')]")))

OR

element = driver.find_element(By.XPATH, "//*[contains(text(), 'My Button')]")
3

Text Containing

To find an element with the text (displayed in the HTML page) containing a specific text, you can use the following XPath expression:

driver.find_element(By.XPATH, "//*[contains(text(), 'text_to_be_contained')]")
  • '*', selects all elements in the document, regardless of the tag name. You can replace the asterisk (*) with a specific tag name if you want to search within a particular tag type.
  • [contains(text(), 'text_to_be_contained')] is a condition that checks if the text contains the specified text ('text_to_be_contained'). This will match elements whose text attribute contains the specified text, even if it is part of a larger text value.
  • 'text_to_be_contained', should be the text you want to find

Attribute Containing

To find an element with the attribute containing a specific text, you can use the following XPath expression:

//*[contains(@attribute_name, 'text_to_be_contained')]
  • Replace 'attribute_name' with the actual name of the attribute you want to search within, such as: class, name, href, value, or any other

Text/Attribute Is Equal

To find an element with the text or attribute a matching exactly a specific text, you can use the following XPath expressions: For the text

For the text:

//*[text()='exact_text']

For the attribute:

//*[@attribute_name='exact_text']
  • 'exact_text', should be the text you want to find

Note: All methods are case sensitive

1
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(text(), 'YourTextHere')]")));
assertNotNull(driver.findElement(By.xpath("//*[contains(text(), 'YourTextHere')]")));
String yourButtonName = driver.findElement(By.xpath("//*[contains(text(), 'YourTextHere')]")).getAttribute("innerText");
assertTrue(yourButtonName.equalsIgnoreCase("YourTextHere"));
0
0

Use driver.find_elements_by_xpath and matches regex matching function for the case insensitive search of the element by its text.

driver.find_elements_by_xpath("//*[matches(.,'My Button', 'i')]")
1
  • 1
    matches() is an xpath 2.0 function, and the browsers regretfully have support only for 1.0. Commented Jan 29, 2019 at 8:56
-21

Try this. It's very easy:

driver.getPageSource().contains("text to search");

This really worked for me in Selenium WebDriver.

6
  • 13
    It does not work if the text is generated by JavaScript.
    – palacsint
    Commented Feb 8, 2014 at 21:27
  • 3
    This is a very way of checking it, because you are transferring the whole contents of the page over the wire. For very small pages this is acceptablem but for very large pages you are transfer all the contents of the file and checking on the server side. A better approach would be to do it on the client side with xpath, javascript or css.
    – thomas.han
    Commented Aug 25, 2014 at 8:22
  • 1
    I would think that the whole page source would already need to be transferred over the wire for the browser to render it?
    – René
    Commented Sep 5, 2014 at 18:31
  • 3
    Josh is asking how to find the element by text, not to test if the text is present in the source of the page.
    – Cedric
    Commented May 28, 2015 at 17:19
  • 1
    For instances where all is needed is to find a static text on a page this solution is good enough. (It helped in my case).
    – Karlth
    Commented Sep 20, 2015 at 23:00

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.