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 ...@@ -45,6 +45,29 @@ That's especially useful in case a given test script is abruptly interrupted lea
45 45
46 The whole [CapserJS test suite](https://github.com/n1k0/casperjs/tree/master/tests/) has been migrated to use this new feature. 46 The whole [CapserJS test suite](https://github.com/n1k0/casperjs/tree/master/tests/) has been migrated to use this new feature.
47 47
48 #### Support for child pages (frames & popups)
49
50 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:
51
52 ```js
53 casper.start('http://foo.bar/').then(function() {
54 this.test.assertTitle('Main page title');
55 this.clickLabel('Open me a popup');
56 });
57
58 casper.waitForPage(/popup\.html$/, function() {
59 this.withChildPage(this.childPages[0], function() {
60 this.test.assertTitle('Popup title');
61 });
62 });
63
64 casper.then(function() {
65 this.test.assertTitle('Main page title');
66 });
67 ```
68
69 **Note:** Support for this is highly experimental though, your [feedback is warmly welcome](https://github.com/n1k0/casperjs/issues/279).
70
48 #### `Casper.mouseEvent()` now uses native events for most operations 71 #### `Casper.mouseEvent()` now uses native events for most operations
49 72
50 Native mouse events from PhantomJS bring a far more accurate behavior. 73 Native mouse events from PhantomJS bring a far more accurate behavior.
......
1 Subproject commit ef16b5cd6af2955a67e63188125338ced0e669b7 1 Subproject commit af59b2ac62695bca6c30eb1b39257124563e24cb
......
...@@ -138,6 +138,7 @@ var Casper = function Casper(options) { ...@@ -138,6 +138,7 @@ var Casper = function Casper(options) {
138 }; 138 };
139 this.mouse = mouse.create(this); 139 this.mouse = mouse.create(this);
140 this.page = null; 140 this.page = null;
141 this.childPages = [];
141 this.pendingWait = false; 142 this.pendingWait = false;
142 this.requestUrl = 'about:blank'; 143 this.requestUrl = 'about:blank';
143 this.resources = []; 144 this.resources = [];
...@@ -1403,7 +1404,7 @@ Casper.prototype.start = function start(location, then) { ...@@ -1403,7 +1404,7 @@ Casper.prototype.start = function start(location, then) {
1403 this.options.logLevel = "warning"; 1404 this.options.logLevel = "warning";
1404 } 1405 }
1405 if (!utils.isWebPage(this.page)) { 1406 if (!utils.isWebPage(this.page)) {
1406 this.page = utils.isWebPage(this.options.page) ? this.options.page : createPage(this); 1407 this.page = this.mainPage = utils.isWebPage(this.options.page) ? this.options.page : createPage(this);
1407 } 1408 }
1408 this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings); 1409 this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings);
1409 if (utils.isClipRect(this.options.clipRect)) { 1410 if (utils.isClipRect(this.options.clipRect)) {
...@@ -1726,6 +1727,28 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -1726,6 +1727,28 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
1726 }; 1727 };
1727 1728
1728 /** 1729 /**
1730 * Waits for a child page having its url matching the provided pattern to be opened
1731 * and loaded.
1732 *
1733 * @param String|RegExp urlPattern The child page url pattern
1734 * @param Function then The next step function (optional)
1735 * @param Function onTimeout Function to call on operation timeout (optional)
1736 * @param Number timeout Timeout in milliseconds (optional)
1737 * @return Casper
1738 */
1739 Casper.prototype.waitForPage = function(urlPattern, then, onTimeout, timeout) {
1740 "use strict";
1741 return this.waitFor(function() {
1742 return this.childPages.some(function(page) {
1743 if (utils.betterTypeOf(urlPattern) === 'regexp') {
1744 return urlPattern.test(page.url);
1745 }
1746 return page.url.indexOf(urlPattern) !== -1;
1747 });
1748 }, then, onTimeout, timeout);
1749 };
1750
1751 /**
1729 * Waits until a given resource is loaded 1752 * Waits until a given resource is loaded
1730 * 1753 *
1731 * @param String/Function test A function to test if the resource exists. 1754 * @param String/Function test A function to test if the resource exists.
...@@ -1839,6 +1862,39 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on ...@@ -1839,6 +1862,39 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
1839 }; 1862 };
1840 1863
1841 /** 1864 /**
1865 * Makes the provided child page as the currently active one. Note that the
1866 * active page will be reverted when finished.
1867 *
1868 * @param WebPage childPage A child page instance
1869 * @param Function then Next step function
1870 * @return Casper
1871 */
1872 Casper.prototype.withChildPage = function withChildPage(childPage, then) {
1873 "use strict";
1874 if (!utils.isWebPage(childPage) || !this.childPages.some(function(activePage) {
1875 return activePage.id === childPage.id;
1876 })) {
1877 throw new CasperError("withChildPage() invalid or missing child page.");
1878 }
1879 if (!utils.isFunction(then)) {
1880 throw new CasperError("withChildPage() requires a step function.");
1881 }
1882 // make the childPage the currently active one
1883 this.page = childPage;
1884 try {
1885 this.then(then);
1886 } catch (e) {
1887 // revert to main page on error
1888 this.page = this.mainPage;
1889 throw e;
1890 }
1891 return this.then(function _step() {
1892 // revert to main page
1893 this.page = this.mainPage;
1894 });
1895 };
1896
1897 /**
1842 * Changes the current page zoom factor. 1898 * Changes the current page zoom factor.
1843 * 1899 *
1844 * @param Number factor The zoom factor 1900 * @param Number factor The zoom factor
...@@ -1960,6 +2016,19 @@ function createPage(casper) { ...@@ -1960,6 +2016,19 @@ function createPage(casper) {
1960 } 2016 }
1961 casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame); 2017 casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame);
1962 }; 2018 };
2019 page.onPageCreated = function onPageCreated(newPage) {
2020 casper.emit('child.page.created', newPage);
2021 newPage.onLoadFinished = function onLoadFinished() {
2022 casper.childPages.push(newPage);
2023 casper.emit('child.page.loaded', newPage);
2024 };
2025 newPage.onClosing = function onClosing(closedPage) {
2026 casper.childPages = casper.childPages.filter(function(childPages) {
2027 return childPages.id !== closedPage.id;
2028 });
2029 casper.emit('child.page.closed', closedPage);
2030 };
2031 };
1963 page.onPrompt = function onPrompt(message, value) { 2032 page.onPrompt = function onPrompt(message, value) {
1964 return casper.filter('page.prompt', message, value); 2033 return casper.filter('page.prompt', message, value);
1965 }; 2034 };
......
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>CasperJS test child page</title>
6 </head>
7 <a href="." class="close">close</a>
8 <body>
9 <script>
10 var w = window.open("http://localhost:54321/tests/site/index.html",
11 "popup", "menubar=no, status=no, scrollbars=no, menubar=no, width=400, height=300");
12 document.querySelector('a').onclick = function onclick(evt) {
13 evt.preventDefault();
14 w.close();
15 };
16 </script>
17 </body>
18 </html>
1 /*jshint strict:false*/
2 /*global CasperError casper console phantom require*/
3 var utils = require('utils');
4 var x = require('casper').selectXPath;
5
6 casper.on('child.page.created', function(page) {
7 this.test.pass('child.page.created event is fired');
8 this.test.assert(utils.isWebPage(page),
9 'child.page.created event callback get a child WebPage instance');
10 });
11
12 casper.on('child.page.loaded', function(page) {
13 this.test.pass('child.page.loaded event is fired');
14 this.test.assertEquals(page.evaluate(function() {
15 return document.title;
16 }), 'CasperJS test index',
17 'child.page.loaded is triggered when child page contents are actually loaded');
18 });
19
20 casper.on('child.page.closed', function(page) {
21 this.test.assertEquals(this.childPages.length, 0, 'child.page.closed event is fired');
22 });
23
24 casper.start('tests/site/child-page.html');
25
26 casper.waitForPage('index.html', function() {
27 this.test.pass('Casper.waitForPage() waits for a child page being created');
28 this.test.assertEquals(this.childPages.length, 1, 'A child page has been added');
29 var childPage = this.childPages[0];
30 this.test.assert(utils.isWebPage(childPage), 'A child page is a WebPage');
31 this.withChildPage(childPage, function() {
32 this.test.assertEquals(this.page.id, childPage.id,
33 'Casper.withChildPage() switched to child page as current active one');
34 this.test.assertEval(function() {
35 return '__utils__' in window;
36 }, 'Casper.withChildPage() has client utils injected');
37 this.test.assertTitle('CasperJS test index',
38 'Casper.withChildPage() can perform evaluation on the active child page');
39 this.test.assertExists('h1',
40 'Casper.withChildPage() can perform assertions on the DOM');
41 this.test.assertExists(x('//h1'),
42 'Casper.withChildPage() can perform assertions on the DOM using XPath');
43 });
44 });
45
46 casper.thenClick('.close', function() {
47 this.test.assertEquals(this.childPages.length, 0, 'Child page is removed when closed');
48 });
49
50 casper.run(function() {
51 this.test.done(14);
52 });