The Story:
Here on StackOverflow, I've seen users reporting that they cannot click an element via selenium WebDriver "click" command and can work around it with a JavaScript click by executing a script.
Example in Python:
element = driver.find_element_by_id("myid")
driver.execute_script("arguments[0].click();", element)
Example in WebDriverJS/Protractor:
var elm = $("#myid");
browser.executeScript("arguments[0].click();", elm.getWebElement());
The Question:
Why is clicking "via JavaScript" works when a regular WebDriver click does not? When exactly is this happening and what is the downside of this workaround (if any)?
I personally used this workaround without fully understanding why I have to do it and what problems it can lead to.
Contrarily to what the currently accepted answer suggests, there's nothing specific to PhantomJS when it comes to the difference between having WebDriver do a click and doing it in JavaScript.
The Difference
The essential difference between the two methods is common to all browsers and can be explained pretty simply:
WebDriver: When WebDriver does the click, it attempts as best as it can to simulate what happens when a real user uses the browser. Suppose you have an element A which is a button that says "Click me" and an element B which is a div element which is transparent but has its dimensions and zIndex set so that it completely covers A. Then you tell WebDriver to click A. WebDriver will simulate the click so that B receives the click first. Why? Because B covers A, and if a user were to try to click on A, then B would get the event first. Whether or not A would eventually get the click event depends on how B handles the event. At any rate, the behavior with WebDriver in this case is the same as when a real user tries to click on A.
JavaScript: Now, suppose you use JavaScript to do A.click(). This method of clicking does not reproduce what really happens when the user tries to click A. JavaScript sends the click event directly to A, and B will not get any event.
Why a JavaScript Click Works When a WebDriver Click Does Not?
As I mentioned above WebDriver will try to simulate as best it can what happens when a real user is using a browser. The fact of the matter is that the DOM can contain elements that a user cannot interact with, and WebDriver won't allow you to click on these element. Besides the overlapping case I mentioned, this also entails that invisible elements cannot be clicked. A common case I see in Stack Overflow questions is someone who is trying to interact with a GUI element that already exists in the DOM but becomes visible only when some other element has been manipulated. This sometimes happens with dropdown menus: you have to first click on the button the brings up the dropdown before a menu item can be selected. If someone tries to click the menu item before the menu is visible, WebDriver will balk and say that the element cannot be manipulated. If the person then tries to do it with JavaScript, it will work because the event is delivered directly to the element, irrespective of visibility.
When Should You Use JavaScript for Clicking?
If you are using Selenium for testing an application, my answer to this question is "almost never". By and large, your Selenium test should reproduce what a user would do with the browser. Taking the example of the drop down menu: a test should click on the button that brings up the drop down first, and then click on the menu item. If there is a problem with the GUI because the button is invisible, or the button fails to show the menu items, or something similar, then your test will fail and you'll have detected the bug. If you use JavaScript to click around, you won't be able to detect these bugs through automated testing.
I say "almost never" because there may be exceptions where it makes sense to use JavaScript. They should be very rare, though.
If you are using Selenium for scraping sites, then it is not as critical to attempt to reproduce user behavior. So using JavaScript to bypass the GUI is less of an issue.
The click executed by the driver tries to simulate the behavior of a real user as close as possible while the JavaScript HTMLElement.click() performs the default action for the click event, even if the element is not interactable.
The differences are:
The driver ensures that the element is visible by scrolling it into the view and checks that the element is interactable.
The driver will raise an error:
when the element on top at the coordinates of the click is not the targeted element or a descendant
when the element doesn't have a positive size or if it is fully transparent
when the element is a disabled input or button (attribute/property disabled is true)
when the element has the mouse pointer disabled (CSS pointer-events is none)
A JavaScript HTMLElement.click() will always perform the default action or will at best silently fail if the element is a disabled.
The driver is expected to bring the element into focus if it is focusable.
A JavaScript HTMLElement.click() won't.
The driver is expected to emit all the events (mousemove, mousedown, mouseup, click, ...) just like like a real user.
A JavaScript HTMLElement.click() emits only the click event.
The page might rely on these extra events and might behave differently if they are not emitted.
These are the events emitted by the driver for a click with Chrome:
mouseover {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
mousemove {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
mousedown {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
mouseup {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
click {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
And this is the event emitted with a JavaScript injection:
click {target:#topic, clientX:0, clientY:0, isTrusted:false, ... }
The event emitted by a JavaScript .click() is not trusted and the default action may not be invoked:
https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted
https://googlechrome.github.io/samples/event-istrusted/index.html
Note that some of the drivers are still generating untrusted events. This is the case with PhantomJS as of version 2.1.
The event emitted by a JavaScript .click() doesn't have the coordinates of the click.
The properties clientX, clientY, screenX, screenY, layerX, layerY are set to 0. The page might rely on them and might behave differently.
It may be ok to use a JavaScript .click() to scrap some data, but it is not in a testing context. It defeats the purpose of the test since it doesn't simulate the behavior of a user. So, if the click from the driver fails, then a real user will most likely also fail to perform the same click in the same conditions.
What makes the driver fail to click an element when we expect it to succeed?
The targeted element is not yet visible/interactable due to a delay or a transition effect.
Some examples :
https://developer.mozilla.org/fr/docs/Web (dropdown navigation menu)
http://materializecss.com/side-nav.html (dropdown side bar)
Workarrounds:
Add a waiter to wait for the visibility, a minimum size or a steady position :
// wait visible
browser.wait(ExpectedConditions.visibilityOf(elem), 5000);
// wait visible and not disabled
browser.wait(ExpectedConditions.elementToBeClickable(elem), 5000);
// wait for minimum width
browser.wait(function minimumWidth() {
return elem.getSize().then(size => size.width > 50);
}, 5000);
Retry to click until it succeeds :
browser.wait(function clickSuccessful() {
return elem.click().then(() => true, (ex) => false);
}, 5000);
Add a delay matching the duration of the animation/transition :
browser.sleep(250);
The targeted element ends-up covered by a floating element once scrolled into the view:
The driver automatically scrolls the element into the view to make it visible. If the page contains a floating/sticky element (menu, ads, footer, notification, cookie policy..), the element may end-up covered and will no longer be visible/interactable.
Example: https://twitter.com/?lang=en
Workarounds:
Set the size of the window to a larger one to avoid the scrolling or the floating element.
Mover over the element with a negative Y offset and then click it:
browser.actions()
.mouseMove(elem, {x: 0, y: -250})
.click()
.perform();
Scroll the element to the center of the window before the click:
browser.executeScript(function scrollCenter(elem) {
var win = elem.ownerDocument.defaultView || window,
box = elem.getBoundingClientRect(),
dy = box.top - (win.innerHeight - box.height) / 2;
win.scrollTo(win.pageXOffset, win.pageYOffset + dy);
}, element);
element.click();
Hide the floating element if it can't be avoided:
browser.executeScript(function scrollCenter(elem) {
elem.style.display = 'none';
}, element);
NOTE: let's call 'click' is end-user click. 'js click' is click via JS
Why is clicking "via JavaScript" works when a regular WebDriver click does not?
There are 2 cases for this to happen:
I. If you are using PhamtomJS
Then this is the most common known behavior of PhantomJS . Some elements are sometimes not clickable, for example <div>. This is because PhantomJS was original made for simulating the engine of browsers (like initial HTML + CSS -> computing CSS -> rendering). But it does not mean to be interacted with as an end user's way (viewing, clicking, dragging). Therefore PhamtomJS is only partially supported with end-users interaction.
WHY DOES JS CLICK WORK? As for either click, they are all mean click. It is like a gun with 1 barrel and 2 triggers. One from the viewport, one from JS. Since PhamtomJS great in simulating browser's engine, a JS click should work perfectly.
II. The event handler of "click" got to bind in the bad period of time.
For example, we got a <div>
-> We do some calculation
-> then we bind event of click to the <div>.
-> Plus with some bad coding of angular (e.g. not handling scope's cycle properly)
We may end up with the same result. Click won't work, because WebdriverJS trying to click on the element when it has no click event handler.
WHY DOES JS CLICK WORK? Js click is like injecting js directly into the browser. Possible with 2 ways,
Fist is through devtools console (yes, WebdriverJS does communicate with devtools' console).
Second is inject a <script> tag directly into HTML.
For each browser, the behavior will be different. But regardless, these methods are more complicating than clicking on the button. Click is using what already there (end-users click), js click is going through backdoor.
And for js click will appear to be an asynchronous task. This is related a with a kinda complex topic of 'browser asynchronous task and CPU task scheduling' (read it a while back can't find the article again). For short this will mostly result as js click will need to wait for a cycle of task scheduling of CPU and it will be ran a bit slower after the binding of the click event.
(You could know this case when you found the element sometimes clickable, sometimes not.
)
When exactly is this happening and what is the downside of this
workaround (if any)?
=> As mention above, both mean for one purpose, but about using which entrance:
Click: is using what providing by default of browser.
JS click: is going through backdoor.
=> For performance, it is hard to say because it relies on browsers. But generally:
Click: doesn't mean faster but only signed higher position in schedule list of CPU execution task.
JS click: doesn't mean slower but only it signed into the last position of schedule list of CPU task.
=> Downsides:
Click: doesn't seem to have any downside except you are using PhamtomJS.
JS click: very bad for health. You may accidentally click on something that doesn't there on the view. When you use this, make sure the element is there and available to view and click as the point of view of end-user.
P.S. if you are looking for a solution.
Using PhantomJS? I will suggest using Chrome headless instead. Yes, you can set up Chrome headless on Ubuntu. Thing runs just like Chrome but it only does not have a view and less buggy like PhantomJS.
Not using PhamtomJS but still having problems? I will suggest using ExpectedCondition of Protractor with browser.wait() (check this for more information)
(I want to make it short, but ended up badly. Anything related with theory is complicated to explain...)
Thank you for the good explanation, I was hitting the same issue and your explanation helped to resolve my issue.
button = driver.wait.until(EC.presence_of_element_located(
(By.XPATH, "//div[#id='pagination-element']/nav[1]/span[3]/button[1]/span[1]/i[1]")
))
driver.execute_script("arguments[0].click();", button)
if (theElement.Enabled)
{
if (!theElement.Selected)
{
var driver = (IJavaScriptExecutor)Driver;
driver.ExecuteScript("arguments[0].click();", theElement); //ok
//theElement.Click();//action performed on theElement, then pops exception
}
}
I do not agree we will "almost never" use JS to simulate the click action.
Above theElement.Click(), we'll check the Radio button but then Exception pops as above image.
Actually, this is no page load action after the Click, the click is just to select the Radio button, and I don't know why the WebDriver Click() will cause this exception, can anyone explain why this exception occurred.
I use the Webdriver 3.141.59 and IE 11 and selenium-server-standalone-3.141.59.jar for a remote test.
I have a canvas app where I handle window's keyup and other events. This works fine. However, now I've created a popup div with a textarea in it, and I don't want my keyup handling to be active when the textarea is focused (or when the popup is visible).
I could set a bool isPopupVisible and check for that in my keyup handling, but it strikes me that much more elegant would be to just use the standard focus management of HTML. So I tried handling the canvas's keyup event rather than the window's, but now the problem is that the canvas never receives focus (not even if I click on it) so it doesn't receive any key events. Apparently most HTML elements can't receive focus.
What would be a good way to resolve this?
Edit: It now occurs to me that what I want is in effect a modal dialog box, which HTML doesn't natively support. To support this modality, it's normal to implement it manually with a bool as I initially planned. Standard HTML focus doesn't provide for that, even if I could get the canvas to receive focus. Because the user can switch focus back to the canvas by clicking on it even when the popup is still visible (undesirable).
So I guess I withdraw my question.
i'm having trouble in chrome opening the popup for the file upload of a file input type.
As you can see here: http://jsfiddle.net/cavax/K99gg/3/, clicking on an elements can trigger a click on the file input, but for example hovering a element wont trigger a click on the input.
$('#test').on('click', function(){
$('#upload').trigger('click');
});
$('#test').on('mouseenter', function(){
$('#upload').trigger('click');
});
In the real life i'm having this trouble because in a web app i'm loading throw ajax a content witch has inside an input file.
At the end of the load the popup file must open, but there is no way to get this works on Chrome (workign on FF).
The problem i guess is that the action is not generated by a mouse click (hover, timeout etc) so chrome wont trigger the fileupload.
I've also tryed this: http://jsfiddle.net/cavax/K99gg/7/, so click on the element, wait then trigger the click but nothing, because there is the "settimeout" in the middle of the click and the trigger :(
$('#test').on('click', function(){
setTimeout(function(){
$('#upload').trigger('click');
}, 3000);
});
if you remove the delay: http://jsfiddle.net/cavax/K99gg/8/ it works
Any idea to get this work?
If I remember correctly, mouseenter and mouseleave are specific to IE, not standard events. Maybe they were extended, but don't think they became a standard. So the event itself may generate you some problems.
To resolve this you can use a lib (like jQuery for example), that treats the browser differences (or you can check the code there and take what you need).
Second way... try mouseover... it worked better (again... didn't work with them for a while so things may have happened, but this is how I remember them to be).
There is no way to trigger click event of input file type, because of a security reason.
You may try a hack of this by setting your button/div position to absolute and top to -100px
It means positioning it outside the viewport by setting above style make it works.
But for mouseenter and mouseover i don't think it's going to work!
Edit:
Moved input outside the viewport and target click event
Example on click
Side note: Right now click occurs 2 times as you have written
$('#upload').trigger('click').click();
You just need
$('#upload').trigger('click'); // $('#upload').click()
unless you want it to fire more than single time.
I'm trying to detect when a user has finished resizing a text selection on iOS, using Javascript.
While I'm aware of the selectionchange even , if the user uses the native controls I'll get a lot of scroll events, and a few selectionchange.
Now, I have no idea when the user is done, however. If he spends 5 minutes with his finger down, without moving the controls nor changing the selection, I don't get a "mouseup equivalent" event.
EDIT: ...my situation is basically this one: https://developer.apple.com/library/ios/DOCUMENTATION/AppleApplications/Reference/SafariWebContent/Art/events_information_bubble.jpg
Any idea how this could be achieved?
Thanks!
I've found that selectionchange event fires multiple times even on a single word selection (with a tap or a long press), and of course it fires when you drag the selection handles ...
Here is a workaround i have created (not a perfect one) to get only the text selection end event.
you can see it here: End of text selection event?
or in a small post on my blog: http://www.dimshik.com/end-of-text-selection-event-on-ios-workaround/
There is a lot of material/posts on ghost clicks and I'm trying to understand it better. So the way I understand it, the reason for ghost clicks is the click event being dispatched ~300ms after the touch event. jQuery Mobile suggests to not use their vclick event whenever there is any chance of changing the content underneath the finger position.
My first question would be: Does that mean ghost clicks will only fire if the element targeted by click is different from the one originally touched? So, say, I write a database entry when a button is touched – nothing else. Is there a chance of ghost clicking or not?
If this is the case, wouldn't that mean that I can prevent ghost clicks altogether if I simply use only tap events and no click events whatsoever?
My last question would be if I can simply tell the browser to not use the 300ms delay when using PhoneGap (which would instantly solve the problem), but I'd just guess that I can't do that as it's probably hard-coded into the browser.
The click event is delayed by 300 ms to detect things like double tap or fat finger mistakes.
Yes, wherever possible you should use the touch events instead.
Yes, there are many ways to enable fast clicks by doing a bit of JS. For instance:
https://developers.google.com/mobile/articles/fast_buttons
https://forum.jquery.com/topic/how-to-remove-the-300ms-delay-when-clicking-on-a-link-in-jquery-mobile
http://labs.ft.com/2011/08/fastclick-native-like-tapping-for-touch-apps/
You don't have to live with the 300ms delay.
If everything on your page that can be clicked has the appropriate vclick jQuery event handlers installed, then one easy way of stopping ghost clicks is create a touchend event handler on the body and call preventDefault from it:
$(document.body).on('touchend', null, function(e) {
e.preventDefault();
});
Note that this will disable regular clicks from touches, so any conventional links or form inputs you have will stop working unless you add vclick handlers to them.