Casper#fill() can now fill input[type=file] fields; also added a new 'faultToler…
…ant' option to the Casper class
Showing
5 changed files
with
97 additions
and
32 deletions
... | @@ -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> | ... | ... |
-
Please register or sign in to post a comment