8

I am trying to emulate a user's click on a site who's code I do not control. The element I am trying to engage with a div acting as button.

<div role="button" class="c-T-S a-b a-b-B a-b-Ma oU v2" aria-disabled="false" style="-webkit-user-select: none;" tabindex="0">
    Generate
</div>

The event listeners that are associated with element (according to Chrome's inspector) are:

enter image description here

And I am simply trying to click the button using:

var button = $('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2')
button.click()

... but nothing happens. The selector is valid, as verified by:

alert($('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').length); // alerts "1"

I have tried permutations of all the event handlers

button.trigger('click');
button.mouseover().mousedown().mouseup()
button.trigger('mouseover', function() { button.trigger('mousedown', function() { button.trigger('mouseup'); });  });

... but still nothing. How can I simulate a click on this div?

In case it is not clear, I am trying to simulate a click on this div and trigger the original function, not define a new click function on the element.

UPDATE

Many of these answer do indeed click the button, but don't produce the same result as manually clicking the button. So it appears the problem is not necessarily clicking the button per se, but emulating a real click.

coneybeare
  • 33,113
  • 21
  • 131
  • 183

5 Answers5

11

I have made a FIDDLE to simulate your "non-clickable" button. The blue div there has the same eventListeners attached as you have shown in your question. Playing around ended up with following results:

1) Let's get the DOM-element first:

var button = document.getElementsByClassName('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2')[0];

2) The page doesn't include jQuery. So all eventListeners there are attached by native .addEventListener(). If you use jQuery to trigger events, it triggers only the events that are attached by jQuery, but not the native attached events. That means:

$(button).trigger('mousedown'); // this doesn't work
button.dispatchEvent(new Event('mousedown')); // this should work

3) As Scimonster pointed out, there is no click-eventListener attached. That means:

$(button).trigger('click'); // doesn't work anyway, see 2)
// next works, but has no visible result on the page,
// because there is no click-handler to do anything:
button.dispatchEvent(new Event('click'));

4) The click-event fires when the mousebutton goes up. When the mouseup-event is used instead it looks like a click. In the fiddle the mouseup makes the red div visible. You may try to trigger the mouseup-event by adding this line to the code:

button.dispatchEvent(new Event('mouseup')); // "works", but has no visible result

The mouseup-handler is "hidden" by the mouseover- and mouseout-events, the first attaches it and the latter removes it. That way mouseup has only a result when mouse is over the button. I assume your google-page does something similar to cover the click-event.

5) What you should try:

First trigger some single events in native way:

button.dispatchEvent(new Event('eventName'));

If that gives no usable results use some reasonable combinations of events:

button.dispatchEvent(new Event('mouseover'));
button.dispatchEvent(new Event('mousedown'));
// or:
button.dispatchEvent(new Event('mousedown'));
button.dispatchEvent(new Event('mouseup')); // or: .....

There are many ways to combine events so that a single event doesn't do anything, but only the right combination works.

EDIT According to your invitation to investigate the source I found two ways:

1) The button itself has no eventListeners attached. The button is wrapped in an <a>-tag. This tag is parent of the button and its attribute jsaction 's value tells that <a> has listeners for click, focus, and mousedown. Targeting it directly works:

button.parentElement.dispatchEvent(new Event('click'));

2) If you want to click the button itself you must trigger an event that bubbles up the DOM to reach an element that has a click-handler. But when creating an event with new Event('name'), its bubbles-property defaults to false. It was my bad not thinking of that.. So the following works directly on the button:

button.dispatchEvent(new Event('click', {bubbles: true}));

EDIT 2 Digging deeper in whats going on on that page yielded an usable result:

It has been found that the page takes care of the mouse-pointer position and right order of the events, probable to distinguish wether a human or a robot/script triggers the events. Therefore this solution uses the MouseEvent - object containing the clientX and clientY properties, which holds the coordinates of the pointer when the event is fired.

A natural "click" on an element always triggers four events in given order: mouseover, mousedown, mouseup, and click. To simulate a natural behaviour mousedown and mouseup are delayed. To make it handy all steps are wrapped in a function which simulates 1) enter element at it's topLeft corner, 2) click a bit later at elements center. For details see comments.

function simulateClick(elem) {
    var rect = elem.getBoundingClientRect(), // holds all position- and size-properties of element
        topEnter = rect.top,
        leftEnter = rect.left, // coordinates of elements topLeft corner
        topMid = topEnter + rect.height / 2,
        leftMid = topEnter + rect.width / 2, // coordinates of elements center
        ddelay = (rect.height + rect.width) * 2, // delay depends on elements size
        ducInit = {bubbles: true, clientX: leftMid, clientY: topMid}, // create init object
        // set up the four events, the first with enter-coordinates,
        mover = new MouseEvent('mouseover', {bubbles: true, clientX: leftEnter, clientY: topEnter}),
        // the other with center-coordinates
        mdown = new MouseEvent('mousedown', ducInit),
        mup = new MouseEvent('mouseup', ducInit),
        mclick = new MouseEvent('click', ducInit);
    // trigger mouseover = enter element at toLeft corner
    elem.dispatchEvent(mover);
    // trigger mousedown  with delay to simulate move-time to center
    window.setTimeout(function() {elem.dispatchEvent(mdown)}, ddelay);
    // trigger mouseup and click with a bit longer delay
    // to simulate time between pressing/releasing the button
    window.setTimeout(function() {
        elem.dispatchEvent(mup); elem.dispatchEvent(mclick);
    }, ddelay * 1.2);
}

// now it does the trick:
simulateClick(document.querySelector(".c-T-S.a-b.a-b-B.a-b-Ma.oU.v2"));

The function does not simulate the real mouse movement being unnecessary for the given task.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Martin Ernst
  • 3,199
  • 1
  • 12
  • 12
  • Thanks for the informative answer. While I understand your points, and your Fiddle works as intended using this technique, it appears there may be something else going on on this page where any of the dispatchEvent permutations I have tried fail to yield the same result on the page in question. I have tried mouse over/down/up with a focus event in all possible spots. Not sure what else to try – coneybeare Oct 01 '14 at 13:54
  • @coneybeare :( sorry, I can't think any further myself. I have never heard of a way to render an element immune against events. – Martin Ernst Oct 01 '14 at 14:01
  • I will be awarding a bounty on this question tomorrow (the soonest SO lets me do so) so if you did want to try and dig deeper to help me solve this perplexing issue, the actual "Generate" button on the page may have more going on: https://security.google.com/settings/security/apppasswords – coneybeare Oct 01 '14 at 14:08
  • @coneybeare I never wanted to have a google-account, but now I have. I found this issue so interesting that I couldn't wait for the bounty. See my **EDIT** and try yourself: – Martin Ernst Oct 01 '14 at 16:07
  • Are we looking at the same button? The button on the page I linked to says "Generate" and is not wrapped by an anchor tag. You need to enable 2-factor auth on this new test account before seeing this page. But, following your lead I attempt to send the mousover, mousedown and mouseup events on the Generate button with the bubbles: true option... nothing. I also tried "click" on it as well. – coneybeare Oct 01 '14 at 16:22
  • The only jsaction attr in the button's lineage that I can see is on the div that contains the entire page's content :/ – coneybeare Oct 01 '14 at 16:23
  • @coneybeare Yeah we must be on different pages. Your link takes me to a german page with my account, instead of `2-factor auth` I made german `Bestätigung in zwei Schritten`. But under all pages I can reach is only one with such kind of button, and that's a-wrapped and says `Jetzt starten` instead of `Generate`. – Martin Ernst Oct 01 '14 at 17:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62278/discussion-between-coneybeare-and-martin-ernst). – coneybeare Oct 01 '14 at 17:26
0

$('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').trigger('click'); does the job - the click event is triggered.

.trigger() : Execute all handlers and behaviors attached to the matched elements for the given event type.

http://api.jquery.com/trigger/

$('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').on('click', function() {
   alert( $( this ).text() );
});

$('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').trigger('click');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div role="button" class="c-T-S a-b a-b-B a-b-Ma oU v2" aria-disabled="false" style="-webkit-user-select: none;" tabindex="0">
    Generate
</div>
bancer
  • 7,475
  • 7
  • 39
  • 58
  • Yes, this shows the new JS behavior, but I need to trigger the original behavior, not a new function – coneybeare Sep 30 '14 at 17:38
  • I am trying to simulate a click, not define a new function on an element. – coneybeare Sep 30 '14 at 17:43
  • $('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').trigger('click'); does exactly that. - simulates the click. Onlcick handler and alert is for demonstration only. – bancer Sep 30 '14 at 17:44
  • No, the whole point of the question is that $('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').trigger('click'); does not trigger the click on this element. – coneybeare Sep 30 '14 at 17:46
  • It DOES trigger the click in the snippet in my answer. So your problem is not in trigger() if it does not work on your page – bancer Sep 30 '14 at 17:51
  • Precisely. That is the point of this question. "Works for me" answers are not really considered valuable input on Stack Overflow. – coneybeare Sep 30 '14 at 17:53
  • Hey, that's not "works for me" answer. You can run my snippet on your machine and ensure that the same code works "for you". Click the button. – bancer Sep 30 '14 at 17:55
  • PLease reread the question... I am not sure you understand. I dont want to attach a new eventhandler to the button, I need to simulate the click to call the existing function on the button. – coneybeare Sep 30 '14 at 17:56
  • I repeat again: $('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').trigger('click'); simulates the click without attaching any new event handler. Your problem is not in trigger() function but in something else that prevents this function to do its job. – bancer Sep 30 '14 at 18:00
  • True, which means this does not answer my question of how can I get the button to fire. – coneybeare Sep 30 '14 at 18:08
  • So, maybe you want to trigger the wrong element which does not have any event listeners attached. – bancer Sep 30 '14 at 18:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62204/discussion-between-coneybeare-and-bancer). – coneybeare Sep 30 '14 at 18:12
-1

From the image you posted of event handlers, click is not one of them. Programmatically triggering a click is not the same as doing so naturally. Mouse enter/leave/press events are not fired here.

Scimonster
  • 32,893
  • 9
  • 77
  • 89
  • Yes, I get that, but I have tried button.mouseover().mousedown().mouseup() with no success either – coneybeare Sep 30 '14 at 17:37
  • And those events don't fire when you call them directly? – Scimonster Sep 30 '14 at 17:37
  • 1
    They do not. This is a google page so they may have extra mechanisms in play idk – coneybeare Sep 30 '14 at 17:41
  • 1
    Oh, i should have known. No-one else gives such crazy class names. :P They probably do have other wacky things going on. – Scimonster Sep 30 '14 at 17:42
  • I've seen google create some unexpected events before (in the maps api), so you may be looking for a custom event you need to fire – OneHoopyFrood Sep 30 '14 at 17:46
  • @colepanike any advice on how to locate this potential custom event? – coneybeare Sep 30 '14 at 18:16
  • Ooof, it's a toughy with their code. It's highly optimized so it's hard to read and may not even have an intelligible name (I site the very class this question references). The only thing to do is perhaps step through the code as it runs on your natrual click and see what it is doing. I guess it depends on how bad you want it. Sorry man. – OneHoopyFrood Sep 30 '14 at 18:18
  • Please note also that this is just a guess, I simply can't think of anything else that might be causing what you're seeing. I'm assuming that you're writing a browser extension and therefore have legitimate access to the page of course. If this is some kind of iframe thing then I'm afraid you may be out of luck. – OneHoopyFrood Sep 30 '14 at 18:20
  • It's an iOS app that will take a bit of the pain out of generating app specific pages for non-technical users. thanks for the suggestions – coneybeare Sep 30 '14 at 18:28
  • In that case you could also look for the code it's firing and call it directly, thus skipping the need for simulating the event. – OneHoopyFrood Oct 01 '14 at 15:50
-1

I actually tried on the link you gave and it works. There's no jquery on the page, so have to go by native methods.

var click= document.createEvent("MouseEvents");
click.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);

document.getElementsByClassName('c-T-S a-b a-b-B')[0].dispatchEvent(click)
bhavya_w
  • 9,186
  • 9
  • 29
  • 38
  • make sure you give the proper classname in the above code...the one which actually exists in the DOM (i.e first search the dom for proper classname)....let me know if u still have any problem.. – bhavya_w Sep 30 '14 at 18:01
  • So this returns true in the console when I run it, but the desired outcome does not occur... meaning nothing happens on the page whereas a real click does the thing I am trying to emulate. – coneybeare Sep 30 '14 at 18:21
-1

I have created a JS Fiddle for this and it is working fine. I have used following

$('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').click(function(){
    alert("clicked");
});
$('.c-T-S.a-b.a-b-B.a-b-Ma.oU.v2').click();// This will trigger click event programatically
Arindam Nayak
  • 7,346
  • 4
  • 32
  • 48
  • This just binds a new clickhandler. I am trying to simulate a click, not change it's action. – coneybeare Sep 30 '14 at 18:35
  • @coneybeare , yes, i have tried with second line ".click();", which gots invoked at page load ,and it is working fine. Not sure if i misunderstood your purpose! – Arindam Nayak Sep 30 '14 at 18:38
  • As indicated earlier, it does not accurately emulate a manual click on the page in question. – coneybeare Sep 30 '14 at 18:39
  • When you invoke ".click()", it does programmatic Click, and i thought, this is where you are facing issue, is it correct? – Arindam Nayak Sep 30 '14 at 18:40