Click item on Chrome or Safari page by XPath

Keyboard Maestro has excellent built-in actions for clicking on web-page links specified by their labels.

For cases where the target is not uniquely labelled, here is a custom action (generalised from an earlier macro ) which should be able to identify and click on any item on a web page.

The item is specified by an XPath, applied either to the whole document, or to the current selection, or mouse position.


Moderator Edit -- 2017-06-26 3:39 PM CT
Original plug in no longer works. The author has issued an update in a later post in this thread:



Custom Keyboard Maestro Plug-in

NAME

  • Click Chrome or Safari item at XPath

VERSION

  • 0.1

SYNOPSIS

  • Clicks a button, link or other item on a Chrome or Safari web page.
  • The item is specified by an XPath which can be applied to:
    • the root of the whole document,
    • the start of the current selection,
    • or the current hover position of the mouse.

OPTIONS

Note 1: an XPath applied to a mouse position or selection should start with a dot ./, referring to the selected or hovered node

Note 2: In Google Chrome, a simple absolute XPath for a selected element can be obtained with:
- Right-Click, Inspect Element, then
- Right-Click, Copy XPath

( The copied XPath will work in Safari, as well as in Chrome itself – Safari does not have its own Copy XPath feature )

  • Applied to:
    • document
    • mouse position
    • selection

REQUIREMENTS

  • Yosemite
    • The core script clickItemAtXPath.sh is mainly written in Javascript for Applications

INSTALLATION

  • Drag the .zip file onto the Keyboard Maestro icon in the OS X toolbar.
  • (if updating a previous version of the action, first manually remove the previous copy from the custom actions folder)
    • ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Actions

CONTACT

5 Likes

For reference, the unminified .js

// Rob Trew Twitter @ComplexPoint 2015 MIT License 
(function (strPath, strScope, strBrowser) {

  function fnClick(strPath, strScope) {
    var oAnchor = (strScope === 'document') ?
      document : (
        strScope === 'selection' ?
        window.getSelection().anchorNode : null
      ),

      nh = oAnchor ? null : document.querySelectorAll(':hover'),
      iLast = (nh ? nh.length : null),
      nodeHover = iLast ? nh[iLast - 1] : null,

      oRoot = oAnchor ? oAnchor : nodeHover,
      nodeToClick = oRoot ? document.evaluate(
        strPath,
        oRoot,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      ).singleNodeValue : null;


    if (nodeToClick) {
      if (nodeToClick.fireEvent) {
        nodeToClick.fireEvent("onclick");
      } else {
        var e = document.createEvent("Events");
        e.initEvent("click", true, false);
        nodeToClick.dispatchEvent(e);
      }
      return 0;
    } else return 1;
  }

  // Evaluate code for a function application to a named browser (Chrome | Safari)
  // fn --> [arg] --> strBrowserName --> a
  function evalJSinBrowser(fnMain, lstArgs, strBrowser) {

    var strDefault = "Safari",
      blnSafari = ((
        strBrowser = ((strBrowser || '').indexOf(strDefault) === 0) ?
        strDefault : "Google Chrome"
      ) === strDefault),
      appBrowser = Application(strBrowser),
      lstWins = appBrowser.windows(),
      lngWins = lstWins.length,

      // an open window (new if none exists)
      oWin = lngWins ? lstWins[0] : blnSafari ?
      appBrowser.Document().make() && appBrowser.windows[0] :
      appBrowser.Window().make(),

      // code of an fnMain().apply(null, lstArgs) function application
      strJS = [
        '(',
        fnMain.toString(),
        ').apply(null, ',
        JSON.stringify(lstArgs),
        ');'
      ].join('');

    return (
      blnSafari ?
      appBrowser.doJavaScript(
        strJS, {
          "in": oWin.tabs[0]
        }
      ) :
      oWin.activeTab.execute({
        "javascript": strJS
      })
    );
  }

  /***** MAIN ***/
  evalJSinBrowser(fnClick, [strPath, strScope], strBrowser);

})(
  "$KMPARAM_XPath",
  "$KMPARAM_applied_to",
  "$KMPARAM_Browser"
);
3 Likes

Thanks, Rob. I think this will be very useful! :+1:

###For those not familiar with XPath, like me, you may find these helpful:

  1. Introduction to using XPath in JavaScript @ mozilla.org
  2. XPath Tutorial @ w3schools.com
1 Like

What am I doing wrong? I cannot get it to work! The previous macro works great but the plugin does not work :frowning:

What are you doing ?

probably best to:

  1. post a macro
  2. tell us what you expect it to do
  3. show us what is happening instead

Forget it! I restarted Keyboard Maestro and it works fine now…

Unlike @carycrusiau, I wasn’t able to get this to work at all. As a test I used the example in the previous macro to see if I could even get it to click the search box on the page & no luck. Nothing happens in either Chrome or Safari.

What else can I provide to get help in getting this to work?

Thanks!

Same checklist as Sep 15 (above), I think.

the search box on the page

The problem, incidentally could be with the two definite articles there : - )

( No two HTML pages or search boxes necessarily have the same (or even similar) XPaths )

I just tried it by copying the XPath from the search element on this page and still nothing happens. :confused:

If you post a macro we can look at it.

Thanks for this custom action and the macro that proceeded it.

Can you confirm whether this action is all that is needed to “click” the item represented by the XPath (the macro needed an If/then statement to execute the XPath)? I have the following but can’t get it to click that item. The XPath comes from using Chrome’s Inspect; the same XPath works successfully in the macro.

XPath: //*[@id=“downloadDocumentLink0”]
applied to: document
Browser: Google Chrome

Also, Click Google Chrome item matching XPath doesn’t appear to accept a variable, so is it possible use a loop to click on multiple similar XPath items (download links to bank statements) such as:
/[@id=“downloadDocumentLink0”]
/
[@id=“downloadDocumentLink1”]
/*[@id=“downloadDocumentLink2”]
etc.

Thanks!

aal

@ComplexPoint

I tried this macro and couldn’t get it to work. Here’s a short screencast showing what I did:

Can you tell me what I’m doing wrong? Thanks.

Hey Dan,

I think that plugin is currently broken.

-Chris

Well, dang! LOL. Thanks for the reply.

I no longer have editing rights to the original post, alas, but I have now tracked down the problem (a shift in the optional status of the Results key), and this copy now works again on my system:

Click Chrome or Safari item at XPath.zip (8.4 KB)

1 Like

Had to watch my video to remember what I was trying to do!

Thanks for fixing it.

After a few hours of despair, I think I found the reasons, why so many people - me included - seem to struggle with this KM action. Most examples of XPath syntax use double quotes, e.g.:

//*[@id="search-button"]

However this does not work in the KM custom action. Because of the scripts it uses, it can only parse single quotes, so you need to write instead:

//*[@id='search-button']

While searching for a solution, I found out that you can test and debug your XPath definition from the console within the Google Chrome developer tools. Just type:

$x('<your_xpath_definition>')

But be aware that you need to use double quotes within your XPath there. So it becomes:

$x('//*[@id="search-button"]')

When your syntax is correct the console returns the full line of HTML you were searching for and when you hover the mouse pointer over the result, the actual element is also highlighted in the content area. From there you can just copy it into KM. But don’t forget to change the quote symbols back to single quotes :slight_smile:

Hope this will save people some time. And by the way: A big thank you to @ComplexPoint for this great custom action. It is very helpful

Julian

2 Likes

Well done Julian!

great.
any idea why it works in Chrome but not in Safari ?

Unfortunately I have no idea. I even gave it a try. My XPath syntax worked from the devel console from within Safari, but not when triggered from KM. So my guess is, that there is a problem with the custom action, that affects Safari only. Sorry. Maybe @ComplexPoint could have another look at it.