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'); }); ```
Showing
5 changed files
with
164 additions
and
2 deletions
... | @@ -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. | ... | ... |
... | @@ -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 | }; | ... | ... |
tests/site/child-page.html
0 → 100644
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> |
tests/suites/casper/child-page.js
0 → 100644
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 | }); |
-
Please register or sign in to post a comment