Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interacting with elements inside an iframe #203

Closed
rclai opened this issue Aug 4, 2015 · 23 comments
Closed

Interacting with elements inside an iframe #203

rclai opened this issue Aug 4, 2015 · 23 comments

Comments

@rclai
Copy link

rclai commented Aug 4, 2015

Is this possible?

EDIT: You should probably just use Puppeteer.

@rclai
Copy link
Author

rclai commented Aug 5, 2015

EDIT: This doesn't work anymore on 2.0 and above, scroll all the way down for the new solutions.

I had to create a withFrame action function that goes inside the iframe context and a backToParentFrame function to go back to the parent frame:

exports.withFrame = function(frameSelector, fn, done){
  // `frameSelector` is either an index number or the iframe's name
  // no class names or ids unfortunately
  // As you can see, the code is identical to that of the `use` function
  debug('.withFrame():' + frameSelector);
  this.page.switchToFrame(frameSelector);
  var cache = this.queue;
  this.queue = [];
  fn(this);
  var self = this;
  this.queue = this.queue.concat(cache);
  done();
};

exports.backToParentFrame = function(done){
  debug('.backToParentFrame()');
  this.page.switchToParentFrame();
  done();
};

I originally tried to make it so that once the actions inside withFrame are done, it would automatically go backtoParentFrame but I couldn't figure out how to do that, so I made it separate. So essentially you would do:

<!-- For this given iframe -->
<iframe name="formIframe" src="somelogin.php" />
nightmare
  .withFrame('formIframe', function (nightmare) {
    nightmare
      .wait('form#login')
      .type('#username', 'rclai')
      .type('#password', 'password')
      .click('#submit')
  })
  .backToParentFrame()
  .wait('.for-my-logged-in-indicator')
  .run(...);

Don't know if you guys want to include this functionality or not. I've taken the concept from CasperJS here. If you guys like it, I would suggest figuring out how we can automatically go back to the parent frame without explicitly having to do so.

@molinto
Copy link

molinto commented Aug 11, 2015

Nice feature +1

@molinto
Copy link

molinto commented Aug 11, 2015

Where would this be added @rclai please?

@rclai
Copy link
Author

rclai commented Aug 11, 2015

You should be able to copy and paste the code I typed above and put it into nightmare/lib/actions.js before the actions are attached to the Nightmare prototype.

@molinto
Copy link

molinto commented Aug 12, 2015

Sweet, thanks!

@avioli
Copy link

avioli commented Sep 8, 2015

+1

@avioli
Copy link

avioli commented Sep 8, 2015

Thanks @rclai I've created a plugin from your code: https://www.npmjs.com/package/nightmare-iframe

@rclai
Copy link
Author

rclai commented Sep 9, 2015

Cool.

@rclai rclai changed the title How do I submit a form inside an iframe? Interacting with elements inside an iframe Sep 13, 2015
@zeevl
Copy link
Contributor

zeevl commented Oct 10, 2015

The above PR allows you to do new Nightmare({'web-preferences': {'web-security': false}});, which turns off same-origin policy in electron. You can then do things like,

var result = yield nightmare
  .evaluate(function () {
    return $("#id_description_iframe").contents().find("body").html();
  });

@Skit
Copy link

Skit commented Nov 12, 2015

Thank you for the feature!
Why do I get an exception?

TypeError: Cannot read property 'switchToFrame' of undefined
    at Nightmare.exports.withFrame (/var/www/html/antiqode/node_modules/nightmare/lib/actions.js:439:12)
    at Nightmare.next (/var/www/html/antiqode/node_modules/nightmare/lib/nightmare.js:144:12)
    at EventEmitter.<anonymous> (/var/www/html/antiqode/node_modules/nightmare/lib/nightmare.js:150:12)
    at EventEmitter.g (events.js:260:16)
    at emitNone (events.js:67:13)
    at EventEmitter.emit (events.js:166:7)
    at ChildProcess.<anonymous> (/var/www/html/antiqode/node_modules/nightmare/lib/ipc.js:28:10)
    at emitTwo (events.js:87:13)
    at ChildProcess.emit (events.js:172:7)
    at handleMessage (internal/child_process.js:686:10)

I tried to install the plugin

npm install --save nightmare-iframe
$ phantomjs -v
2.0.1-development
$ node -v
v4.2.2

@rickmed
Copy link

rickmed commented Dec 2, 2015

I tried the change preferences solution but could not change elements (type, etc) inside the iframe, just extract info. And the first solution does not work for me as the iframes in my problem only have ids. Any ideas?

@nathan-martinez
Copy link

So, how can we navigate through iframes with the new Nightmare built on Electron? It seems Electron has no support for iframes.

@tiangolo
Copy link

I just submitted a PR (#496) that should solve this issue. If you have the time you may check it to see if it works.

An example of the usage would be:

var Nightmare = require('nightmare');
var nightmare = Nightmare();
nightmare.goto('http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe').then();

nightmare.title().then(function(res){console.log(res)});

nightmare.enterIframe('#iframeResult').enterIframe('iframe');

nightmare.title().then(function(res){console.log(res)});

nightmare.exitIframe();

nightmare.title().then(function(res){console.log(res)});

nightmare.end();

It would print to the console:

Tryit Editor v2.6
W3Schools Online Web Tutorials
Tryit Editor v2.6

Also, it should work with general selectors, not just with iframe names or numbers.

@RichardJECooke
Copy link

RichardJECooke commented Aug 4, 2016

Hello. I'm trying to enter a credit card number in an iframe and I'm not sure which of the methods to use:

  • using plain nightmare (doesn't work): .wait('[name=card.number]')
  • using nightmare-iframe
  • using tiangolo's patch
  • using {'web-security': false} (and then what?)

Please can someone tell me which is best? This issue has too many options.

PS. I'm using Electron, I see references above to phantom.js which confuse me even more, because I thought that was for Nightwatch, not Nightmare.

@RichardJECooke
Copy link

More Googling shows me there isn't a solution to this problem. Please will whoever runs this project add a sentence to the main readme.md stating that nightmare doesn't support iframes.

We wouldn't have tried to learnt this framework if we knew it couldn't handle iframes. Their supoprt is absolutely essential to test any website that does ecommerce.

@tiangolo
Copy link

tiangolo commented Aug 4, 2016

@RichardJECooke here's the summary.

  • The first approach commented here doesn't work anymore with the latest versions.
  • I wrote the PR, it got rejected.
  • @rosshinkley used parts of that PR to create an improved version of the functionality as an external plug-in.

Right now, the best you can do is use @rosshinkley's https://github.com/rosshinkley/nightmare-iframe-manager. Go there and check his documentation. That's what I'm doing.

The Nightmare team (Segmentio) don't like the approach taken in the PR and the plug-in (not even Ross, the plug-in's writer). They dislike it so much that didn't wan to publish it to NPM, you have to install it from GitHub. Feel yourself hyper-warned. No warranties anywhere, use at your own risk, etc.

Given that, for non-mission critical problems (to emphasize the warnings) the plug-in works pretty good.

I use it to extract my credit card transactions in my bank's website.


Here's what you would do (excessively detailed):

  • Create a directory for your project and enter it:
mkdir myProject
cd myProject
  • Initiate your Node / Nightmare project with npm filling the package data:
npm init
  • Install Ross's plug-in from the GitHub repo (npm does it automatically for you, but you probably need to have git installed):
npm install --save rosshinkley/nightmare-iframe-manager
  • In your main project file, require Nightmare and the plug-in and pass the Nightmare object to the plug-in:
var Nightmare = require('nightmare');
require('nightmare-iframe-manager')(Nightmare);

@RichardJECooke
Copy link

Sadly this doesn't work for me, I get:

TypeError: Cannot set property 'evaluate_now' of undefined
    at module.exports.exports (/home/rje/projects/ekaya/dist/client_gumtree/ts_output/client_gumtree/node_modules/nightmare-iframe-manager/nightmare-iframe.js:5:36)
    at Object.<anonymous> (/home/rje/projects/ekaya/dist/client_gumtree/ts_output/client_gumtree/tests/helper.js:5:73)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/rje/projects/ekaya/dist/client_gumtree/ts_output/client_gumtree/tests/root/root.js:11:16)
    at Module._compile (module.js:413:34)
not ok 1 no plan found
not ok 2 no assertions found

when calling

import Nightmare = require('nightmare');
const show = (process.env.ekaya_gumtree_test_show_browser_window === 'true');
export const nightmare = new Nightmare({'show': show, 'height': 900 });
import manageNightmareIframesPlugin = require('nightmare-iframe-manager');
manageNightmareIframesPlugin(nightmare);

@tiangolo
Copy link

tiangolo commented Aug 4, 2016

You are modifying the nightmare instance after creating it. You have to modify the Nightmare "class" and then create the instance.

I bet it would be something like:

import Nightmare = require('nightmare');
import manageNightmareIframesPlugin = require('nightmare-iframe-manager');
manageNightmareIframesPlugin(Nightmare);
const show = (process.env.ekaya_gumtree_test_show_browser_window === 'true');
export const nightmare = new Nightmare({'show': show, 'height': 900 });

@yokomotod
Copy link

Thank you for summary ! This could work for me.

For different domain iframe,

new Nightmare({webPreferences: {webSecurity: false})

is needed, seems. (web-preferencesand web-security are renamed since Electron 1.0)

Otherwise, an error

Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "*****" from accessing a cross-origin frame.

will occur.

I hope this can help someone.

@RoyTinker
Copy link

RoyTinker commented Mar 30, 2017

I'm getting errors when using nightmare-iframe-manager under xvfb (i.e. xvfb-run node nightmare-script.js) on linux. If I use a real X server as the display (or run it on MS Windows), it works fine.

Kind of a bummer, as I was hoping to use nightmare as part of a build on our Jenkins server (Ubuntu server 16.06).

@tiangolo
Copy link

@RoyTinker you should probably move to https://github.com/GoogleChrome/puppeteer .

It is made by the GoogleChrome team directly, uses Chrome headless and has official support for iframes.

It was released about a month ago or so, and the GitHub stars count is already over 9000.

@saegen
Copy link

saegen commented Jan 31, 2018

OMG! Well that's a nightmare! It was doing so well until the Iframe :(

@RoyTinker
Copy link

Ok, moved to puppeteer, it's working great! A few gotchas in automation, but it's good now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.