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')
Related
I saw that part of my question was answered before: AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'
And I believe that now if I want to accomplish (for example):
driver.find_element_by_xpath('//*[#id="#ICClear"]').click()
I need to use:
driver.find_element("xpath", '//*[#id="#ICClear"]').click()
However, I'm very unsophisticated in programming, so in my code I, for better or worse, have one of my scripts "defining" this functionality as:
xpath = driver.find_element_by_xpath
So that later on I would use:
xpath("""//*[#id="#ICClear"]""").click()
(Note that I do more than just use the click method, I also send text, etc.)
I have about 20 or so scripts that import this definition of "xpath" and use it throughout. I'm not sure how to change my 'xpath' definition to work with the new format so that I can still reference it without refactoring all of the code that relies on this.
I haven't tested this, but I would expect this to do what you are asking for:
def xpath(xpath_expr):
return driver.find_element("xpath", xpath_expr)
This would replace your definition of xpath as driver.find_element_by_xpath in your common script.
i am using pytest with selenium for automation tests and POM. I have files with page objects which they inherit from base_page_objects, files with test code and conftest.py where they are all #pytest.fixtures. My question is, how to store for example 200 locators?
def current_email(self):
return self.driver.find_element(By.CSS_SELECTOR, "input[id='current-email']")
Right now i am using simple method for each locator but i'm guessing that isn't a proper way to store locators. How to store all locators?
you can create a list and append your locator there.
list=[]
def current_email(self):
for i in range(200):
variable=self.driver.find_element(By.CSS_SELECTOR, "input[id='current-email']")
list.append(variable)
print(list)
This will vary greatly depending on the application you are automating and your framework architecture, so it's up to you to decide what will be best.
That being said your current approach is not bad at all. By calling the method every time you want to interact with the element and not storing a reference you minimize the chance of getting StaleElementException.
If you want to have all locators in different files that is easily achievable since all you need is a tuple containing By and the locator itself
# locators.py
from selenium.webdriver.common.by import By
current_mail_locator = (By.CSS_SELECTOR, "input[id='current-email']")
Then you import locators into your POM files:
from locators import *
#property
def current_email(self):
return self.driver.find_element(*current_mail_locator)
Benefits of this approach is that all your locators will be organised neatly into files, so it makes them easier to fix when eventually they change.
It also makes sense to use the #property decorator for these methods that don't require arguments, so you can use them as if they were fields (without "()")
current_email.click()
Instead of:
current_email().click()
Could you please help me with a test scenario?
I'm starting to automate a project using python and selenium organized in page objects.
I have separated file for the locators, separated files for different pages (so far, only 2 pages) and separated file for the test itself.
In the locators file, I write my selector, which contains a variable. I need it to be variable because later on I will have more than 10 selectors with the same Xpath, where only the text changes. So, this text I set it as a variable.
class AgreementPageLocators:
.........
MAIN_SEARCH_PK = (By.XPATH, f'//div[contains(text(), {variable})]')
In the "agreement" page, I define the action what should be done. Basically is to add something from a dropdown box. Marked in blue, is where I call the locator which contains the variable.
In my test file, I call the agreements method, where I define the variable:
# add agreement if it doesn't exist
if len(browser.find_elements(*AgreementPageLocators.AGREEMENT_CRIME)) == 0:
agreement_page = AgreementPage(browser)
agreement_page.agreements(variable='text1')
else:
pass
Obviously this doesn't work, as my test returns:
MAIN_SEARCH_PK = (By.XPATH, f'//div[contains(text(), {variable})]')
E NameError: name 'variable' is not defined
By putting the desired text instead of the variable in the selector, everything works fine. But I want to make it dynamically and not rewriting lots of times the same function just with different selectors.
Could someone help me please?
Try to define MAIN_SEARCH_PK as
MAIN_SEARCH_PK = (By.XPATH, '//div[contains(text(), "{}")]')
and
def agreements(self, variable):
...
pluscrime = browser.find_element(*AgreementPageLocators.MAIN_SEARCH_PK.format(variable))
P.S. Do not add your code as image!!!
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
I am constructing a little wizard following Qt's classwizard example.
Now I set the subTitle of my QWizard instance to some html text that includes a link.
I know about QLabel.setOpenExternalLinks(True) but how do I achieve the same effect with a QWizard's subTitle?
I looked at QWizardOptions but there is nothing there.
Please take a look at the following image:
I want to make www.plugincafe.com open in the default browser.
Thanks for reading.
EDIT: I am already calling self.setSubTitleFormat(1) where self is the QWizard instance and 1 is the enum value for Qt::RichText because I don't know how to get the proper enum constant in PyQt.
I tried all possible 4 enum values but other than text styling or no it didn't change anything.
The string value with the embedded HTML is 'Obtain a unique ID from www.plugincafe.com or use <font color="maroon">%s</font> for testing purposes.' % PLUGIN_ID_TESTING
QWizard (and QWizardPage) only expose mimimal control over the QLabel used as subtitle, but you can just iterate through all child QLabels in your QWizard and call setOpenExternalLinks(True):
for label in wizard.findChildren(QLabel):
label.setOpenExternalLinks(True)
If you don't want to set it on all labels, then you need to check if you found the correct one.