32

I have a webpage with an iframe. I'd like to access the contents of the iframe using CasperJS. In particular, I need to click buttons and fill a form. How can I do that?

The main webpage is main.html:

<html><body>
<a id='main-a' href="javascript:console.log('pressed main-a');">main-a</a>
<iframe src="iframe.html"></iframe>
<a id='main-b' href="javascript:console.log('pressed main-b');">main-b</a>
</body></html>

The iframe is:

<html><body>
<a id='iframe-c' href="javascript:console.log('pressed iframe-c');">iframe-c</a>
</body></html>

My naïve approach:

var casper = require('casper').create({
    verbose: true,
    logLevel: "debug"
});

casper.start("http://jim.sh/~jim/tmp/casper/main.html", function() {
    this.click('a#main-a');
    this.click('a#main-b');
    this.click('a#iframe-c');
});

casper.run(function() {
    this.exit();
});

Doesn't work, of course, because the a#iframe-c selector isn't valid in the main frame:

[info] [phantom] Starting...
[info] [phantom] Running suite: 2 steps
[debug] [phantom] opening url: http://jim.sh/~jim/tmp/casper/main.html, HTTP GET
[debug] [phantom] Navigation requested: url=http://jim.sh/~jim/tmp/casper/main.html, type=Other, lock=true, isMainFrame=true
[debug] [phantom] url changed to "http://jim.sh/~jim/tmp/casper/main.html"
[debug] [phantom] Navigation requested: url=http://jim.sh/~jim/tmp/casper/iframe.html, type=Other, lock=true, isMainFrame=false
[debug] [phantom] Successfully injected Casper client-side utilities
[info] [phantom] Step 2/2 http://jim.sh/~jim/tmp/casper/main.html (HTTP 200)
[debug] [phantom] Mouse event 'click' on selector: a#main-a
[info] [remote] pressed main-a
[debug] [phantom] Mouse event 'click' on selector: a#main-b
[info] [remote] pressed main-b
[debug] [phantom] Mouse event 'click' on selector: a#iframe-c
FAIL CasperError: Cannot dispatch click event on nonexistent selector: a#iframe-c
#    type: uncaughtError
#    error: "CasperError: Cannot dispatch click event on nonexistent selector: a#iframe-c"
CasperError: Cannot dispatch click event on nonexistent selector: a#iframe-c    
  /tmp:901 in mouseEvent
  /tmp:365 in click
  /tmp/test.js:9
  /tmp:1103 in runStep
  /tmp:324 in checkStep

Is there any way to make this work? A hack that involves poking into phantomjs directly would be fine, but I don't know what to do there.

I'm using CasperJS version 1.0.0-RC1 and phantomjs version 1.6.0.

Jim Paris
  • 888
  • 2
  • 9
  • 15

5 Answers5

41

Spent forever looking for this, and of course I found the answer minutes after posting the question.

I can use the new frame switching commands added to phantomjs in this commit. Specifically, the this.page.switchToChildFrame(0) and this.page.switchToParentFrame() functions. It appears undocumented, and it also seems that the methods have been changed for upcoming releases, but it does work:

var casper = require('casper').create({
    verbose: true,
    logLevel: "debug"
});

casper.start("http://jim.sh/~jim/tmp/casper/main.html", function() {
    this.click('a#main-a');
    this.click('a#main-b');
    this.page.switchToChildFrame(0);
    this.click('a#iframe-c');
    this.page.switchToParentFrame();
});

casper.run(function() {
    this.exit();
});
Jim Paris
  • 888
  • 2
  • 9
  • 15
  • Jim, did you happen to know if you can this.test.assertVisible('#someElemInsideIframe') after switching? Seems to barf for me but this.click(...) works. – Rob Barreca Dec 06 '12 at 09:00
  • I don't, sorry. Visibility might be tested in some other way that doesn't work well with frames, I'm not sure. – Jim Paris Dec 06 '12 at 21:24
  • 1
    Did you see @olleolleolle's answer? withFrame is a documented method. – Capi Etheriel Feb 18 '13 at 17:54
  • 3
    Yes, I did. `withFrame` did not exist when I asked this question or accepted this answer, but it sounds like the right thing to use from now on. – Jim Paris Feb 18 '13 at 21:00
35

From 1.0 you can use withFrame

  casper.open("http://www.example.com/page.html", function() {
    casper.withFrame('flashHolder', function() {
      this.test.assertSelectorExists('#the-flash-thing', 'Should show Flash');
    });
  });
olleolleolle
  • 1,918
  • 16
  • 20
  • How could this work with frames that have dynamic names? I can't rely on the frame index (eg 0 or 1) instead because social services like twitter (via addthis) often load in their own iframes in the background. – DynamicDan Apr 01 '14 at 11:35
  • you can use frame index instead of frame name - http://docs.casperjs.org/en/latest/modules/casper.html#withframe – Valery Lourie Apr 08 '15 at 19:34
4

As a matter of fact you'll have to use the new --web-security=no feature provided by Phantomjs 1.5 in order to be able to access those iFrames and their contents.

Mihai Iorga
  • 39,330
  • 16
  • 106
  • 107
Turcu Vlad
  • 41
  • 2
  • 1
    Not really related -- this example is a same-domain iframe and cross-domain security isn't my problem. See my own answer for what worked (`--web-security=no` is still not required there). – Jim Paris Aug 27 '12 at 22:14
3

Suppose we have different frames(frame1 and frame2) and we have to access different elements(like click or check if div tag exits or not) of those frames.

casper.withFrame('frame1', function() {
    var file = '//*[@id="profile_file"]';
    casper.thenClick(x(file));
});

casper.withFrame('frame2', function() {
  casper.then(function () {
     casper.waitForSelector('#pageDIV',
            function pass() {
                console.log("pass");
            },
            function fail(){
                console.log("fail");
            }
      );
   });
});
0

You can do something like this:

casper.start("url here...", function() { 
    this.withFrame(0, function() {
        this.evaluate(function() {
            document.querySelector('img#btn_start').click();
        })
    })
});

You can replace the zero with the name of the iframe.

Bla...
  • 7,228
  • 7
  • 27
  • 46