casperJS How select element(s) by specific starting text with querySelector or querySelectorAll

17,193

Solution 1

Here's a simple loop that matches the text of the link you want and adds them to an array:

the html:

<div>
    <a name="test">not this one</a>
    <a name="test">not this one</a>
    <a name="test">not this one</a>
    <a name="test">not this one</a>
    <a name="test">this one</a>
</div>

and the script:

var as = document.querySelectorAll("a");
var match = "this one";
var elems = [];

for (var i=0; i<as.length; i++){
    if (as[i].textContent === match) {
        elems.push(as[i]);
    }
}

now you have the matched elements in the elems array.

fiddle here: http://jsfiddle.net/0pLd8s9r/

Solution 2

If you anyway know the text within the anchor, try clickLabel. This is what I use

this.clickLabel('Anchor_Text','a')

Where Anchor_Text is the complete text in the anchor

Found here

Solution 3

querySelector(All) will not let you specify the content of the element you're looking for. You would have to look into each anchor individually.

You could try something like this:

var hrefs = document.getElementById('all_anchors').querySelectorAll('a');
for (var x=0;x<hrefs.length;x++) {
    if (hrefs[x].innerHTML==text) {
        alert(hrefs[x].href);   
    }
}

(JSFiddle)

Solution 4

You seem to be confused with page context. Everything that is executed inside an evaluate callback is sandboxed. It will cannot see variables on the outside or the outside cannot see variables on the inside. They have to be specifically passed and can only be composed of basic primitives. From the docs:

Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.

Regarding your actual question, you can use XPath as suggested by BoltClock. As you have seen, CasperJS provides the XPath utility which is usable outside of the page context.

var x = require('casper').selectXPath;
casper.click(x("//a[starts-with(.,'clickhere')]"));

starts-with(string, string) is an XPath 1.0 function to check if some string starts with another string.

If you want to use this only to retrieve the element based on the starting text and then do something else with it, you can use the client utils of CasperJS for that inside the page context. It provides for example the function getElementByXPath:

casper.evaluate(function(){
    var element = __utils__.getElementByXPath("//a[starts-with(.,'clickhere')]");
    // do something with element
});

The __utils__ property is injected by CasperJS into the page context for every page.

The . in the XPath is the complete text that is inside this element whereas text() would only select text nodes that are directly inside the a element. Since the text that you use to select the a element is inside of the b element, you need to either use . like shown above or select the b element and then go to the parent:

"//a/b[starts-with(text(),'clickhere')]/.."
Share:
17,193
Marco
Author by

Marco

Updated on June 05, 2022

Comments

  • Marco
    Marco almost 2 years

    I have to use casperJS to upload one file to a customer server, now before uploading I need to emulate a click on two specific links, these links (simple HTML anchors) don't have a name/id/class... (really ugly HTML code) so I have the only option to select it by it's text content.

    How can I find it using the querySelector or querySelectorAll methods?

    So far, I could come up with the following (unsuccessful attempts) :(

    querySelector("a[text()='texttofind']");
    querySelector("a[text='texttofind']");
    querySelector("a[text=texttofind]");
    

    EDIT AFTER ALL SUGGESTION

    TITLE UPDATED to be more specific about my problem that seem related only to casperjs

    PLATFORM - Windows 7 - CasperJS version 1.1.0-beta3 - phantomjs version 1.9.7 - Python 2.7

    so, probably i'm too dumb :( now i post a complete example that sadly don't work for me :(

    HTML main index

    <html>
    <head>
    <title>TEST Main Page</title>
    </head>
    <frameset cols="100,100" >
        <frame name="menu_a" src="menu_1.html">
        <frame name="menu_b" src="menu_2.html">
    </frameset>
    </html>
    

    HTML menu_1.html

    <html>
    <head>
    <title>TEST Menu 1</title>
    </head>
    <body style="background-color:red;">
    <h3>Menu 1</h3>
    <select onchange="javscript:alert('test')" id="test" size="1" name="systemID">
        <option value="0">---</option>
        <option selected="selected" value="1">TestMenu1            </option>
        <option value="17">TestMenu2                               </option>
    </select>
    </body>
    </html>
    

    HTML menu_2.html

    <html>
    <head>
    <title>TEST Menu 1</title>
    </head>
    <body style="background-color:orange;">
    <h3>Menu 2</h3>
     <a href="javascript:alert('test')"><b>clickhere   </b></a>
     <a href="javascript:alert('noclickhere')"><b>NoClickHere   </b></a>
    </body>
    </html>
    

    CasperJS script

    start equal for all tests:

    var casper = require('casper').create();
    
    casper.start(serverName, function(){});
    

    first test - clicklabel as suggested by @Ka0s

    casper.then(function(){
        this.withFrame('menu_b', function(){
            this.clickLabel('clickhere', 'a');
        });
    });
    

    result:

    CasperError: Cannot dispatch mousedown event on nonexistent selector: xpath selector: //a[text()="test"]
      /bin/casperjs/modules/casper.js:1355 in mouseEvent
      /bin/casperjs/modules/casper.js:462 in click
      /bin/casperjs/modules/casper.js:487 in clickLabel
      /test.js:90
      /bin/casperjs/modules/casper.js:1553 in runStep
      /bin/casperjs/modules/casper.js:399 in checkStep
    

    this not work even if i cleanup the string removing blank spaces at the end of the clickhere string on my test code.

    second test - xPath as sugggested by @ArtjomB

    casper.then(function(){
        this.withFrame('menu_b', function(){
        this.evaluate(function(){
         var element = __utils__.getElementByXPath("//a[starts-with(text(),'clickhere')]");
         console.log(element);
        });
     });
    });
    

    result:

    remote message caught: undefined
    

    so i suppose that the xPath fail finding the element.

    Third test - querySelectorAll with for loop as suggested by @Brunis

    This is a strange beaviour, casperJS return the content of href instead the object this not seem an error on below code but a problem on my implementation or something else.

    casper.then(function(){
        this.withFrame('menu_b', function(){
            this.evaluate(function(){
            var as = document.querySelectorAll("a");
            var match = "clickhere";
            var elems = [];     
            for (var i=0; i<as.length; i++){
                if (as[i].textContent === match) {
                    elems.push(as[i]);
                }
            }
            console.log(elems[0]);
        });
     });
    });
    

    Result: remote message caught: javascript:alert('test')

    i obtain the href code not the object! if i try this example in a fiddle, i receive the object and i can call onclick() on it.

  • Marco
    Marco over 9 years
    thank you, this seem work on browser but casperJs have a strange behavior on your code. If i do console.log(elems[0]) on browser (firefox) it return [a] object so i can do elems[0].onclick() to fire click event. but if i do the same on casperJs i receive the content on the href not the object!!
  • Brunis
    Brunis over 9 years
    Remove the onchange="javascript:alert('test')" from your select in html