I'm trying to make a test for my site. Having troubles on some user form. The trick is, that the number of text fields in the form varies depending in user options (disabled ones are present in the code, but have a style <displayed: none;> tag), so I'm trying to find more flexible approach than locating every element one-by-one and filling the forms with try/except blocks.
I'm using an xpath locator
text_fields = driver.find_elements_by_xpath("//div[#class='form-line']/div[#class='form-inputs']/input[#type='text' and not(ancestor::div[#style='display: none;'])]")
The trouble is that firebug locates only needed elements, but when I use it my selenium script, printing the list of text_fields gives me all the elements, even without a <displayed: none;> tag
How can I get only visible elements?
PS Sorry for my bad English ^_^
You can get all the form elements the usual way, then iterate on the list and remove those elements that do not return true on is_displayed().
Try the contains() method:
text_fields = driver.find_elements_by_xpath(
"//div[#class='form-line']/div[#class='form-inputs']/input[#type='text' and
not(ancestor::div[contains(#style, 'display: none;')])]")
The important part is:
div[contains(#style, 'display: none;')]
Note, that if the style contains the string display:none; or display:none, the selector won't match.
I use the following and it works great.
self.assertTrue(driver.find_element_by_xpath("//div[#id='game_icons']/div/div[2]/div/a/img"))
This is for Selenium and Python of course.
Related
If I am implementing string locators, such as:
continue_button: str = "button:has-text(\"Continue\")"
If there are multiple buttons on the same page that say continue, but are for different paths, how do I select the correct continue... is there a way to add an index to that string locator?
There is several good practices for creating locators/selectors.
Using playwright there is official documentation for each common and unique selector on how-to and what-is doing.
More information in https://playwright.dev/docs/selectors#text-selector
About your case, i would suggest always to use an parent selector for locating an element.
When there is a button, try to find its unique parent.
By id
By unique class
Something else unique.
Example:
<dv id=test>
<button id=continue-test>Continue</button>
</div>
In this case you can use the unique id of the button and not the text.
Selector css: #continue-test
But if you, don't have an unique identifier for the button you can use the parent and go down to the button.
Selector css: #test > button
Matching text using css is not possible, but with XPATH can look like this:
//button[text()="Continue"]
This selector MATCHES the text using "equals".
Using playwright:
button:has-text("Continue")
Using has-text and quotes - matches the text using equals.
If you are using another selector for example text=Continue, this will match all elements that CONTAINS the text "Continue"
All this is explained with example in the official documentation for playwright selectors.
That does not mean to not use XPATH to achieve the goals.
CSS selectors are fast but kind of restricted to work with text.
Xpath is quite slower but much more powerful to work in text/parent/child elements etc.
I would suggest always to use an parent element with unique identifier and go down to reach your actual element, which will receive the interaction.
The fact that I love Playwright is because of scenarios like this and how easily it can be handled.
If you have a string named abc and there are multiple occurrences of that string on a single page, then you can use the nth-match criteria to pick the nth element.
For eg ,
await page.locator(':nth-match(:text("abc"), 3)').click();
will select the 3rd occurrence of the word abc. Similarly, in your case, if you want to select the first or second or third, you can simply do
await page.locator(':nth-match(:text("Continue"), 1)').click();
await page.locator(':nth-match(:text("Continue"), 2)').click();
await page.locator(':nth-match(:text("Continue"), 3)').click();
Please refer to the Selectors documentation for Playwright -> Selectors
This is different than the nth-child concept as mentioned
Unlike :nth-child(), elements do not have to be siblings, they could
be anywhere on the page. In the snippet above, all three buttons match
:text("Buy") selector, and :nth-match() selects the third button.
I am coding a python web automation selenium script.
In the script, I use driver.find_element_by_xpath('xpath') to find elements on Binary.com. This means I would have to preload Binary.com and copy xpaths of the elements I need to find. For most elements the method works but for a few I realised that the Xpath is unique to each login.
For example, if I login in now and try to copy the xpath of a certain element it will be //*[#id="tp1602844250562"] but if the page is reloaded or I try to login on a different window the xpath would have then changed to //*[#id="tp1602844157070"]. Please note they are not the same id numbers. This means I cannot use one xpath on a separate page login
The desired element has an HTML code:
<input type="text" class="time hasTimepicker" tab-index="-1" value="00:00" readonly="" id="tp1602844157070">
Refer to the supplied image for clear html code
You can try below with class instead of id as the id is getting pulled from DB i guess-
//div[#class='date-time']//input[contains(#class,'time')]
Why don't you use the class instead of the id? Try this xpath:
driver.find_element_by_xpath('//input[#class = "time hasTimepicker"]')
Try changing your xpath expression to
//input[starts-with(#id,"tp")]
or, if it's not always input
//*[starts-with(#id,"tp")]
To find the input element with that class use.
driver.find_element_by_css_selector("input.time.hasTimepicker")
I have a xpath as:
//*[#id="jobs-search-box-keyword-id-ember968"]
The number 968 constantly keeps on changing after every reload.
Rest of the string remains constant.
How to I find the constantly changing xpath?
You can use partial id with contains()
//*[contains(#id, "jobs-search-box-keyword-id-ember")]
You can try using starts-with below,
//*[starts-with(#id,'jobs-search-box-keyword-id-ember')]
The details provided is insufficient to to provide the accurate result. Still you can follow the below code references
In //*[#id="jobs-search-box-keyword-id-ember968"] the last number 968 keeps changing. but if you make this like //*[starts-with(#id,'jobs-search-box-keyword-id-ember')] then there might be possibility that you can have more then one element with the same partial is i.e. jobs-search-box-keyword-id-ember in this case it will locate on 1st matching element. that may not be your expected one
Use the tag name lets say element is an input tag whose id is jobs-search-box-keyword-id-ember968
Xpath - //input[starts-with(#id,'jobs-search-box-keyword-id-ember')]
CSS - input[id^='jobs-search-box-keyword-id-ember']
Use the relevant parent element to make this more specific. e.g the element is in parent tag <div class="container">
Xpath- //div[#class='container']//input[starts-with(#id,'jobs-search-box-keyword-id-ember')]
CSS - div.container input[id^='jobs-search-box-keyword-id-ember']
This worked for me:
Locator:
JOBS_SEARCH_BOX_XPATH = "//*[contains(#id,'jobs-search-box-keyword-id-ember')]"
Code:
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, JOBS_SEARCH_BOX_XPATH)))
.send_keys("SDET")
I want to create function that will allow to fill registration, authorization and other text forms. Something like:
def fill_form(my_list):
forms = driver.find_elements_by_xpath(common_xpath)
for element in forms:
element.send_keys(my_list[forms.index(element)])
It should get as arguments a list of text values to send into <input type="text">, <input type="password"> and <textarea> elements.
So far I have:
common_xpath ='//input[#type="text" or #type="password"]'
but I can't understand how to add textarea element to this XPath to match it also
A simpler and more future-proof strategy would be separate the XPath expression into 2/3 distinct expressions that search for required WebElements, but if you really need to use a single XPath expression I imagine you could use the | operator to devise something along the lines of:
//input[#type="text" or #type="password"] | //textarea
Not sure if this is going to do what you look for, but, from what I can see this Xpath could get the job done:
common_xpath = "//*[self::input[#type='text'] or self::input[#type='password'] or self::textarea]"
I don't program in Python, but tried that one in Chrome's console (using $x("...")), and it seems to do what you want. You should consider calling that XPath inside the form (path/to/your/form//*...), to make it more specific.
TIL that you could select different tags in Xpath :)
Check this related answer for more info.
Finally, as a personal note, I'm not that experience with Selenium, but I would suggest you to consider using the PageObject model design pattern, to make the tests easier to maintain. Hope it works for you.
I am not aware of Python, but this is what I would do in a JAVa
String [] commanXpath= {"text", "password"};
String xpathVar= "//input[#type='"+commanXpath[index]+"']";
System.out.println(xpathVar);
By common_xpath= By.xpath(xpathVar);
See if you can implement similar logic in Python. Can you also update original post with exact html tags.
In the case that I want the first use of class so I don't have to guess the find_elements_by_xpath(), what are my options for this? The goal is to write less code, assuring any changes to the source I am scraping can be fixed easily. Is it possible to essentially
find_elements_by_css_selector('source[1]')
This code does not work as is though.
I am using selenium with Python and will likely be using phantomJS as the webdriver (Firefox for testing).
In CSS Selectors, square brackets select attributes, so your sample code is trying to select the 'source' type element with an attribute named 1, eg
<source 1="your_element" />
Whereas I gather you're trying to find the first in a list that looks like this:
<source>Blah</source>
<source>Rah</source>
If you just want the first matching element, you can use the singular form:
element = find_element_by_css_selector("source")
The form you were using returns a list, so you're also able to get the n-1th element to find the nth instance on the page (Lists index from 0):
element = find_elements_by_css_selector("source")[0]
Finally, if you want your CSS selectors to be completely explicit in which element they're finding, you can use the nth-of-type selector:
element = find_element_by_css_selector("source:nth-of-type(1)")
You might find some other helpful information at this blog post from Sauce Labs to help you write flexible selectors to replace your XPath.