Reputation: 73
I'm using Python-Webdriver to automate a "click" action. Here is my code:
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.common.exceptions import InvalidSelectorException
LOGIN_BUTTON = (By.XPATH, '//a[contains(@class,"aui-nav-link login-link")]')
NEWS_OPTION = (By.ID, 'blq-nav-news')
driver = webdriver.Chrome()
driver.implicitly_wait(30)
driver.get("http://bbc.co.uk/")
myDynamicElement = driver.find_element(NEWS_OPTION)
myDynamicElement.click()
The console generates an exception as below
raise InvalidSelectorException("Invalid locator values passed in")
selenium.common.exceptions.InvalidSelectorException: Message: Invalid locator values passed in
But, if I change the line
"myDynamicElement = driver.find_element(NEWS_OPTION) "
to
"myDynamicElement = driver.find_element_by_id('blq-nav-news') "
, there is no exception and the script works as expected.
I figured out the root cause is we don't use
find_element_by_*
. So I'd like to know this is limitation on Python-Webdriver ? whether we have a solution to fix my issue without changing my code as I did.
Upvotes: 6
Views: 14540
Reputation: 474191
According to the documentation, you can use either find_element_by_*
shortcuts, or find_element()
and find_elements()
"private" methods directly:
Apart from the public methods given above, there are two private methods which might be useful with locators in page objects. These are the two private methods: find_element and find_elements.
Example usage:
from selenium.webdriver.common.by import By driver.find_element(By.XPATH, '//button[text()="Some text"]') driver.find_elements(By.XPATH, '//button')
But, in your case, instead of passing 2 parameters to find_element()
, you are passing a single one - a tuple
NEWS_OPTION
. You just need to unpack the tuple into positional arguments:
NEWS_OPTION = (By.ID, 'blq-nav-news')
myDynamicElement = driver.find_element(*NEWS_OPTION)
Or, just as an alternative, you could use keyword arguments also:
NEWS_OPTION = {'by': By.ID, 'value': 'blq-nav-news'}
myDynamicElement = driver.find_element(**NEWS_OPTION)
And, also, whenever you have any doubts how things should work, just dig up the source code and clarify it for yourself. In this case, see how find_element_by_id()
method is actually implemented:
def find_element_by_id(self, id_):
"""Finds an element by id.
:Args:
- id\_ - The id of the element to be found.
:Usage:
driver.find_element_by_id('foo')
"""
return self.find_element(by=By.ID, value=id_)
Upvotes: 8
Reputation: 16201
According to the api doc private method find_elements
and find_element
takes two parameters
each and you are passing one which is obviously wrong.
And,
myDynamicElement = driver.find_element(NEWS_OPTION)
and
myDynamicElement = driver.find_element_by_id('blq-nav-news')
are NOT same. driver.find_element_by_id
takes one parameter and thus this work and find_element
takes two so that fails.
So, literally you should be using
myDynamicElement = driver.find_element(By.ID,'blq-nav-news')
EDIT
NEWS_OPTION = (By.ID, 'blq-nav-news')
will return you ('id', 'blq-nav-news')
whereas find_element()
expects the first parameter to be the implementation of By
class mechanism to locate the element not simply a string.
A direct call of NEWS_OPTION = (By.ID, 'blq-nav-news')
has returned me a tuple
and the attribute of By.ID
which is 'id'
A simple print out of NEWS_OPTION = (By.ID, 'blq-nav-news')
provided me
('id', 'blq-nav-news')
And, here is the source of By
class
class By(object):
"""
Set of supported locator strategies.
"""
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
@classmethod
def is_valid(cls, by):
for attr in dir(cls):
if by == getattr(cls, attr):
return True
return False
Upvotes: 2