I am trying to create flashcards on quizlet.com with selenium. If you visit, you will see a "Create" button (or just a "+" depending on window size) in the navbar, when you click this it turns into a dropdown menu with 3 more buttons: 'Study Set', 'Folder' and 'Class'. (I am trying to click Study Set)
First, I am not even sure If I need to have selenium click the first 'Create' button to access the 'Study Set' button or if I can just jump straight to the 'Study Set' button. Anyway, here is the html related to the 'Create' button and 'Study Set' button, respectively:
<button type="button" aria-label="Create"><span>Create</span>/button>
and: (Note, all 3 buttons share the class 'UILink', the first button is the 'Study Set' button and the comments are mine)
<div class="s1ovpdog"> <!-- going to grab this div first because theres 85 elements with the class 'UILink' on the page, grabbing this div cuts it to 3-->
<button class="UILink" type="button"> <!-- then going to grab this button-->
<div class="c1ap9d88">
<div class="iiekfr8">
<div class="i1q3l8tw">
<svg aria-label="sets" class="AssemblyIcon AssemblyIcon--medium" role="img"><noscript></noscript>
<use xlink:href="#sets"></use><noscript></noscript></svg><span class="AssemblyMenuItem--title t1nsp0j0">Study set</span>
</div>
</div>
</div>
</button>
<button class="UILink" type="button">
<div class="c1ap9d88">
<div class="iiekfr8">
<div class="i1q3l8tw">
<svg aria-label="folder" class="AssemblyIcon AssemblyIcon--medium" role="img"><noscript></noscript>
<use xlink:href="#folder"></use><noscript></noscript></svg><span class="AssemblyMenuItem--title t1nsp0j0">Folder</span>
</div>
</div>
</div>
</button>
<button class="UILink" type="button">
<div class="c1ap9d88">
<div class="iiekfr8">
<div class="i1q3l8tw">
<svg aria-label="class" class="AssemblyIcon AssemblyIcon--medium" role="img"><noscript></noscript>
<use xlink:href="#class"></use><noscript></noscript></svg><span class="AssemblyMenuItem--title t1nsp0j0">Class</span>
</div>
</div>
</div>
</button>
</div>
Python code:
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome('./chromedriver')
driver.get("https://quizlet.com")
# Code to log in to quizlet omitted...
# Grab and click first 'Create' button
create_button = driver.find_element_by_xpath("//button[#aria-label='Create']") # only one element has this aria-label
create_button.send_keys(Keys.RETURN) # doesn't display drop-down menu, makes me think something is wrong here but also does not throw an error
# Grab and click 'Study Set' button
div_containting_study_set_button = driver.find_element_by_class_name('s1ovpdog') # only one element has this class
study_set_button = div_containting_study_set_button.find_elements_by_class_name('UILink')[0] # returns 3 buttons, only need first one
study_set_button.send_keys(Keys.RETURN) # Error,ElementNotInteractableException
When I run this, it throws
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.ElementNotInteractableException: Message: element not interac
table
Though study_set_button should definitely be referencing a button element, I believe? Thanks for any help with this.
EDIT:
Upon searching, I found is_displayed() and ran
create_button.is_displayed() # True
study_set_button.is_displayed() #False
this returns True and False, respectively. Think my problem lies somewhere there.
This is what I found to work. It was not easy to find a unique locator for "Study set" inside the dropdown. There are two "Study set" elements on the page and the first one in the DOM is not visible. I added the waits just to be safe since you are clicking and the dropdown has to load. You may not need the waits but it won't hurt to have them (it won't slow anything down) just in case.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button[aria-label='Create']"))).click()
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div.UIOverlayTrigger-content svg[aria-label='sets'] + span"))).click()
Related
I am running into an issue where I am unable to select a button in the modal. The modal only appears when running my selenium script and does not appear when manually traversing the webpage. Interestingly enough the modal does consistently appear when my script runs try/except as shown below, but the program advances to the next step and is not able to get the element to click. Trying to get the element after this try:except also fails. The modal does not show up otherwise.
try:
element=driver.find_element_by_xpath('//div[contains(#href,"appointment")]')
element.click()
print("Looking for the modal button")
except NoSuchElementException:
print("*****Did not find the modal button")
I have tried the following with no success:
WebDriverWait(driver, 300).until(EC.element_to_be_clickable((By.XPATH, "//button[#class='btn btn-default btn-ok'][#data-dismiss='modal']"))).click() This simply times out.
Different methods to get the element:
element = driver.find_element_by_class_name("btn.btn-default.bt-ok")
and
element=driver.find_element_by_xpath('//div[contains(#href,"appointment")]')
In both cases the element is not found.
Tried running with cookies, and the result is the same.
Tried various sleep timers, but the modal does not appear during this sleep period
Here is the HTML Snippet of this section. Not sure if <div class =schedule-modal is a hint that there is some logic behind how/when the modal shows up?
<div class="schedule-modal modal fade in" tabindex="-1" role="dialog" aria-hidden="false" style="display: block;">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<p><strong> Text
</p>
</div>
<div class="modal-footer">
<a class="btn btn-default btn-ok" href="/appointment">Start Appointment</a>
</div>
</div>
</div>
</div>
Thanks in advance for your help!
Update
Looks like the above code exists HTML code exists on the source page before the modal pops up which is why I was not able to access the element. My next question is what is this schedule-modal function doing:
function showNotice(dom) {
var $scheduleModal = $(".schedule-modal");
if ($scheduleModal.length) {
var link = $(dom).attr('href');
$scheduleModal.find('.btn-ok').attr('href', link);
$scheduleModal.modal('toggle');
return false;
}
it looks like you are trying to find a div element with an href attribute containing appointment. you'll want to select the a element, which is the element that actually contains the reference you are trying to select. you have two options:
driver.find_element_by_xpath('//a[contains(#href,"appointment")]')
or
driver.find_element_by_xpath('//div/a[contains(#href,"appointment")]')
both will achieve the same results. but you may want to refine the selection just in case there are multiple a tags that have an href attribute contianing appointment
If you are unsure if the modal element would appear or not you can wrap up the click to the modal button within a try-catch{} block inducing WebDriverWait for the element_to_be_clickable() and catching TimeoutException and you can use the following Locator Strategy:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
try:
WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//*[contains(., 'login or register')]"))).click()
print("Modal button clicked")
except TimeoutException:
print("Modal button was not found")
So I'm trying to write a test for a webpage which has some elements within an iframe. I've been able to successfully run the test using webdriver.Firefox() without any problems but if I switch it over to webdriver.Chrome() I get a timeout exception on the following lines of code:
self.driver.switch_to.frame(0)
self.activity_status = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#overview > div.details.w-66 > div > div.duration-and-status > span.status.stat_incomplete#')))
It'd be great to get a solution to this as I'm all out of ideas.
Thanks for your help.
edit, partial html for the page:
<iframe id="iframe_course_details" allowfullscreen="" src="../Course/Details.aspx?HidePageNav=true&IsInIframe=true"></iframe>
Close
Edit (Inactive)
Edit
<span id="ctl00_cph_main_content_area_ucCourseDetails_spn_favourite" class="favourite button tooltipstered" style="display: none;">Favourite</span>
<span id="ctl00_cph_main_content_area_ucCourseDetails_spn_basket_dull" class="add-to-basket button delete tooltipstered" style="display: none;">Enrolled (Remove From Enrolments)</span>
<span id="ctl00_cph_main_content_area_ucCourseDetails_spn_basket" class="add-to-basket button tooltipstered">Add to Enrolments</span>
<span id="ctl00_cph_main_content_area_ucCourseDetails_spn_print" class="print button tooltipstered">Print</span>
</div>
<section id="overview" style="opacity: 1;">
<div id="fullname" class="fullname w-100" style="display: none;">
</div>
<div class="image w-33" style="cursor: pointer;">
<div style="background-image:url(/App_Themes/MainTheme-responsive/Images/Course/webcast.jpg);"></div></div>
<div class="details w-66">
<div class="inner">
<h2>testing activity</h2>
<div class="star-rating-num-ratings">
<div class="star-rating">
<span></span><span></span><span></span><span></span><span></span>
</div>
<span class="num-of-ratings">0 Ratings</span>
</div>
<div class="duration-and-status">
<span class="duration">
<label>
Duration:
</label>
<span>0</span>
</span>
<span class="status stat_incomplete">Started</span>
</div>
Edit 2:
So we've managed to find a solution to this and its even more confusing than the original problem
WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'iframe_course_details')))
time.sleep(0)
self.activity_status = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//*[#id="overview"]/div[3]/div/div[2]/span[2]')))
I'd be really curious to hear some theories on why this works, it times out without the 'time.sleep(0).
If you reference the iframe directly rather then an integer that will work between Firefox/Chrome.
self.driver.switch_to.frame(driver.find_element_by_name("iframe"))
You can find the iframe element any way you wish e.g by css/xpath etc
As the the desired element is within an <iframe> so to invoke click() on the element you have to:
Induce WebDriverWait for the desired frame to be available and switch to it.
Code Block:
# as per your comment assuming -> there is only one frame on the page
WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.TAG_NAME,"iframe")))
self.element = self.activity_status = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#overview > div.details.w-66 > div > div.duration-and-status > span.status.stat_incomplete#')))
Note : You have to add the following imports :
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
Reference
You can find a relevant detailed discussion in:
Ways to deal with #document under iframe
I'm working on a test which, at some point, records some datas displayed in a scrollable table:
<div class='table-body'>
<div class='scroll-wrapper>
<div class='row'>
<button class='button' type='button'></button>
<div class='inner-data></div>
</div>
<div class='row'>
<button class='button' type='button'></button>
<div class='inner-data></div>
</div>
<div class='row'>
<button class='button' type='button'></button>
<div class='inner-data></div>
</div>
</div>
</div>
The total number of line present in the table is displayed on the screen, allowing me to use a while loop in order to be sure to collect all the datas.
However, as you can see on the html code, each row has a button i'm clicking for every row. And here is my issue : at some point, my method find_elements_by_css_selector(div.row) finds a WebElement which is not visible on the window, and tries to click on its <button>.
Consequently, i get the following error:
ElementNotInteractableException: Message: Element <button class="btn" type="button"> could not be scrolled into view
I tried using is_displayed() method and is_enabled() to check if the element is visible on the screen, unfortunately they are always returning True.
Do you guys have any solutions ?
This error message...
ElementNotInteractableException: Message: Element <button class="btn type="button"> could not be scrolled into view
...implies that the desired element could not be scrolled into view and is not interactable.
If you observe the relevant HTML the <button> tags are:
<button class='button' type='button'></button>
which are within the parent <div> tag as follows:
<div class='row'>
You have targetted the <div> tag with your Locator Strategy:
find_elements_by_css_selector(div.row)
But the error ElementNotInteractableException is returned for some other element:
<button class="btn type="button">
observe the ^^^class^^^ attribute value
Which is of coarse not your desired element. Hence you see the error.
Solution
As a solution to target the <button> tags you need to dig deeper one step further inducing induce WebDriverWait for the visibility_of_all_elements_located() and click each of the elements and you can use either of the following Locator Strategies:
Using CSS_SELECTOR:
for element in WebDriverWait(driver, 20).until(EC.visibility_of_all_elements_located((By.XPATH, "div.table-body > div.scroll-wrapper div.row > button.button[type='button']"))):
WebDriverWait(driver, 20).until(EC.visibility_of(element)).click()
Using XPATH:
for element in WebDriverWait(driver, 30).until(EC.visibility_of_all_elements_located((By.XPATH, "//div[#class='table-body']/div[#class='scroll-wrapper']//div[#class='row']/button[#class='button' and #type='button']"))):
WebDriverWait(driver, 20).until(EC.visibility_of(element)).click()
Note : You have to add the following imports:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
Thanks for your answer #DebanjanB. Your solution works well:
try:
WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS-SELECTOR, "button"))):
element.click()
except TimeoutException:
# The button is not clickable, I scroll down
However, i know that the button is not clickable (and not visible) only when it's in the DOM but not visible on my window. And even if your solution works, it's quite time consuming, considering that i've a timeout after 20s. I know that I can decrease the time parameter in the WebDriverWait(driver, time) solution, but I found another solution:
try:
self.driver.find_element_by_css_selector('button').click()
except ElementNotInteractableException:
# The button is not clickable, I scroll down
Anyway, thanks for you help, and I hope this trick can help others ;)
I have the following Selenium Test for Python/Django application:
class EmailRecordsTest(StaticLiveServerTestCase):
def test_can_store_email_and_retrieve_it_later(self):
self.browser.get(self.live_server_url)
emailbox = self.browser.find_element_by_xpath("//form[#class='pma-subscribe-form']/input[1]")
self.assertEqual(emailbox.get_attribute("placeholder"), 'Enter your Email')
print("tested until here")
print("The placeholder: ", emailbox.get_attribute("placeholder"))
print(emailbox)
emailbox.send_keys('vio#mesmerizing.com')
First occurance of emailbox is clearly identified as seen from the print runs and assert Equal for placeholder. The last instance of emailbox.send_keys throws following error:
selenium.common.exceptions.ElementNotVisibleException: Message:
Element is not currently visible and so may not be interacted with
Cannot find why the same element become Not Visible when using with send_keys.
The Html code being tested is as below:
<!-- Start footer -->
<footer id="pma-footer">
<!-- start footer top -->
<div class="pma-footer-top">
<div class="container">
<div class="pma-footer-top-area">
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-3">
<div class="pma-footer-widget">
<h4>News letter</h4>
<p>Get latest update, news & offers</p>
<form class="pma-subscribe-form">
<input id="subscribe-email" type="email" placeholder="Enter your Email">
<button class="btn btn-danger btn-md" type="submit">Subscribe!</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- end footer top -->
Kindly help.
Actually find_element returns element which would be present on the DOM no matter it's visible or not and you can get attribute of this element as well but send_keys does an action on element and selenium does action only visible element, So you need to be sure before doing action on element that it's visible using WebDriverWait as below :-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
emailbox = wait.until(EC.visibility_of_element_located((By.ID, "subscribe-email")))
#do your all stuff before send keys
# now use send_keys
emailbox.send_keys('vio#mesmerizing.com')
Edited :- If you are still unable to interact with element try using execute_script() to set value as below :-
emailbox = wait.until(EC.presence_of_element_located((By.ID, "subscribe-email")))
#do your all stuff before send keys
# now use execute_script
driver.execute_script("arguments[0].value = 'vio#mesmerizing.com'", emailbox)
Another option which worked in this case is that you scroll to the specific element (which was at the bottom of the page)and then use send_keys it works.
emailbox = self.browser.find_element_by_xpath("//form[#class='mu-subscribe-form']/input[1]")
self.browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
emailbox.send_keys('vio#mesmerizing.com')
I get an element isn't visible error:
ElementNotVisibleException: Message: u'Element is not currently visible and so may
not be interacted with'
For every find element line when I run this code:
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://www.example.com')
browser.find_element_by_name('username').send_keys('myusername')
browser.find_element_by_name('password').send_keys('mypassword')
browser.find_element_by_class_name('welcomeLoginButton').click()
The HTML for the login section of the page looks like this:
<div class='welcomeLoginUsername'>
<div class='welcomeLoginUsernameLabel'><b>Username:</b></div>
<div class='welcomeLoginUsernameInput'><input type='text' name='username' tabindex='1'>
<br><a class='sf' href='javascript: void(0);' onclick='showUsernamePopup();'>
<b>Forgot Username?</b></a>
</div>
</div>
<div class='welcomeLoginPassword'>
<div class='welcomeLoginPasswordLabel'>
<b>Password:</b>
<br><span class='sf'>(It's cAsE sEnSitIvE!)</span>
</div>
<div class='welcomeLoginPasswordInput'>
<input type='password' name='password' tabindex='2'>
<br><a class='sf' href="javascript: void(0);" onclick="showPasswordPopup();">
<b>Forgot Password?</b></a>
</div>
</div>
</div>
<input type="submit" value="" class='welcomeLoginButton' style='border: 0px;
padding: 0px; margin: 0px;) no-repeat;' onclick='document.forms["login"].submit()'>
Selenium interacts with the web browser in a similar way that the user would. So if there is an html element you're trying to interact with that is not visible then the simplest explanation is that when youre writing your selenium code you're not interacting with the web page like a normal user would.
In the end this isn't about the html of your web page its about the DOM and an element's hidden attribute. I suggest you download firebug or some other html viewer program, and then highlight the button you want to press. Use the DOM lookup for the html viewer and go through the sign in process manually. Notice what you have to do to make the element visible in order to interact with it then mimic the same steps in your selenium code.
If it is a matter of the fact that you did everything you needed to do, but selenium is interacting with the web page faster than the javascript will make the element visible then there is a wait that you have to programmed in.
Naive way:
import time
time.sleep(1) # this is done in seconds
More scalable manner:
import time
welcome_button = browser.find_element_by_class_name('welcomeLoginButton')
wait_for_element_visibility(welcome_button).click()
def wait_for_element_visibility(element):
if element.is_visible():
return element
else:
for i in range(10):
if not element.is_visible():
time.sleep(.5)
else:
return element