This function receives a WebElement and uses it to locate other elements. But when I switch to an iframe and back, the WebElement seems to be invalidated. The error I get is "stale element reference: element is not attached to the page document".
def map_vid(vid):
vid_name = vid.find_element(By.XPATH, "./*[1]").text
frame = driver.find_element(By.XPATH, FRAME_XPATH)
driver.switch_to.frame(frame)
quality_button = driver.find_element(By.CLASS_NAME, 'icon-cog')
quality_button.click()
driver.switch_to.default_content()
close_button = vid.find_element(By.XPATH, CLOSE_BUTTON_XPATH)
close_button.click()
Relocating the WebElement is not an option, since it's passed as an argument. Any help would be greatly appriciated :)
You need to pass a By locator as a parameter instead of passing a WebElement parameter.
You can not make this your code working with passing a WebElement parameter since Selenium WebElement is a reference to physical web element on the page.
As you can see, switching to/from an iframe causes previously found WebElements to become Stale.
Passing a By locator will allow you to find the passed parent element inside the method each time you will need to use it.
Something like the following:
def map_vid(vid_by):
vid = driver.find_element(vid_by)
vid_name = vid.find_element(By.XPATH, "./*[1]").text
frame = driver.find_element(By.XPATH, FRAME_XPATH)
driver.switch_to.frame(frame)
quality_button = driver.find_element(By.CLASS_NAME, 'icon-cog')
quality_button.click()
driver.switch_to.default_content()
vid = driver.find_element(vid_by)
close_button = vid.find_element(By.XPATH, CLOSE_BUTTON_XPATH)
close_button.click()
Related
I am trying to click multiple elements at the same time without any delay in between.
For example, instead of 1 then 2, it should be 1 and 2.
this is the 1st element that I want to click:
WebDriverWait(driver, 1).until(EC.element_to_be_clickable(
(By.XPATH,
"//div[contains(#class, 'item-button')]//div[contains(#class, 'button-game')]"))).click()
this is the 2nd elements that I want to click:
(run the first line then second line)
WebDriverWait(driver, 1).until(
EC.frame_to_be_available_and_switch_to_it((By.XPATH, "/html/body/div[4]/div[4]/iframe")))
WebDriverWait(driver, 1.4).until(EC.element_to_be_clickable(
(By.XPATH, "//*[#id='rc-imageselect']/div[3]/div[2]/div[1]/div[1]/div[4]"))).click()
Basically, Click 1st element and 2nd elements' first line then second line.
I have tried this, but did not work:
from threading import Thread
def func1():
WebDriverWait(driver, 1).until(EC.element_to_be_clickable(
(By.XPATH,
"//div[contains(#class, 'item-button')]//div[contains(#class, 'button-game')]"))).click()
def func2():
WebDriverWait(driver, 1).until(
EC.frame_to_be_available_and_switch_to_it((By.XPATH, "/html/body/div[4]/div[4]/iframe")))
WebDriverWait(driver, 1.4).until(EC.element_to_be_clickable(
(By.XPATH, "//*[#id='rc-imageselect']/div[3]/div[2]/div[1]/div[1]/div[4]"))).click()
if __name__ == '__main__':
Thread(target = func1).start()
Thread(target = func2).start()
Use case: I am trying to automate a website and I need to be fast. Sometimes, element1 is not showing, the website shows element2 instead and vice versa. If I did not check element1 and element2 at the same time, I will be late. The code above starts function1 before function2 and not at the same time. Thank you
You can make it simultaneous if you use jQuery:
click_script = """jQuery('%s').click();""" % css_selector
driver.execute_script(click_script)
That's going to click all elements that match that selector at the same time, assuming you can find a common selector between all the elements that you want to click. You may also need to escape quotes before feeding that selector into the execute_script. And you may need to load jQuery if it wasn't already loaded. If there's no common selector between the elements that you want to click simultaneously, then you can use javascript to set a common attribute between them.
You can also try it with JS execution, which might be fast enough:
script = (
"""var simulateClick = function (elem) {
var evt = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
var canceled = !elem.dispatchEvent(evt);
};
var $elements = document.querySelectorAll('%s');
var index = 0, length = $elements.length;
for(; index < length; index++){
simulateClick($elements[index]);}"""
% css_selector
)
driver.execute_script(script)
As before, you'll need to escape quotes and special characters first.
Using import re; re.escape(STRING) can be used for that.
All of this will be made easier if you use the Selenium Python framework: SeleniumBase, which has build-in methods for simultaneous clicking:
self.js_click_all(selector)
self.jquery_click_all(selector)
And each of those above will automatically escape quotes of selectors before running driver.execute_script(SCRIPT), and will also load jQuery if it wasn't already loaded on the current page. If the elements above didn't already have a common selector, you can use self.set_attribute(selector, attribute, value) in order to create a common one before running one of the simultaneous click methods.
Simultaneous clicking is possible with selenium ActionChains class.
Reference:
https://github.com/SeleniumHQ/selenium/blob/64447d4b03f6986337d1ca8d8b6476653570bcc1/py/selenium/webdriver/common/actions/pointer_input.py#L24
And here is code example, in which 2 clicks will be performed on 2 different elements at the same time:
from selenium.webdriver.common.actions.mouse_button import MouseButton
from selenium.webdriver.common.action_chains import ActionChains
b1 = driver.find_element(By.ID, 'SomeButtonId')
b2 = driver.find_element(By.ID, 'btnHintId')
location1 = b1.rect
location2 = b2.rect
actions = ActionChains(driver)
actions.w3c_actions.devices = []
new_input = actions.w3c_actions.add_pointer_input('touch', 'finger1')
new_input.create_pointer_move(x=location1['x'] + 1, y=location1['y'] + 2)
new_input.create_pointer_down(MouseButton.LEFT)
new_input.create_pointer_up(MouseButton.LEFT)
new_input2 = actions.w3c_actions.add_pointer_input('touch', 'finger2')
new_input2.create_pointer_move(x=location2['x'] + 1, y=location2['y'] + 2)
new_input2.create_pointer_down(MouseButton.LEFT)
new_input2.create_pointer_up(MouseButton.LEFT)
actions.perform()
You can use ActionChains to perform multiple actions almost immediately.
actions = ActionChains(driver)
actions.move_to_element(element1).click()
actions.move_to_element(element2).click()
actions.perform()
Depending on the use case, you could also use click_and_hold which is also available using ActionChains.
I am trying to get a book's description text from its amazon webpage. I get the book's image and title and price fine by using driver.find_element_by_id, but when it comes to the description which is in a div with id="iframeContent", it doesn't work. Why? I have also tried WebDriverWait but no luck.
I use the following code:
def get_product_description(self, url):
"""Returns the product description of the Amazon URL."""
self.driver.get(url)
try:
product_desc = self.driver.find_element_by_id("iframeContent")
except:
pass
if product_desc is None:
product_desc = "Not available"
return product_desc
Since that element is inside the iframe you have to switch to that iframe in order to access elements inside the iframe.
So first you have to locate the iframe
iframe = driver.find_element_by_id("bookDesc_iframe")
then switch to it with
driver.switch_to.frame(iframe)
Now you can access the element located by the div#iframeContent css_selector
And finally you should get out from the iframe to the default content with
driver.switch_to.default_content()
Credits to the author
you need to use text method from selenium.
try:
product_desc = self.driver.find_element_by_id("iframeContent").text
print(product_desc)
except:
pass
Update 1:
There's a iframe involved so you need to change the focus of webdriver to iframe first
iframe = self.driver.find_element_by_id("bookDesc_iframe")
self.driver.switch_to.frame(iframe)
product_desc = self.driver.find_element_by_id("iframeContent").text
print(product_desc)
Now you can access every element which is inside bookDesc_iframe iframe, but the elements which are outside this frame you need to set the webdriver focus to default first, something like this below :
self.driver.switch_to.default_content()
I need to find and store the location of some elements so the bot can click on those elements even the if page changes. I have read online that for a single element storing the location of that element in a variable can help however I could not find a way to store locations of multiple elements in python. Here is my code
comment_button = driver.find_elements_by_css_selector("svg[aria-label='Comment']")
for element in comment_button:
comment_location = element.location
sleep(2)
for element in comment_location:
element.click()
this code gives out this error:
line 44, in <module>
element.click()
AttributeError: 'str' object has no attribute 'click'
Is there a way to do this so when the page refreshes the script can store the locations and move on to the next location to execute element.click without any errors?
I have tried implementing ActionChains into my code
comment_button = driver.find_elements_by_css_selector("svg[aria-label='Comment']")
for element in comment_button:
ac = ActionChains(driver)
element.click()
ac.move_to_element(element).move_by_offset(0, 0).click().perform()
sleep(2)
comment_button = driver.find_element_by_css_selector("svg[aria-label='Comment']")
comment_button.click()
sleep(2)
comment_box = driver.find_element_by_css_selector("textarea[aria-label='Add a comment…']")
comment_box.click()
comment_box = driver.find_element_by_css_selector("textarea[aria-label='Add a comment…']")
comment_box.send_keys("xxxx")
post_button = driver.find_element_by_xpath("//button[#type='submit']")
post_button.click()
sleep(2)
driver.back()
scroll()
However this method gives out the same error saying that the page was refreshed and the object can not be found.
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <svg class="_8-yf5 "> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed
Edited:
Assuming No. of such elements are not changing after refresh of page, you can use below code
commentbtns = driver.find_elements_by_css_selector("svg[aria-label='Comment']")
for n in range(1, len(commentbtns)+1):
Path = "(//*[name()='svg'])["+str(n)+"]"
time.sleep(2)
driver.find_element_by_xpath(Path).click()
You can use more sophisticated ways to wait for element to load properly. However for simplicity purpose i have used time.sleep.
I try find target element by xpath so that I can click on it. But can't find it when run code, although can find it by right-click option manually on chrome browser.
detail: I am using
driver.get('chrome://settings/clearBrowserData')
to get history pop-up from chrome, then wait element by selenium,
and next action I try to click it by:
driver.find_element_by_css_selector('* /deep/ #clearBrowsingDataConfirm').click()
or by:
driver.find_element_by_xpath(r'//paper-button[#id="clearBrowsingDataConfirm"]').click()
both does not work
Could you tell solution by xpath if possible because I am more familiar with it. Or any other way to clear history on chrome, thank
Looking into Chrome Settings page source it looks like the button, you're looking for is hidden in the ShadowDOM
So you need to iterate down several levels of ShadowRoot
So the algorithm looks like:
Locate parent WebElement
Locate its shadow-root and cast it to the WebElement
Use WebElement.find_element() function to locate the next WebElement which is the parent for the ShadowRoot
Repeat steps 1-3 until you're in the same context with the element you want to interact with
Example code:
from selenium import webdriver
def expand_root_element(element):
shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
return shadow_root
driver = webdriver.Chrome("c:\\apps\\webdriver\\chromedriver.exe")
driver.maximize_window()
driver.get("chrome://settings/clearBrowserData")
settingsUi = driver.find_element_by_tag_name("settings-ui")
settingsUiShadowRoot = expand_root_element(settingsUi)
settingsMain = settingsUiShadowRoot.find_element_by_tag_name("settings-main")
settingsShadowRoot = expand_root_element(settingsMain)
settingsBasicPage = settingsShadowRoot.find_element_by_tag_name("settings-basic-page")
settingsBasicPageShadowroot = expand_root_element(settingsBasicPage)
settingsPrivacyPage = settingsBasicPageShadowroot.find_element_by_tag_name("settings-privacy-page")
settingsPrivacyShadowRoot = expand_root_element(settingsPrivacyPage)
settingsClearBrowsingDataDialog = settingsPrivacyShadowRoot.find_element_by_tag_name(
"settings-clear-browsing-data-dialog")
settingsClearBrowsingDataDialogShadowRoot = expand_root_element(settingsClearBrowsingDataDialog)
settingsClearBrowsingDataDialogShadowRoot.find_element_by_id("clearBrowsingDataConfirm").click()
I got it to work by doing this:
driver.ExecuteScript("return document.querySelector('body > settings-ui').shadowRoot.querySelector('#main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('#advancedPage > settings-section:nth-child(1) > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataConfirm').click();");
I want to locate the elements of a popup on some page,
the popup html is written in an iframe,
also the popup is triggered by clicking a link on the main page.
The weird thing is, if I run the whole code, I can not locate the 'target' element:
dr = webdriver.Chrome('chromedriver.exe', options=chrome_options)
modify = (By.CLASS_NAME, "modify")
ec_visible(dr, modify).click()
popup = (By.CLASS_NAME, "add-addr-iframe")
dr.switch_to.frame(ec_visible(dr, popup))
target = (By.CLASS_NAME, "cndzk-entrance-division-header-click")
ec_visible(dr, target).click()
def ec_visible(driver, locator):
return WebDriverWait(driver, 5).until(EC.visibility_of_element_located(locator))
But, if I first open the popup then locate, it works.
First:
modify = (By.CLASS_NAME, "modify")
ec_visible(dr, modify).click()
#popup = (By.CLASS_NAME, "add-addr-iframe")
#dr.switch_to.frame(ec_visible(dr, popup))
#target = (By.CLASS_NAME, "cndzk-entrance-division-header-click")
#ec_visible(dr, target).click()
Then: (works too if I manually open the popup and run this code)
#modify = (By.CLASS_NAME, "modify")
#ec_visible(dr, modify).click()
popup = (By.CLASS_NAME, "add-addr-iframe")
dr.switch_to.frame(ec_visible(dr, popup))
target = (By.CLASS_NAME, "cndzk-entrance-division-header-click")
ec_visible(dr, target).click()
Appreciate if you can point out my problem!
Here is the exception from shell:
raise TimeoutException(message, screen, stacktrace)
selenium.common.exceptions.TimeoutException: Message:
This is the html screenshot,
sometimes the content in iframe can not even be seen.
iframe not extendable
when iframe extendable
7/26 update
I am wondering whether I asked the right question which may lead you guys just focusing on my code part. Since my code works(seperately), the elements and frames approach are good.
I step back and find one detail which may help but I don't know how it matters.
Here are the two shots about ctrl-F some element in the page source:
Normal result: target found and highlighted
Weird result: target found and no highlight
I mean when the page exists 'weird result', my code does not work.
PS. The page is the order-confirmation part of an e-commerical site, but the site groups its goods into two types which led to TWO types order page.
For ifarmes you have frame_to_be_available_and_switch_to_it as the EC.
So try this:
WebDriverWait(driver, 5).until(EC.frame_to_be_available_and_switch_to_it((By.CLASS_NAME, "add-addr-iframe")))
target = (By.CLASS_NAME, "span.cndzk-entrance-division-header-click")
ec_visible(dr, target).click()