TL,DR:
For whatever reason, my selenium python script can't seem to "click" on the buttons I need.
Context:
Hello. My task is one many are probably familiar with: I'd like to automate the process of opening a website, logging in, and clicking on a few drop-down menu links within the website, which will ultimately lead me to a page where I can download a spreadsheet. I'm able to open the web page and log in. To proceed, I have to:
Click on the drop down menu header, and
in the drop down menu, click on the appropriate option.
Here's a snapshot of the pertinent HTML code from the website:
<td class="x-toolbar-cell" id="ext-gen45">
<table id="ext-comp-1045" class="x-btn x-btn-noicon" style="width: auto;" cellspacing="0">
<tbody class="x-btn-small x-btn-icon-small-left">
<tr>
<td class="x-btn-ml"><i> </i></td>
<td class="x-btn-mc">
<em class="x-btn-arrow" unselectable="on">
<button type="button" id="ext-gen46" class=" x-btn-text">Reports</button>
</em>
</td>
<td class="x-btn-mr"><i> </i></td>
</tr>
</tbody>
</table>
</td>
The item I need to "click" has a button tag, specifically:
<button type="button" id="ext-gen46" class=" x-btn-text">Reports</button>
To select it with selenium, I've tried the following:
reports_click_element = browser.find_element_by_id('ext-gen46').click()
and when that failed,
reports_element = browser.find_element_by_xpath("//button[contains(text(), 'Reports')]").click()
That one actually executed without an ExceptionMessage error, but I found out it was selecting other elements in the page that had "Reports" text, as opposed to the particular button I need.
When I've tried to zero in on the button I need clicked, the interpreter returned an error message indicating that the html attributes could not be found.
How can I proceed from here? (Should I be focusing on the unselectable="on" tag in the element right above the button I need clicked?)
Please let me know if I can add anything to the question. Thanks in advance.
Update:
I have switched into an iframe that I believe the menu is a part of- but I still cannot select the button. So far, here is my Python code:
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
import time
binary = FirefoxBinary('C:\Program Files (x86)\Mozilla Firefox\Firefox.exe')
browser = webdriver.Firefox(firefox_binary=binary)
browser.get("https://app.website.com")
login_entry(username, password) # this works fine; it's just a user-created function to login. Ignore.
time.sleep(10) # wait for website's markup to load
browser.switch_to.frame(browser.find_element_by_tag_name("iframe"))
time.sleep(10)
# This is the point where I'm trying to click on the "Reports" button
reports_element = browser.find_element_by_xpath("//*[contains(text(), 'Reports')]") #this refers to other elements
reports_element = browser.find_element_by_xpath("//button[contains(text(), 'Reports')][1]") #no luck here either
A couple of cases which came to mind.
More than one element exists but not all are visible
elements = browser.find_elements_by_xpath("//button[contains(text(), 'Reports')]")
for element in elements:
if element.is_displayed():
print "element is visible"
element.click()
else:
print("element is not visible")
print(element)
The element exists, it would be visible but is out of screen.
from selenium.webdriver.common.action_chains import ActionChains
elements = browser.find_elements_by_xpath("//button[contains(text(), 'Reports')]")
for element in elements:
ActionChains(driver).move_to_element(element).perform()
try:
element.click()
except:
print("couldn't click on {}".format(element))
Can you also try to record your clicks and keyboard entries with
Selenium IDE for Firefox? Then save it as a python script and post it as a comment here?
Some ideas:
Print the DOM source of the iframe and check if the html is what you expected:
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
import time
binary = FirefoxBinary('C:\Program Files (x86)\Mozilla Firefox\Firefox.exe')
browser = webdriver.Firefox(firefox_binary=binary)
browser.get("https://app.website.com")
login_entry(username, password) # this works fine; it's just a user-created function to login. Ignore.
time.sleep(10) # wait for website's markup to load
browser.switch_to.frame(browser.find_element_by_tag_name("iframe"))
time.sleep(10)
print browser.page_source
It could be, for example, that you switched to the wrong iframe, in case there is more than one in the page.
If the html is correct, then you could try to use the id of the button instead of trying to get it by text:
reports_element = browser.find_element_by_xpath("//button[#id='ext-gen46']")
If that still doesn't work, you can try to click the button with javascript:
browser.execute_script("""
(function() {
document.getElementById("ext-gen46").click();
})()
""")
You can even include jQuery by saving jQuery in a local file, then storing the contents to a variable, and running, like:
with open(JQUERY_PATH) as f:
jquery = f.read()
browser.execute_script(jquery)
Then you could use it to click the button with:
driver.execute_script("""
(function() {
jQuery(document).ready(function($) {
$('#ext-gen46').click();
}, jQuery)
})()
""")
The "page source" is only what comes with the document request. It will not show any DOM elements created via javascript after the page loads. It does sound like your elements are within some sort of iframe. In the browser console, try this and see if it returns any elements:
document.querySelectorAll('iframe')
EDIT for your update:
Once again, the page source is only what is available at document load. Everything that comes afterward that is loaded dynamically can only be seen by using the browser inspector or by getting parts of the document w/ javascript. The link being basically the same html is probably because it is a link that acts with javascript and isn't meant to lead to an actual html document page. You probably need to do in your code is:
browser.switch_to.frame(browser.find_element_by_css_selector('iframe'))
reports_element = browser.find_element_by_link_text('Reports')
reports_element.click()
I would suggest the following things to try:
Switch to the correct frame.
There may be a chance of no frame or one frame or more than one nested frames, where your element can be as a child of another. Therefore, you must switch to the right frame before you find the element. My detailed answer is here.
Use a more restricted XPATH. For example:
report_element =browser.find_element_by_xpath("//td/em/button[text()='Reports']")
You should check whether there is only one iframe on current page. You can use
len(browser.find_elements_by_tag_name('iframe'))
If output is greater than 1 and you use browser.switch_to.frame(browser.find_element_by_tag_name("iframe")), it means that you're trying to switch to first iframe found by webdriver, but you actually might need to switch to another iframe
Good solution is to find required iframe in button's ancestors with F12 or right-click on button + inspect element and use specific attributes of iframe in XPath that will match exact iframe, e.g.
browser.switch_to.frame(browser.find_element_by_xpath('//iframe[#class="iframe_class_name"]'))
Bad solution is to define index of required iframe by exhaustive search like:
browser.switch_to.frame(browser.find_elements_by_tag_name("iframe")[0])
reports_element = browser.find_element_by_xpath("//button[text()='Reports'][contains(#id, 'ext-gen')]")
reports_element.click()
...
browser.switch_to.frame(browser.find_elements_by_tag_name("iframe")[1])
reports_element = browser.find_element_by_xpath("//button[text()='Reports'][contains(#id, 'ext-gen')]")
reports_element.click()
....
I'm doing something similar with Siebel OneView, which puts most of its control buttons and menus inside Java screen elements. These elements prevent me from finding the HTML objects to activate with Selenium, so I have fallen back to using pyautogui:
import pyautogui
import time
#look for and click the Activites menu
try:
x,y = pyautogui.locateCenterOnScreen('acts_button.png')
try:
pyautogui.click(x, y)
except PermissionError:
#allow load - note this is AFTER the click permission error
time.sleep(7)
pass
except TypeError:
<code to deal with NOT finding the button>
This looks for a copy of a previously taken screenshot, stored in the same file location as the python script. In this section the image is called acts_button.png.
[Quick note: There are two try: except: statements in here. The inner one deals with any problems clicking the button as Windows will often throw out permission errors. The outer one is more important and tells your script what to do if it can't find the button. In my code, I try clicking on a preset x,y location with pyautogui.click(958, 169); if that fails, I ask the user for input (Failures detected by the next screen not loading).
The screenshots themselves are created by using commands like this
acts_button = pyautogui.screenshot('acts_button.png', region=(928,162,63,15))
Where the region is a tuple of the following parts of your target button, measured as pixels
left hand edge
top edge
width
height
Now all you need is a way to find the (x,y) values for when you take the screenshot. Fortunately I have code for that too:
#! python3
import pyautogui, sys, time
print('Press Ctrl-C to quit.')
try:
while True:
x, y = pyautogui.position()
positionStr = 'X: ' + str(x).rjust(4) + ' Y: ' + str(y).rjust(4)
print(positionStr, end='')
print('\b' * len(positionStr), end='', flush=True)
time.sleep(1)
except KeyboardInterrupt:
print('\n')
This will pop up a small window with the (x,y) coordinates of the mouse, updating once per second.
The routine to set up a new click event is therefore
Open the site you want to navigate
Use the mouse locator script to find the settings for the screenshot tuple
Use pyautogui.screenshot to save an image of the button
Use the main script to look for and click that image
After a couple of goes you should be able to set up a new click event in less than a minute. You can either use this method where you know Selenium will fail, or as a way of catching and managing exceptions when Selenium might fail.
Why not use python request (with session) and BeautifulSoup modules to do this kind of job (user interact on a website) ?
I want to enable duplex-mode of a printer automatically by just opening a PDF file in a browser. this code works completely fine - also setting it to duplex-mode works. the only big problem is: it is setting the page-handling to booklet-style.
var pp = this.getPrintParams();
pp.printerName = "Company Printer Name";
pp.interactive = pp.constants.interactionLevel.silent;
pp.pageHandling = pp.constants.handling.booklet;
pp.booklet.binding = pp.constants.bookletBindings.Left;
pp.booklet.duplexMode = pp.constants.bookletDuplexModes.BothSides;
this.print(pp);
you find this example a couple of times, also in the official Adobe documentation: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf
but it's always just referring to the booklet-page style.
is there a possibility to change to duplex-mode without being in booklet mode? (preferably with pdf js)
update:
I managed to set my duplexmode successfully by changing the PDF's source adding these lines (using FPDF):
/ViewerPreferences<<
/Duplex /DuplexFlipLongEdge
>>
the duplex-mode is set in the print dialog, but it is ignored when you silently print it with adobe js. it works when you manually click the "print" button in the print dialog though.
so my "bonus"-question is: how do i automatically (silently) print it with adobe js without ignoring the document's settings? maybe some delay could help here?
I have been given a JSON url which returns some data. I need to create a javascript / html5 / css app that will grub this data and return it in a list.
Up to there I'm ok with doing this.
My issue is that I then need to be able to provide a <script></script> which the user can paste in any other website to display the web app.
The script or iframe need to be able to take parameters so it can filter the returned data.
Can anyone guide me in the right direction on instructions on how to do this?
The simplest way is using global variables.
Edit: added a way to "embed" the generated content. I use jQuery in the example, but it can be easily done with any other DOM manipulation library or plain JS.
The final user add an HTML element with a specific id and a <script> tag containing the parameters. We use a div element in the example.
<div id="generated-list"></div>
<script>
// the user defines parameters
var configParameters = {param1: 'some value', param2: ['is', 'array']};
</script>
The final user should paste your code. I have no idea how is your code, so I made this up:
<script>
// The user pastes your script here
// which may be a function accepting the defined parameters
function createList(configParameters) {
// create the necessary elements and append them to the element
// with the right id
// We create an empty ul element in the example
var newList = $('<ul></ul>');
$('#generated-list').append(newList);
}
</script>
Google analytics uses a similar approach
I'm looking into PDF.js for use in a web app. So far, it's meeting all of our business requirements. However, management has requested that we have the ability to disable hyperlinks within the PDF. We don't necessarily have to get rid of the blue text and underline, but if the user clicks on the hyperlink, it shouldn't go anywhere.
I've looked carefully through what API there is and couldn't find anything for it. I also looked through the source code, but nothing jumped out at me as something I could comment out in order to disable hyperlinks. Is there any way to disable hyperlinks contained within a PDF?
After a great deal of experimentation, I found out how to do this by modifying the source. There is a block of code that begins with the following:
document.addEventListener('pagerendered', function (e) {
At the end of the function before the close bracket, add the following code:
var allowInternalLinks = true;
var page = document.getElementById('pageContainer' + pageNumber);
var hyperlinks = page.getElementsByTagName('a');
for (var i=0; i<hyperlinks.length; i++){
if (!allowInternalLinks || hyperlinks[i].className != 'internalLink'){
hyperlinks[i].onclick = function(e) {
e.preventDefault();
}
}
};
What this does is take the rendered page, iterate through all of the hyperlinks on that page, and disable them. I have also added a boolean variable that allows you to optionally allow or disallow internal links (i.e. links that take the user to another location within the document).
There is a HTML textarea. I'm able to catch that event when a local file is dragged and dropped onto the textarea. But how to obtain the name of the dropped file? (To be modified and inserted into the textarea finally.)
The following expressions returns None in that case:
event.dataTransfer.files
event.dataTransfer.getData('text/plain')
I made a short example for Firefox 3 that is my target platform currently.
<script>
function init() {
document.getElementById('x').addEventListener('drop', onDrop, true)
}
function onDrop(event) {
var data = event.dataTransfer.getData('text/plain')
event.preventDefault()
alert('files: ' + event.dataTransfer.files + ' && data: ' + data + '.')
}
</script>
<body onload='init()'>
<textarea cols=70 rows=20 id='x'></textarea>
This is a bit late - but I think what you are looking for is this:
event.dataTransfer.files[0].name
You can also get the following properties:
event.dataTransfer.files[0].size
event.dataTransfer.files[0].type
And you can loop thru these files with the following:
var listOfNames='';
for(var i=0,tot=event.dataTransfer.files.length; i<tot; i++){
listOfNames+=event.dataTransfer.files[i].name + '\r\n';
}
Btw - if you are using jQuery then the dataTransfer object can be found here:
event.originalEvent.dataTransfer.files[0].name
Don't know if it's still relevant, but I faced the same problem. Here's how I solved it:
Create a normal upload form with a single input field (type="file")
Add this HTML attribute to the input field:
dropzone="copy file:image/png file:image/jpg file:image/jpeg"
Set JQuery listener or whatever to catch the "drop"-event on the input field
When you drag & drop a local image on the input field, the "value" attribute is filled automatically and you can process it like any other HTML form.
I also wrapped the form into another HTML element (div), set the size of the div and set overflow:hidden via CSS - this way, you can get rid of the "browse" button. It's not nice, but it works. I also used the AjaxForm plugin to upload the image in the background - works very nice.
as far as I know, you need to obtain an instance of nsIFile in order to get the file path (the File class does not offer this feature).
This MDC page explains how to do this: https://developer.mozilla.org/En/DragDrop/Recommended_Drag_Types#file.
Note that although not listed in the previous link, obtaining an nsIFile instance requires privileges escalation (cf. my answer to Can I drag files from the desktop to a drop area in Firefox 3.5 and initiate an upload? show how to do this).
im doing it by detecting mouseover and mousedown over the "Drop" zone
Alemjerus is correct, you don't have access to what you're looking for.
The behavior you mentioned in reply to his comment is the default behavior of certain browsers. For instance, with the stackoverflow textarea for this entry, if I use Safari and drag a file into it, it places the file's path into the textarea. With firefox 3.5, on the other hand, it attempts to open the file with the browser.
Basically, the "drag and drop" functionality you're attempting to implement is something thats handled by the browser and OS on the client machine -- you can't use Javascript for this purpose.
You can not do it with Javascript because of security reasons. Javascript VM has no direct access to OS file system. You can only drag and drop text.