Commit 3a9fcefd 3a9fcefdeda50c97dc6ae298502e95fd625861dc by Nicolas Perriault

Added Casper.waitForPage() and Casper.withChildPage()

PhantomJS 1.7 ships with support for child pages (say popups and frames). CasperJS can now wait for a child being opened and loaded to react accordingly using these two new methods:

```js
casper.start('http://foo.bar/').then(function() {
    this.test.assertTitle('Main page title');
    this.clickLabel('Open me a popup');
});

casper.waitForPage(/popup\.html$/, function() {
    this.withChildPage(this.childPages[0], function() {
        this.test.assertTitle('Popup title');
    });
});

casper.then(function() {
    this.test.assertTitle('Main page title');
});
```
1 parent f4514d88
......@@ -45,6 +45,29 @@ That's especially useful in case a given test script is abruptly interrupted lea
The whole [CapserJS test suite](https://github.com/n1k0/casperjs/tree/master/tests/) has been migrated to use this new feature.
#### Support for child pages (frames & popups)
PhantomJS 1.7 ships with support for child pages (say popups and frames). CasperJS can now wait for a child being opened and loaded to react accordingly using the new [`Casper.waitForPage()`](http://casperjs.org/api.html#casper.waitForPage) and [`Casper.withChildPage()`](http://casperjs.org/api.html#casper.withChildPage) methods:
```js
casper.start('http://foo.bar/').then(function() {
this.test.assertTitle('Main page title');
this.clickLabel('Open me a popup');
});
casper.waitForPage(/popup\.html$/, function() {
this.withChildPage(this.childPages[0], function() {
this.test.assertTitle('Popup title');
});
});
casper.then(function() {
this.test.assertTitle('Main page title');
});
```
**Note:** Support for this is highly experimental though, your [feedback is warmly welcome](https://github.com/n1k0/casperjs/issues/279).
#### `Casper.mouseEvent()` now uses native events for most operations
Native mouse events from PhantomJS bring a far more accurate behavior.
......
Subproject commit ef16b5cd6af2955a67e63188125338ced0e669b7
Subproject commit af59b2ac62695bca6c30eb1b39257124563e24cb
......
......@@ -138,6 +138,7 @@ var Casper = function Casper(options) {
};
this.mouse = mouse.create(this);
this.page = null;
this.childPages = [];
this.pendingWait = false;
this.requestUrl = 'about:blank';
this.resources = [];
......@@ -1403,7 +1404,7 @@ Casper.prototype.start = function start(location, then) {
this.options.logLevel = "warning";
}
if (!utils.isWebPage(this.page)) {
this.page = utils.isWebPage(this.options.page) ? this.options.page : createPage(this);
this.page = this.mainPage = utils.isWebPage(this.options.page) ? this.options.page : createPage(this);
}
this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings);
if (utils.isClipRect(this.options.clipRect)) {
......@@ -1726,6 +1727,28 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
};
/**
* Waits for a child page having its url matching the provided pattern to be opened
* and loaded.
*
* @param String|RegExp urlPattern The child page url pattern
* @param Function then The next step function (optional)
* @param Function onTimeout Function to call on operation timeout (optional)
* @param Number timeout Timeout in milliseconds (optional)
* @return Casper
*/
Casper.prototype.waitForPage = function(urlPattern, then, onTimeout, timeout) {
"use strict";
return this.waitFor(function() {
return this.childPages.some(function(page) {
if (utils.betterTypeOf(urlPattern) === 'regexp') {
return urlPattern.test(page.url);
}
return page.url.indexOf(urlPattern) !== -1;
});
}, then, onTimeout, timeout);
};
/**
* Waits until a given resource is loaded
*
* @param String/Function test A function to test if the resource exists.
......@@ -1839,6 +1862,39 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
};
/**
* Makes the provided child page as the currently active one. Note that the
* active page will be reverted when finished.
*
* @param WebPage childPage A child page instance
* @param Function then Next step function
* @return Casper
*/
Casper.prototype.withChildPage = function withChildPage(childPage, then) {
"use strict";
if (!utils.isWebPage(childPage) || !this.childPages.some(function(activePage) {
return activePage.id === childPage.id;
})) {
throw new CasperError("withChildPage() invalid or missing child page.");
}
if (!utils.isFunction(then)) {
throw new CasperError("withChildPage() requires a step function.");
}
// make the childPage the currently active one
this.page = childPage;
try {
this.then(then);
} catch (e) {
// revert to main page on error
this.page = this.mainPage;
throw e;
}
return this.then(function _step() {
// revert to main page
this.page = this.mainPage;
});
};
/**
* Changes the current page zoom factor.
*
* @param Number factor The zoom factor
......@@ -1960,6 +2016,19 @@ function createPage(casper) {
}
casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame);
};
page.onPageCreated = function onPageCreated(newPage) {
casper.emit('child.page.created', newPage);
newPage.onLoadFinished = function onLoadFinished() {
casper.childPages.push(newPage);
casper.emit('child.page.loaded', newPage);
};
newPage.onClosing = function onClosing(closedPage) {
casper.childPages = casper.childPages.filter(function(childPages) {
return childPages.id !== closedPage.id;
});
casper.emit('child.page.closed', closedPage);
};
};
page.onPrompt = function onPrompt(message, value) {
return casper.filter('page.prompt', message, value);
};
......
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CasperJS test child page</title>
</head>
<a href="." class="close">close</a>
<body>
<script>
var w = window.open("http://localhost:54321/tests/site/index.html",
"popup", "menubar=no, status=no, scrollbars=no, menubar=no, width=400, height=300");
document.querySelector('a').onclick = function onclick(evt) {
evt.preventDefault();
w.close();
};
</script>
</body>
</html>
/*jshint strict:false*/
/*global CasperError casper console phantom require*/
var utils = require('utils');
var x = require('casper').selectXPath;
casper.on('child.page.created', function(page) {
this.test.pass('child.page.created event is fired');
this.test.assert(utils.isWebPage(page),
'child.page.created event callback get a child WebPage instance');
});
casper.on('child.page.loaded', function(page) {
this.test.pass('child.page.loaded event is fired');
this.test.assertEquals(page.evaluate(function() {
return document.title;
}), 'CasperJS test index',
'child.page.loaded is triggered when child page contents are actually loaded');
});
casper.on('child.page.closed', function(page) {
this.test.assertEquals(this.childPages.length, 0, 'child.page.closed event is fired');
});
casper.start('tests/site/child-page.html');
casper.waitForPage('index.html', function() {
this.test.pass('Casper.waitForPage() waits for a child page being created');
this.test.assertEquals(this.childPages.length, 1, 'A child page has been added');
var childPage = this.childPages[0];
this.test.assert(utils.isWebPage(childPage), 'A child page is a WebPage');
this.withChildPage(childPage, function() {
this.test.assertEquals(this.page.id, childPage.id,
'Casper.withChildPage() switched to child page as current active one');
this.test.assertEval(function() {
return '__utils__' in window;
}, 'Casper.withChildPage() has client utils injected');
this.test.assertTitle('CasperJS test index',
'Casper.withChildPage() can perform evaluation on the active child page');
this.test.assertExists('h1',
'Casper.withChildPage() can perform assertions on the DOM');
this.test.assertExists(x('//h1'),
'Casper.withChildPage() can perform assertions on the DOM using XPath');
});
});
casper.thenClick('.close', function() {
this.test.assertEquals(this.childPages.length, 0, 'Child page is removed when closed');
});
casper.run(function() {
this.test.done(14);
});