Commit b962f381 b962f3811817fb325c7f8021360f96613ef8d521 by Nicolas Perriault

Casper#fill() can now fill input[type=file] fields; also added a new 'faultToler…

…ant' option to the Casper class
1 parent 1e8f2c59
...@@ -146,10 +146,12 @@ Casper constructor accepts a single `options` argument which is an object. Avail ...@@ -146,10 +146,12 @@ Casper constructor accepts a single `options` argument which is an object. Avail
146 146
147 ``` 147 ```
148 Name | Type | Default | Description 148 Name | Type | Default | Description
149 ——————————————————+——————————+—————————+———————————————————————————————————————————————————————————————————— 149 ——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————————
150 clientScripts | Array | [] | A collection of script filepaths to include to every page loaded 150 clientScripts | Array | [] | A collection of script filepaths to include to every page loaded
151 faultTolerant | Boolean | true | Catch and log exceptions when executing steps in a non-blocking fashion
151 logLevel | String | "error" | Logging level (see logLevels for available values) 152 logLevel | String | "error" | Logging level (see logLevels for available values)
152 onDie | function | null | A function to be called when Casper#die() is called 153 onDie | function | null | A function to be called when Casper#die() is called
154 onError | function | null | A function to be called when an "error" level event occurs
153 onPageInitialized | function | null | A function to be called after WebPage instance has been initialized 155 onPageInitialized | function | null | A function to be called after WebPage instance has been initialized
154 page | WebPage | null | An existing WebPage instance 156 page | WebPage | null | An existing WebPage instance
155 pageSettings | Object | {} | PhantomJS's WebPage settings object 157 pageSettings | Object | {} | PhantomJS's WebPage settings object
......
...@@ -27,8 +27,9 @@ ...@@ -27,8 +27,9 @@
27 * Main Casper class. Available options are: 27 * Main Casper class. Available options are:
28 * 28 *
29 * Name | Type | Default | Description 29 * Name | Type | Default | Description
30 * ——————————————————+——————————+—————————+———————————————————————————————————————————————————————————————————— 30 * ——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————————
31 * clientScripts | Array | [] | A collection of script filepaths to include to every page loaded 31 * clientScripts | Array | [] | A collection of script filepaths to include to every page loaded
32 * faultTolerant | Boolean | true | Catch and log exceptions when executing steps in a non-blocking fashion
32 * logLevel | String | "error" | Logging level (see logLevels for available values) 33 * logLevel | String | "error" | Logging level (see logLevels for available values)
33 * onDie | function | null | A function to be called when Casper#die() is called 34 * onDie | function | null | A function to be called when Casper#die() is called
34 * onError | function | null | A function to be called when an "error" level event occurs 35 * onError | function | null | A function to be called when an "error" level event occurs
...@@ -51,6 +52,7 @@ ...@@ -51,6 +52,7 @@
51 // default options 52 // default options
52 this.defaults = { 53 this.defaults = {
53 clientScripts: [], 54 clientScripts: [],
55 faultTolerant: true,
54 logLevel: "error", 56 logLevel: "error",
55 onDie: null, 57 onDie: null,
56 onError: null, 58 onError: null,
...@@ -151,10 +153,14 @@ ...@@ -151,10 +153,14 @@
151 self.log(stepInfo + self.page.evaluate(function() { 153 self.log(stepInfo + self.page.evaluate(function() {
152 return document.location.href; 154 return document.location.href;
153 }) + ' (HTTP ' + self.currentHTTPStatus + ')', "info"); 155 }) + ' (HTTP ' + self.currentHTTPStatus + ')', "info");
156 if (self.options.faultTolerant) {
154 try { 157 try {
155 step(self); 158 step(self);
156 } catch (e) { 159 } catch (e) {
157 self.log("Fatal: " + e, "error"); 160 self.log("Step error: " + e, "error");
161 }
162 } else {
163 step(self);
158 } 164 }
159 var time = new Date().getTime() - self.startTime; 165 var time = new Date().getTime() - self.startTime;
160 self.log(stepInfo + "done in " + time + "ms.", "info"); 166 self.log(stepInfo + "done in " + time + "ms.", "info");
...@@ -195,7 +201,7 @@ ...@@ -195,7 +201,7 @@
195 * @return Casper 201 * @return Casper
196 */ 202 */
197 debugHTML: function() { 203 debugHTML: function() {
198 this.echo(this.page.evaluate(function() { 204 this.echo(this.evaluate(function() {
199 return document.body.innerHTML; 205 return document.body.innerHTML;
200 })); 206 }));
201 return this; 207 return this;
...@@ -207,7 +213,7 @@ ...@@ -207,7 +213,7 @@
207 * @return Casper 213 * @return Casper
208 */ 214 */
209 debugPage: function() { 215 debugPage: function() {
210 this.echo(this.page.evaluate(function() { 216 this.echo(this.evaluate(function() {
211 return document.body.innerText; 217 return document.body.innerText;
212 })); 218 }));
213 return this; 219 return this;
...@@ -299,20 +305,42 @@ ...@@ -299,20 +305,42 @@
299 * Fills a form with provided field values. 305 * Fills a form with provided field values.
300 * 306 *
301 * @param String selector A CSS3 selector to the target form to fill 307 * @param String selector A CSS3 selector to the target form to fill
302 * @param Object values Field values 308 * @param Object vals Field values
303 * @param Boolean submit Submit the form? 309 * @param Boolean submit Submit the form?
304 */ 310 */
305 fill: function(selector, values, submit) { 311 fill: function(selector, vals, submit) {
306 if (!typeof(values) === "object") { 312 if (!typeof(vals) === "object") {
307 throw "form values must be an object"; 313 throw "form values must be an object";
308 } 314 }
309 return this.evaluate(function() { 315 var fillResults = this.evaluate(function() {
310 __utils__.fill('%selector%', JSON.parse('%values%'), JSON.parse('%submit%')); 316 return __utils__.fill('%selector%', JSON.parse('%values%'));
311 }, { 317 }, {
312 selector: selector.replace("'", "\'"), 318 selector: selector.replace("'", "\'"),
313 values: JSON.stringify(values).replace("'", "\'"), 319 values: JSON.stringify(vals).replace("'", "\'"),
314 submit: JSON.stringify(submit||false) 320 });
321 if (!fillResults) {
322 throw "unable to fill form";
323 }
324 // File uploads
325 if (fillResults.files && fillResults.files.length > 0) {
326 (function(self) {
327 fillResults.files.forEach(function(file) {
328 var fileFieldSelector = [selector, 'input[name="' + file.name + '"]'].join(' ');
329 self.page.uploadFile(fileFieldSelector, file.path);
315 }); 330 });
331 })(this);
332 }
333 // Form submission?
334 if (submit === true) {
335 self.evaluate(function() {
336 var form = document.querySelector('%selector%');
337 console.log('submitting form to ' + (form.getAttribute('action') || "unknown")
338 + ', HTTP ' + (form.getAttribute('method').toUpperCase() || "GET"));
339 form.submit();
340 }, {
341 selector: selector.replace("'", "\'"),
342 });
343 }
316 }, 344 },
317 345
318 /** 346 /**
...@@ -602,15 +630,19 @@ ...@@ -602,15 +630,19 @@
602 * 630 *
603 * @param HTMLElement|String form A form element, or a CSS3 selector to a form element 631 * @param HTMLElement|String form A form element, or a CSS3 selector to a form element
604 * @param Object vals Field values 632 * @param Object vals Field values
633 * @return Object An object containing setting result for each field, including file uploads
605 */ 634 */
606 this.fill = function(form, vals, submit) { 635 this.fill = function(form, vals) {
607 submit = submit || false; 636 var out = {
637 fields: [],
638 files: [],
639 };
608 if (!(form instanceof HTMLElement) || typeof(form) === "string") { 640 if (!(form instanceof HTMLElement) || typeof(form) === "string") {
609 form = document.querySelector(form); 641 form = document.querySelector(form);
610 } 642 }
611 if (!form) { 643 if (!form) {
612 console.log('form not found or invalid selector provided:'); 644 console.log('form not found or invalid selector provided:');
613 return; 645 return out;
614 } 646 }
615 for (var name in vals) { 647 for (var name in vals) {
616 if (!vals.hasOwnProperty(name)) { 648 if (!vals.hasOwnProperty(name)) {
...@@ -622,13 +654,20 @@ ...@@ -622,13 +654,20 @@
622 console.log('no field named "' + name + '" in form'); 654 console.log('no field named "' + name + '" in form');
623 continue; 655 continue;
624 } 656 }
625 this.setField(field, value); 657 try {
658 out.fields[name] = this.setField(field, value);
659 } catch (e) {
660 if (e.name === "FileUploadError") {
661 out.files.push({
662 name: name,
663 path: e.path,
664 });
665 } else {
666 throw e;
667 }
626 } 668 }
627 if (submit) {
628 console.log('submitting form to ' + (form.getAttribute('action') || "unknown")
629 + ', HTTP ' + (form.getAttribute('method').toUpperCase() || "GET"));
630 form.submit();
631 } 669 }
670 return out;
632 }; 671 };
633 672
634 /** 673 /**
...@@ -664,7 +703,7 @@ ...@@ -664,7 +703,7 @@
664 * @param mixed value The field value to set 703 * @param mixed value The field value to set
665 */ 704 */
666 this.setField = function(field, value) { 705 this.setField = function(field, value) {
667 var fields; 706 var fields, out;
668 value = value || ""; 707 value = value || "";
669 if (field instanceof NodeList) { 708 if (field instanceof NodeList) {
670 fields = field; 709 fields = field;
...@@ -673,7 +712,7 @@ ...@@ -673,7 +712,7 @@
673 if (!field instanceof HTMLElement) { 712 if (!field instanceof HTMLElement) {
674 console.log('invalid field type; only HTMLElement and NodeList are supported'); 713 console.log('invalid field type; only HTMLElement and NodeList are supported');
675 } 714 }
676 console.log('set "' + field.getAttribute('name') + '" value to ' + value); 715 console.log('set "' + field.getAttribute('name') + '" field value to ' + value);
677 var nodeName = field.nodeName.toLowerCase(); 716 var nodeName = field.nodeName.toLowerCase();
678 switch (nodeName) { 717 switch (nodeName) {
679 case "input": 718 case "input":
...@@ -701,7 +740,11 @@ ...@@ -701,7 +740,11 @@
701 field.setAttribute('checked', value ? "checked" : ""); 740 field.setAttribute('checked', value ? "checked" : "");
702 break; 741 break;
703 case "file": 742 case "file":
704 console.log("file field filling is not supported"); 743 throw {
744 name: "FileUploadError",
745 message: "file field must be filled using page.uploadFile",
746 path: value
747 };
705 break; 748 break;
706 case "radio": 749 case "radio":
707 if (fields) { 750 if (fields) {
...@@ -709,11 +752,11 @@ ...@@ -709,11 +752,11 @@
709 e.checked = (e.value === value); 752 e.checked = (e.value === value);
710 }); 753 });
711 } else { 754 } else {
712 console.log('radio elements are empty'); 755 out = 'provided radio elements are empty';
713 } 756 }
714 break; 757 break;
715 default: 758 default:
716 console.log("unsupported input field type: " + type); 759 out = "unsupported input field type: " + type;
717 break; 760 break;
718 } 761 }
719 break; 762 break;
...@@ -722,10 +765,11 @@ ...@@ -722,10 +765,11 @@
722 field.value = value; 765 field.value = value;
723 break; 766 break;
724 default: 767 default:
725 console.log('unsupported field type: ' + nodeName); 768 out = 'unsupported field type: ' + nodeName;
726 break; 769 break;
727 } 770 }
728 } 771 return out;
772 };
729 }; 773 };
730 774
731 /** 775 /**
......
...@@ -2,9 +2,17 @@ phantom.injectJs('casper.js'); ...@@ -2,9 +2,17 @@ phantom.injectJs('casper.js');
2 phantom.injectJs('tests/assert.js'); 2 phantom.injectJs('tests/assert.js');
3 3
4 var casper = new phantom.Casper({ 4 var casper = new phantom.Casper({
5 faultTolerant: false,
5 verbose: true, 6 verbose: true,
6 }); 7 });
7 8
9 phantom.args.forEach(function(arg) {
10 var debugMatch = /--loglevel=(\w+)/.exec(arg);
11 if (debugMatch) {
12 casper.options.logLevel = debugMatch[1];
13 }
14 });
15
8 casper.start('tests/site/index.html', function(self) { 16 casper.start('tests/site/index.html', function(self) {
9 self.assertEvalEquals(function() { 17 self.assertEvalEquals(function() {
10 return document.title; 18 return document.title;
...@@ -25,10 +33,11 @@ casper.assert(casper.steps.length === 2, 'then() adds a new navigation step'); ...@@ -25,10 +33,11 @@ casper.assert(casper.steps.length === 2, 'then() adds a new navigation step');
25 33
26 casper.then(function(self) { 34 casper.then(function(self) {
27 self.fill('form[action="form.html"]', { 35 self.fill('form[action="form.html"]', {
28 'email': 'chuck@norris.com', 36 email: 'chuck@norris.com',
29 'content': 'Am watching thou', 37 content: 'Am watching thou',
30 'check': true, 38 check: true,
31 'choice': 'no' 39 choice: 'no',
40 file: phantom.libraryPath + '/README.md'
32 }); 41 });
33 self.assertEvalEquals(function() { 42 self.assertEvalEquals(function() {
34 return document.querySelector('input[name="email"]').value; 43 return document.querySelector('input[name="email"]').value;
...@@ -42,6 +51,15 @@ casper.then(function(self) { ...@@ -42,6 +51,15 @@ casper.then(function(self) {
42 self.assertEvalEquals(function() { 51 self.assertEvalEquals(function() {
43 return document.querySelector('input[name="choice"][value="no"]').checked; 52 return document.querySelector('input[name="choice"][value="no"]').checked;
44 }, true, 'fill() can check a form radio button'); 53 }, true, 'fill() can check a form radio button');
54 self.assertEvalEquals(function() {
55 return document.querySelector('input[name="choice"][value="no"]').checked;
56 }, true, 'fill() can check a form radio button');
57 self.assertEvalEquals(function() {
58 return document.querySelector('input[name="file"]').files.length === 1;
59 }, true, 'fill() can select a file to upload');
60 self.evaluate(function() {
61 document.querySelector('form[action="form.html"]').submit();
62 })
45 }); 63 });
46 64
47 casper.run(function(self) { 65 casper.run(function(self) {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 <title>CasperJS test form</title> 5 <title>CasperJS test form</title>
6 </head> 6 </head>
7 <body> 7 <body>
8 <form action="form.html"> 8 <form action="form.html" enctype="multipart/form-data">
9 <input type="text" name="email" placeholder="email" /> 9 <input type="text" name="email" placeholder="email" />
10 <textarea name="content"></textarea> 10 <textarea name="content"></textarea>
11 <select name="topic"> 11 <select name="topic">
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 <input type="checkbox" name="check" /> 15 <input type="checkbox" name="check" />
16 <input type="radio" name="choice" value="yes"/> 16 <input type="radio" name="choice" value="yes"/>
17 <input type="radio" name="choice" value="no"/> 17 <input type="radio" name="choice" value="no"/>
18 <input type="file" name="file"/>
18 <input type="submit"/> 19 <input type="submit"/>
19 </form> 20 </form>
20 </body> 21 </body>
......