How does starred expression works when using with find_element() method - python

def enter_text_textbox(self, locator, text):
ele = self.driver.find_element(*locator)
try:
logging.info("# Entering text in Textbox.")
ele.clear()
ele.send_keys(text)
return True
except NoSuchElementException:
return False
Here is a method enter_text_textbox which accepts locator and some text as parameters.
I passed locator as something like this:
self.email_field = (By.XPATH, "//input[#id='email']")
When I tried to get into this(self.driver.find_element(*locator)) method, it displayed
As from the image the methods contains parameters of ID only.
By running the test script(not displayed here), it worked perfectly fine.
I want to know how does this method know if this locator is made by XPATH or ID or CSS i.e. How the starred expression works?

self.driver.find_element(*locator)
unpacks into positional arguments. So locator[0] has to be xpath, and locator[1] has to be the other text argument (ID or CSS apparently)
It's equivalent (when locator has the proper number of arguments) to:
self.driver.find_element(locator[0],locator[1])
note:
Passing parameters in the wrong order will fail.
Passing not enough or too many parameters will also fail.
Since this isn't a variable argument function, this is bad practice & very unclear.

The image you are showing shows PyCharm telling you what the method uses by default. If you were to just call find_element() it would use By.ID as the by value and None as the value. As Jean-François Fabre stated, the * just unpacks your tuple into positional arguments

Related

What is a Selenium locator in Python specifically?

My question comes from trying to understand the following code (which is meant to wait for a particular element to be loaded on the page before proceeding):
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# ... start chromium_driver
wait_timeout = 10
wait = WebDriverWait(chromium_driver, wait_timeout)
target_id = "CookiePopup"
target_element = wait.until(EC.presence_of_element_located((By.ID, target_id)))
I can understand what a locator is conceptually ("a way to identify elements on a page"), but I'm trying to wrap my head around its structure and specification as an object in this context (namely, the signature of EC.presence_of_element_located(locator)). N.B., that the (By.ID, target_id) part in the code above needs to be enclosed in parenthesis; i.e.,
EC.presence_of_element_located(By.ID, target_id)
causes
TypeError: __init__() takes 2 positional arguments but 3 were given
The documentation explains that "[a locator] is the argument passed to the Finding element methods".
The Finding element methods page shows that the find_element() method in Python takes two arguments, which is the part that I find somewhat confusing:
vegetable = driver.find_element(By.CLASS_NAME, "tomatoes")
In addition, By.CLASS_NAME, By.ID etc. are actually properties that contain strings ("class name" and "id" respectively).
Compare this to the Java (or any of the other languages) code:
WebElement vegetable = driver.findElement(By.className("tomatoes"));
which makes more sense: By.className() is a method, which takes the (HTML) class name as an argument and returns a locator object that matches elements with that class name.
Given the above, would it be accurate to describe the locator as a tuple of two str, with the first string being the type of identifier used and the second string being the value of that identifier? And as a follow-up question, why is Python different in this way than the other languages?
The documentation doesn't make it clear (to me anyway) why you have to pass the arguments enclosed in parenthesis, but have a look at the source code for that function here:
https://www.selenium.dev/selenium/docs/api/py/_modules/selenium/webdriver/support/expected_conditions.html#presence_of_element_located
You can see that in the implementation of presence_of_element_located(), it takes whatever you pass as a locator and passes that argument on in a call to find_element(*locator)
Note that asterisk (*) is used in Python to unpack the contents of a tuple (or any iterable). For more info on that, see this answer:
https://stackoverflow.com/a/1993732

Unpacked operator in Selenium explicit wait

In my code I faced an issue, when I try to use an unpacking operator in an explicit wait, there is a problem: "Can't use starred expression here"
self.xpath = (locator, string)
WebDriverWait(self.sin.get_driver(),wait).until(EC.presence_of_element_located((*self.xpath)))
In this case is locator something like By.XPATH, and string - "//div[]//..." - xpath of an element on a page.
Can such problem be solved? Or is it impossible to use starred impressions in the explicit wait anyway?
In Selenium EC.presence_of_element_located(()) method is expecting to receive a By class object.
It is not intended to iterate over any kind of iteratable like list, tuple etc.

Selenium calling class elements doesn't work

I'm trying to push a button on a soccer bookmaker's web page using Selenium's Chromedriver. My problem is that nothing happens when I call Selenium's driver.find_elements_by_class_name('c-events__item c-events__item_col').
Fixed:
I was trying to get the info from: a class names 'c-events__more c-events__more_bets js-showMoreBets'.
using find_by_class_name() cannot handle spaces as it will think its compound classes, instead I used csselector and it works like a charm now.
driver.find_elements_by_css_selector('.c-events__item.c-events__item_col')
It's telling you that you can't use multiple classes within find_elements_by_class_name. You can only use one class at a time. Since the method returns a selenium object, you could just chain the method three times, but the better thing to do is to just use the css selector method.
selenium.common.exceptions.InvalidSelectorException: Message: invalid selector: Compound class names not permitted
You should use a different method. If you want to select based on multiple classes, I recommend using the find_elements_by_css_selector method on the driver object. The dots before the class names make what is called a class selector. You can read more about selectors here: https://www.w3schools.com/cssref/css_selectors.asp
driver.find_elements_by_css_selector('.c-events__more .c-events__more_bets .js-showMoreBets')
Since you're hitting an anchor tag, you might want to add the a to the selector as well. E.g.
driver.find_elements_by_css_selector('.c-events__more .c-events__more_bets .js-showMoreBets a')
This is always my goto page for using python selenium. Here are all of the methods available for use -> http://selenium-python.readthedocs.io/locating-elements.html
using find_by_class_name() cannot handle spaces as it will think its compound classes, instead I used csselector and it works like a charm now. driver.find_elements_by_css_selector('.c-events__item.c-events__item_col')

Selenium + Python expect specific already found element

I'd like to use expect on an object already located but I'm getting an error.
I am able to locate the parent only before it is clicked -- afterwards it mutates into "one-of-many-alike" input elements and only way to distinguish it is "value" property (not attribute thus cannot be accessed from XPath).
My code is (something like):
parent_element = self.driver.find_element_by_xpath("//div/div/input[position()=10]/div")
parent_element.click() # After this I cannot locate parent_element any more
child_element = WebDriverWait(self.driver,3).until(
expect.element_to_be_clickable(
parent_element.find_element_by_xpath("./div")
)
)
child_element.click()
Which leads to:
TypeError: find_element() argument after * must be an iterable, not function
Try this line:
child_element = WebDriverWait(parent_element, 3).until(
expect.element_to_be_clickable((By.XPATH, "./div")))
Note that element_to_be_clickable() should receive tuple of "by" and "value" as the only argument, but not WebElement
P.S. I assume that expect is ExpectedConditions

Can I give a click option another name?

I use click like this:
import click
#click.command(name='foo')
#click.option('--bar', required=True)
def do_something(bar):
print(bar)
So the name of the option and the name of the function parameter is the same. When it is not the same (e.g. when you want --id instead of --bar), I get:
TypeError: do_something() got an unexpected keyword argument 'id'
I don't want the parameter to be called id because that is a Python function. I don't want the CLI parameter to be called different, because it would be more cumbersome / less intuitive. How can I fix it?
You just need to add a non-option (doesn't start with -) argument in the click.option decorator. Click will use this as the name of the parameter to the function. This allows you to use Python keywords as option names.
Here is an example which uses id_ inside the function:
import click
#click.command(name='foo')
#click.option('--id', 'id_', required=True)
def do_something(id_):
print(id_)
There is an official example here in the --from option.

Categories

Resources