Implemented a better alternative for retrieving contextualized parameters in Casper#evaluate()
Now as an alternative, CasperJS injects a `__casper_params__` Object instance containing all the parameters you passed: casper.evaluate(function() { document.querySelector('#username').value = __casper_params__.username; document.querySelector('#password').value = __casper_params__.password; document.querySelector('#submit').click(); }, { username: 'Bazoonga', password: 'baz00nga' })
Showing
2 changed files
with
136 additions
and
58 deletions
... | @@ -97,7 +97,7 @@ | ... | @@ -97,7 +97,7 @@ |
97 | */ | 97 | */ |
98 | base64encode: function(url) { | 98 | base64encode: function(url) { |
99 | return this.evaluate(function() { | 99 | return this.evaluate(function() { |
100 | return __utils__.getBase64('%url%'); | 100 | return __utils__.getBase64(__casper_params__.url); |
101 | }, { | 101 | }, { |
102 | url: url | 102 | url: url |
103 | }); | 103 | }); |
... | @@ -134,18 +134,18 @@ | ... | @@ -134,18 +134,18 @@ |
134 | captureSelector: function(targetFile, selector) { | 134 | captureSelector: function(targetFile, selector) { |
135 | return this.capture(targetFile, this.evaluate(function() { | 135 | return this.capture(targetFile, this.evaluate(function() { |
136 | try { | 136 | try { |
137 | var clipRect = document.querySelector('%selector%').getBoundingClientRect(); | 137 | var clipRect = document.querySelector(__casper_params__.selector).getBoundingClientRect(); |
138 | return { | 138 | return { |
139 | top: clipRect.top, | 139 | top: clipRect.top, |
140 | left: clipRect.left, | 140 | left: clipRect.left, |
141 | width: clipRect.width, | 141 | width: clipRect.width, |
142 | height: clipRect.height | 142 | height: clipRect.height |
143 | }; | 143 | }; |
144 | } catch (e) { | 144 | } catch (e) { |
145 | __utils__.log('unable to fetch bounds for element ' + selector, 'warning'); | 145 | __utils__.log("Unable to fetch bounds for element " + __casper_params__.selector, "warning"); |
146 | } | 146 | } |
147 | }, { | 147 | }, { |
148 | selector: selector.replace("'", "\'") | 148 | selector: selector |
149 | })); | 149 | })); |
150 | }, | 150 | }, |
151 | 151 | ||
... | @@ -197,15 +197,18 @@ | ... | @@ -197,15 +197,18 @@ |
197 | * Emulates a click on the element from the provided selector, if | 197 | * Emulates a click on the element from the provided selector, if |
198 | * possible. In case of success, `true` is returned. | 198 | * possible. In case of success, `true` is returned. |
199 | * | 199 | * |
200 | * @param String selector A DOM CSS3 compatible selector | 200 | * @param String selector A DOM CSS3 compatible selector |
201 | * @param Boolean fallbackToHref Whether to try to relocate to the value of any href attribute (default: true) | ||
201 | * @return Boolean | 202 | * @return Boolean |
202 | */ | 203 | */ |
203 | click: function(selector) { | 204 | click: function(selector, fallbackToHref) { |
205 | fallbackToHref = typeof(fallbackToHref) == "undefined" ? true : false; | ||
204 | this.log("click on selector: " + selector, "debug"); | 206 | this.log("click on selector: " + selector, "debug"); |
205 | return this.evaluate(function() { | 207 | return this.evaluate(function() { |
206 | return __utils__.click('%selector%'); | 208 | return __utils__.click(__casper_params__.selector, __casper_params__.fallbackToHref); |
207 | }, { | 209 | }, { |
208 | selector: selector.replace("'", "\'") | 210 | selector: selector, |
211 | fallbackToHref: fallbackToHref | ||
209 | }); | 212 | }); |
210 | }, | 213 | }, |
211 | 214 | ||
... | @@ -297,16 +300,40 @@ | ... | @@ -297,16 +300,40 @@ |
297 | * password: 'baz00nga' | 300 | * password: 'baz00nga' |
298 | * }) | 301 | * }) |
299 | * | 302 | * |
303 | * As an alternative, CasperJS injects a `__casper_params__` Object | ||
304 | * instance containing all the parameters you passed: | ||
305 | * | ||
306 | * casper.evaluate(function() { | ||
307 | * document.querySelector('#username').value = __casper_params__.username; | ||
308 | * document.querySelector('#password').value = __casper_params__.password; | ||
309 | * document.querySelector('#submit').click(); | ||
310 | * }, { | ||
311 | * username: 'Bazoonga', | ||
312 | * password: 'baz00nga' | ||
313 | * }) | ||
314 | * | ||
300 | * FIXME: waiting for a patch of PhantomJS to allow direct passing of | 315 | * FIXME: waiting for a patch of PhantomJS to allow direct passing of |
301 | * arguments to the function. | 316 | * arguments to the function. |
302 | * TODO: don't forget to keep this backward compatible. | 317 | * TODO: don't forget to keep this backward compatible. |
303 | * | 318 | * |
304 | * @param function fn The function to be evaluated within current page DOM | 319 | * @param function fn The function to be evaluated within current page DOM |
305 | * @param object replacements Optional replacements to performs, eg. for '%foo%' => {foo: 'bar'} | 320 | * @param object replacements Parameters to pass to the remote environment |
306 | * @return mixed | 321 | * @return mixed |
307 | * @see WebPage#evaluate | 322 | * @see WebPage#evaluate |
308 | */ | 323 | */ |
309 | evaluate: function(fn, replacements) { | 324 | evaluate: function(fn, replacements) { |
325 | replacements = typeof(replacements) === "object" ? replacements : {}; | ||
326 | this.page.evaluate(replaceFunctionPlaceholders(function() { | ||
327 | window.__casper_params__ = {}; | ||
328 | try { | ||
329 | var jsonString = unescape(decodeURIComponent('%replacements%')); | ||
330 | window.__casper_params__ = JSON.parse(jsonString); | ||
331 | } catch (e) { | ||
332 | __utils__.log("Unable to replace parameters: " + e, "error"); | ||
333 | } | ||
334 | }, { | ||
335 | replacements: encodeURIComponent(escape(JSON.stringify(replacements).replace("'", "\'"))) | ||
336 | })); | ||
310 | return this.page.evaluate(replaceFunctionPlaceholders(fn, replacements)); | 337 | return this.page.evaluate(replaceFunctionPlaceholders(fn, replacements)); |
311 | }, | 338 | }, |
312 | 339 | ||
... | @@ -314,8 +341,8 @@ | ... | @@ -314,8 +341,8 @@ |
314 | * Evaluates an expression within the current page DOM and die() if it | 341 | * Evaluates an expression within the current page DOM and die() if it |
315 | * returns false. | 342 | * returns false. |
316 | * | 343 | * |
317 | * @param function fn Expression to evaluate | 344 | * @param function fn The expression to evaluate |
318 | * @param String message Error message to log | 345 | * @param String message The error message to log |
319 | * @return Casper | 346 | * @return Casper |
320 | */ | 347 | */ |
321 | evaluateOrDie: function(fn, message) { | 348 | evaluateOrDie: function(fn, message) { |
... | @@ -334,7 +361,7 @@ | ... | @@ -334,7 +361,7 @@ |
334 | */ | 361 | */ |
335 | exists: function(selector) { | 362 | exists: function(selector) { |
336 | return this.evaluate(function() { | 363 | return this.evaluate(function() { |
337 | return __utils__.exists('%selector%'); | 364 | return __utils__.exists(__casper_params__.selector); |
338 | }, { | 365 | }, { |
339 | selector: selector | 366 | selector: selector |
340 | }); | 367 | }); |
... | @@ -360,9 +387,9 @@ | ... | @@ -360,9 +387,9 @@ |
360 | */ | 387 | */ |
361 | fetchText: function(selector) { | 388 | fetchText: function(selector) { |
362 | return this.evaluate(function() { | 389 | return this.evaluate(function() { |
363 | return __utils__.fetchText('%selector%'); | 390 | return __utils__.fetchText(__casper_params__.selector); |
364 | }, { | 391 | }, { |
365 | selector: selector.replace("'", "\'") | 392 | selector: selector |
366 | }); | 393 | }); |
367 | }, | 394 | }, |
368 | 395 | ||
... | @@ -382,10 +409,10 @@ | ... | @@ -382,10 +409,10 @@ |
382 | throw "form values must be provided as an object"; | 409 | throw "form values must be provided as an object"; |
383 | } | 410 | } |
384 | var fillResults = this.evaluate(function() { | 411 | var fillResults = this.evaluate(function() { |
385 | return __utils__.fill('%selector%', JSON.parse('%values%')); | 412 | return __utils__.fill(__casper_params__.selector, __casper_params__.values); |
386 | }, { | 413 | }, { |
387 | selector: selector.replace("'", "\'"), | 414 | selector: selector, |
388 | values: JSON.stringify(vals).replace("'", "\'") | 415 | values: vals |
389 | }); | 416 | }); |
390 | if (!fillResults) { | 417 | if (!fillResults) { |
391 | throw "unable to fill form"; | 418 | throw "unable to fill form"; |
... | @@ -412,13 +439,13 @@ | ... | @@ -412,13 +439,13 @@ |
412 | // Form submission? | 439 | // Form submission? |
413 | if (submit) { | 440 | if (submit) { |
414 | this.evaluate(function() { | 441 | this.evaluate(function() { |
415 | var form = document.querySelector('%selector%'); | 442 | var form = document.querySelector(__casper_params__.selector); |
416 | var method = form.getAttribute('method').toUpperCase() || "GET"; | 443 | var method = form.getAttribute('method').toUpperCase() || "GET"; |
417 | var action = form.getAttribute('action') || "unknown"; | 444 | var action = form.getAttribute('action') || "unknown"; |
418 | __utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); | 445 | __utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); |
419 | form.submit(); | 446 | form.submit(); |
420 | }, { | 447 | }, { |
421 | selector: selector.replace("'", "\'") | 448 | selector: selector |
422 | }); | 449 | }); |
423 | } | 450 | } |
424 | }, | 451 | }, |
... | @@ -634,8 +661,8 @@ | ... | @@ -634,8 +661,8 @@ |
634 | }, | 661 | }, |
635 | 662 | ||
636 | /** | 663 | /** |
637 | * Waits a given amount of time (expressed in milliseconds) before | 664 | * Adds a new step that will wait for a given amount of time (expressed |
638 | * processing next step, which can passed as an optional argument. | 665 | * in milliseconds) before processing an optional next one. |
639 | * | 666 | * |
640 | * @param Number timeout The max amount of time to wait, in milliseconds | 667 | * @param Number timeout The max amount of time to wait, in milliseconds |
641 | * @param Function then Next step to process (optional) | 668 | * @param Function then Next step to process (optional) |
... | @@ -648,19 +675,20 @@ | ... | @@ -648,19 +675,20 @@ |
648 | if (then && typeof(then) !== "function") { | 675 | if (then && typeof(then) !== "function") { |
649 | this.die("wait() a step definition must be a function"); | 676 | this.die("wait() a step definition must be a function"); |
650 | } | 677 | } |
651 | this.delayedExecution = true; | 678 | return this.then(function(self) { |
652 | var start = new Date().getTime(); | 679 | self.delayedExecution = true; |
653 | var interval = setInterval(function(self, then) { | 680 | var start = new Date().getTime(); |
654 | if (new Date().getTime() - start > timeout) { | 681 | var interval = setInterval(function(self, then) { |
655 | self.delayedExecution = false; | 682 | if (new Date().getTime() - start > timeout) { |
656 | self.log("wait() finished wating for " + timeout + "ms.", "info"); | 683 | self.delayedExecution = false; |
657 | if (then) { | 684 | self.log("wait() finished wating for " + timeout + "ms.", "info"); |
658 | self.then(then); | 685 | if (then) { |
686 | self.then(then); | ||
687 | } | ||
688 | clearInterval(interval); | ||
659 | } | 689 | } |
660 | clearInterval(interval); | 690 | }, 100, self, then); |
661 | } | 691 | }); |
662 | }, 100, this, then); | ||
663 | return this; | ||
664 | }, | 692 | }, |
665 | 693 | ||
666 | /** | 694 | /** |
... | @@ -745,10 +773,12 @@ | ... | @@ -745,10 +773,12 @@ |
745 | /** | 773 | /** |
746 | * Clicks on the DOM element behind the provided selector. | 774 | * Clicks on the DOM element behind the provided selector. |
747 | * | 775 | * |
748 | * @param String selector A CSS3 selector to the element to click | 776 | * @param String selector A CSS3 selector to the element to click |
777 | * @param Boolean fallbackToHref Whether to try to relocate to the value of any href attribute (default: true) | ||
749 | * @return Boolean | 778 | * @return Boolean |
750 | */ | 779 | */ |
751 | this.click = function(selector) { | 780 | this.click = function(selector, fallbackToHref) { |
781 | fallbackToHref = typeof(fallbackToHref) == "undefined" ? true : false; | ||
752 | var elem = this.findOne(selector); | 782 | var elem = this.findOne(selector); |
753 | if (!elem) { | 783 | if (!elem) { |
754 | return false; | 784 | return false; |
... | @@ -758,7 +788,7 @@ | ... | @@ -758,7 +788,7 @@ |
758 | if (elem.dispatchEvent(evt)) { | 788 | if (elem.dispatchEvent(evt)) { |
759 | return true; | 789 | return true; |
760 | } | 790 | } |
761 | if (elem.hasAttribute('href')) { | 791 | if (fallbackToHref && elem.hasAttribute('href')) { |
762 | document.location = elem.getAttribute('href'); | 792 | document.location = elem.getAttribute('href'); |
763 | return true; | 793 | return true; |
764 | } | 794 | } |
... | @@ -1226,6 +1256,17 @@ | ... | @@ -1226,6 +1256,17 @@ |
1226 | }; | 1256 | }; |
1227 | 1257 | ||
1228 | /** | 1258 | /** |
1259 | * Asserts that the provided input is of the given type. | ||
1260 | * | ||
1261 | * @param mixed input The value to test | ||
1262 | * @param String type The javascript type name | ||
1263 | * @param String message Test description | ||
1264 | */ | ||
1265 | this.assertType = function(input, type, message) { | ||
1266 | return this.assertEquals(betterTypeOf(input), type, message); | ||
1267 | }; | ||
1268 | |||
1269 | /** | ||
1229 | * Asserts that a the current page url matches the provided RegExp | 1270 | * Asserts that a the current page url matches the provided RegExp |
1230 | * pattern. | 1271 | * pattern. |
1231 | * | 1272 | * |
... | @@ -1403,6 +1444,21 @@ | ... | @@ -1403,6 +1444,21 @@ |
1403 | }; | 1444 | }; |
1404 | 1445 | ||
1405 | /** | 1446 | /** |
1447 | * Provides a better typeof operator equivalent | ||
1448 | * | ||
1449 | * @param mixed input | ||
1450 | * @return String | ||
1451 | * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ | ||
1452 | */ | ||
1453 | function betterTypeOf(input) { | ||
1454 | try { | ||
1455 | return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase(); | ||
1456 | } catch (e) { | ||
1457 | return typeof input; | ||
1458 | } | ||
1459 | } | ||
1460 | |||
1461 | /** | ||
1406 | * Creates a new WebPage instance for Casper use. | 1462 | * Creates a new WebPage instance for Casper use. |
1407 | * | 1463 | * |
1408 | * @param Casper casper A Casper instance | 1464 | * @param Casper casper A Casper instance |
... | @@ -1522,10 +1578,10 @@ | ... | @@ -1522,10 +1578,10 @@ |
1522 | function replaceFunctionPlaceholders(fn, replacements) { | 1578 | function replaceFunctionPlaceholders(fn, replacements) { |
1523 | if (replacements && typeof replacements === "object") { | 1579 | if (replacements && typeof replacements === "object") { |
1524 | fn = fn.toString(); | 1580 | fn = fn.toString(); |
1525 | for (var p in replacements) { | 1581 | for (var placeholder in replacements) { |
1526 | var match = '%' + p + '%'; | 1582 | var match = '%' + placeholder + '%'; |
1527 | do { | 1583 | do { |
1528 | fn = fn.replace(match, replacements[p]); | 1584 | fn = fn.replace(match, replacements[placeholder]); |
1529 | } while(fn.indexOf(match) !== -1); | 1585 | } while(fn.indexOf(match) !== -1); |
1530 | } | 1586 | } |
1531 | } | 1587 | } | ... | ... |
... | @@ -57,6 +57,28 @@ casper.then(function(self) { | ... | @@ -57,6 +57,28 @@ casper.then(function(self) { |
57 | 57 | ||
58 | casper.test.assert(casper.steps.length === 2, 'then() adds a new navigation step'); | 58 | casper.test.assert(casper.steps.length === 2, 'then() adds a new navigation step'); |
59 | 59 | ||
60 | // Casper#evaluate() | ||
61 | |||
62 | casper.then(function(self) { | ||
63 | var params = { | ||
64 | "boolean true": true, | ||
65 | "boolean false": false, | ||
66 | "int number": 42, | ||
67 | "float number": 1337.42, | ||
68 | "string": "plop! \"Ÿ£$\" 'no'", | ||
69 | "array": [1, 2, 3], | ||
70 | "object": {a: 1, b: 2} | ||
71 | }; | ||
72 | var casperParams = self.evaluate(function() { | ||
73 | return __casper_params__; | ||
74 | }, params); | ||
75 | self.test.assertType(casperParams, "object", 'Casper.evaluate() exposes parameters in a dedicated object'); | ||
76 | self.test.assertEquals(Object.keys(casperParams).length, 7, 'Casper.evaluate() exposes parameters object has the correct length'); | ||
77 | for (var param in casperParams) { | ||
78 | self.test.assertEquals(JSON.stringify(casperParams[param]), JSON.stringify(params[param]), 'Casper.evaluate() can pass a ' + param); | ||
79 | } | ||
80 | }); | ||
81 | |||
60 | // Casper#fill() | 82 | // Casper#fill() |
61 | casper.then(function(self) { | 83 | casper.then(function(self) { |
62 | self.test.assertTitle('CasperJS test form', 'click() casper can click on a text link and react when it is loaded 2/2'); | 84 | self.test.assertTitle('CasperJS test form', 'click() casper can click on a text link and react when it is loaded 2/2'); |
... | @@ -116,7 +138,7 @@ casper.test.assertMatch(xunit.getXML(), /<testcase classname="foo" name="bar"/, | ... | @@ -116,7 +138,7 @@ casper.test.assertMatch(xunit.getXML(), /<testcase classname="foo" name="bar"/, |
116 | xunit.addFailure('bar', 'baz', 'wrong', 'chucknorriz'); | 138 | xunit.addFailure('bar', 'baz', 'wrong', 'chucknorriz'); |
117 | casper.test.assertMatch(xunit.getXML(), /<testcase classname="bar" name="baz"><failure type="chucknorriz">wrong/, 'addFailure() adds a failed testcase'); | 139 | casper.test.assertMatch(xunit.getXML(), /<testcase classname="bar" name="baz"><failure type="chucknorriz">wrong/, 'addFailure() adds a failed testcase'); |
118 | 140 | ||
119 | // Casper.ClientUtils.log | 141 | // Casper.ClientUtils.log() |
120 | casper.then(function(self) { | 142 | casper.then(function(self) { |
121 | casper.test.comment('client utils log'); | 143 | casper.test.comment('client utils log'); |
122 | var oldLevel = casper.options.logLevel; | 144 | var oldLevel = casper.options.logLevel; |
... | @@ -136,30 +158,30 @@ casper.then(function(self) { | ... | @@ -136,30 +158,30 @@ casper.then(function(self) { |
136 | casper.options.verbose = true; | 158 | casper.options.verbose = true; |
137 | }); | 159 | }); |
138 | 160 | ||
139 | // Casper.wait | 161 | // Casper.wait() |
140 | var start = new Date().getTime(); | 162 | var start = new Date().getTime(); |
141 | casper.wait(1000); | 163 | casper.wait(1000, function(self) { |
142 | casper.then(function(self) { | 164 | self.test.comment('wait'); |
143 | casper.test.comment('wait'); | ||
144 | self.test.assert(new Date().getTime() - start > 1000, 'Casper.wait() can wait for a given amount of time'); | 165 | self.test.assert(new Date().getTime() - start > 1000, 'Casper.wait() can wait for a given amount of time'); |
145 | }); | ||
146 | 166 | ||
147 | // Casper.waitFor | 167 | // Casper.waitFor() |
148 | casper.thenOpen('tests/site/waitFor.html', function(self) { | 168 | casper.thenOpen('tests/site/waitFor.html', function(self) { |
149 | casper.test.comment('waitFor'); | 169 | casper.test.comment('waitFor'); |
150 | self.waitFor(function(self) { | 170 | self.waitFor(function(self) { |
151 | return self.evaluate(function() { | 171 | return self.evaluate(function() { |
152 | return document.querySelectorAll('li').length === 4; | 172 | return document.querySelectorAll('li').length === 4; |
173 | }); | ||
174 | }, function(self) { | ||
175 | self.test.pass('Casper.waitFor() can wait for something to happen'); | ||
176 | }, function(self) { | ||
177 | self.test.fail('Casper.waitFor() can wait for something to happen'); | ||
153 | }); | 178 | }); |
154 | }, function(self) { | ||
155 | self.test.pass('Casper.waitFor() can wait for something to happen'); | ||
156 | }, function(self) { | ||
157 | self.test.fail('Casper.waitFor() can wait for something to happen'); | ||
158 | }); | 179 | }); |
159 | }); | 180 | }); |
160 | 181 | ||
161 | // run suite | 182 | // run suite |
162 | casper.run(function(self) { | 183 | casper.run(function(self) { |
184 | self.test.comment('logging, again'); | ||
163 | self.test.assertEquals(self.result.log.length, 3, 'log() logged messages'); | 185 | self.test.assertEquals(self.result.log.length, 3, 'log() logged messages'); |
164 | self.test.renderResults(true, 0, save); | 186 | self.test.renderResults(true, 0, save); |
165 | }); | 187 | }); | ... | ... |
-
Please register or sign in to post a comment