Casper.open() can now perform POST, PUT, HEAD and DELETE HTTP requests
Showing
7 changed files
with
317 additions
and
24 deletions
... | @@ -32,6 +32,7 @@ var colorizer = require('colorizer'); | ... | @@ -32,6 +32,7 @@ var colorizer = require('colorizer'); |
32 | var events = require('events'); | 32 | var events = require('events'); |
33 | var fs = require('fs'); | 33 | var fs = require('fs'); |
34 | var mouse = require('mouse'); | 34 | var mouse = require('mouse'); |
35 | var qs = require('querystring'); | ||
35 | var tester = require('tester'); | 36 | var tester = require('tester'); |
36 | var utils = require('utils'); | 37 | var utils = require('utils'); |
37 | var f = utils.format; | 38 | var f = utils.format; |
... | @@ -645,27 +646,58 @@ Casper.prototype.mouseClick = function(selector) { | ... | @@ -645,27 +646,58 @@ Casper.prototype.mouseClick = function(selector) { |
645 | }; | 646 | }; |
646 | 647 | ||
647 | /** | 648 | /** |
648 | * Opens a page. Takes only one argument, the url to open (using the | 649 | * Performs an HTTP request. |
649 | * callback argument would defeat the whole purpose of Casper | ||
650 | * actually). | ||
651 | * | 650 | * |
652 | * @param String location The url to open | 651 | * @param String location The url to open |
652 | * @param Object settings The request settings | ||
653 | * @return Casper | 653 | * @return Casper |
654 | */ | 654 | */ |
655 | Casper.prototype.open = function(location, options) { | 655 | Casper.prototype.open = function(location, settings) { |
656 | options = utils.isObject(options) ? options : {}; | 656 | var self = this; |
657 | this.requestUrl = location; | 657 | // settings validation |
658 | // http auth | 658 | if (!settings) { |
659 | var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i); | 659 | settings = { |
660 | if (httpAuthMatch) { | 660 | method: "get" |
661 | var httpAuth = { | ||
662 | username: httpAuthMatch[1], | ||
663 | password: httpAuthMatch[2] | ||
664 | }; | 661 | }; |
665 | this.setHttpAuth(httpAuth.username, httpAuth.password); | ||
666 | } | 662 | } |
667 | this.emit('open', location); | 663 | if (!utils.isObject(settings)) { |
668 | this.page.open(this.filter('open.location', location) || location); | 664 | throw new Error("open(): request settings must be an Object"); |
665 | } | ||
666 | // http method | ||
667 | // taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302 | ||
668 | var methods = ["get", "head", "put", "post", "delete"]; | ||
669 | if (settings.method && (!utils.isString(settings.method) || methods.indexOf(settings.method) === -1)) { | ||
670 | throw new Error("open(): settings.method must be part of " + methods.join(', ')); | ||
671 | } | ||
672 | // http data | ||
673 | if (settings.data) { | ||
674 | if (utils.isObject(settings.data)) { // query object | ||
675 | settings.data = qs.encode(settings.data); | ||
676 | } else if (!utils.isString(settings.data)) { | ||
677 | throw new Error("open(): invalid request settings data value: " + settings.data); | ||
678 | } | ||
679 | } | ||
680 | // current request url | ||
681 | this.requestUrl = this.filter('open.location', location) || location; | ||
682 | // http auth | ||
683 | if (settings.username && settings.password) { | ||
684 | this.setHttpAuth(settings.username, settings.password); | ||
685 | } else { | ||
686 | var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i); | ||
687 | if (httpAuthMatch) { | ||
688 | var httpAuth = { | ||
689 | username: httpAuthMatch[1], | ||
690 | password: httpAuthMatch[2] | ||
691 | }; | ||
692 | this.setHttpAuth(httpAuth.username, httpAuth.password); | ||
693 | } | ||
694 | } | ||
695 | this.emit('open', this.requestUrl, settings); | ||
696 | //this.page.open(this.requestUrl, settings.method, settings.data); | ||
697 | this.page.openUrl(this.requestUrl, { | ||
698 | operation: settings.method, | ||
699 | data: settings.data | ||
700 | }, this.page.settings); | ||
669 | return this; | 701 | return this; |
670 | }; | 702 | }; |
671 | 703 | ||
... | @@ -741,7 +773,7 @@ Casper.prototype.runStep = function(step) { | ... | @@ -741,7 +773,7 @@ Casper.prototype.runStep = function(step) { |
741 | var stepInfo = f("Step %d/%d", this.step, this.steps.length); | 773 | var stepInfo = f("Step %d/%d", this.step, this.steps.length); |
742 | var stepResult; | 774 | var stepResult; |
743 | if (!skipLog) { | 775 | if (!skipLog) { |
744 | this.log(stepInfo + f('%s (HTTP %d)', this.getCurrentUrl(), this.currentHTTPStatus), "info"); | 776 | this.log(stepInfo + f(' %s (HTTP %d)', this.getCurrentUrl(), this.currentHTTPStatus), "info"); |
745 | } | 777 | } |
746 | if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) { | 778 | if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) { |
747 | var stepTimeoutCheckInterval = setInterval(function(self, start, stepNum) { | 779 | var stepTimeoutCheckInterval = setInterval(function(self, start, stepNum) { |
... | @@ -1218,16 +1250,15 @@ function createPage(casper) { | ... | @@ -1218,16 +1250,15 @@ function createPage(casper) { |
1218 | } | 1250 | } |
1219 | if (casper.options.clientScripts) { | 1251 | if (casper.options.clientScripts) { |
1220 | if (!utils.isArray(casper.options.clientScripts)) { | 1252 | if (!utils.isArray(casper.options.clientScripts)) { |
1221 | casper.log("The clientScripts option must be an array", "error"); | 1253 | throw new Error("The clientScripts option must be an array"); |
1222 | } else { | 1254 | } else { |
1223 | for (var i = 0; i < casper.options.clientScripts.length; i++) { | 1255 | casper.options.clientScripts.forEach(function(script) { |
1224 | var script = casper.options.clientScripts[i]; | ||
1225 | if (casper.page.injectJs(script)) { | 1256 | if (casper.page.injectJs(script)) { |
1226 | casper.log(f('Automatically injected %s client side', script), "debug"); | 1257 | casper.log(f('Automatically injected %s client side', script), "debug"); |
1227 | } else { | 1258 | } else { |
1228 | casper.log(f('Failed injecting %s client side', script), "warning"); | 1259 | casper.log(f('Failed injecting %s client side', script), "warning"); |
1229 | } | 1260 | } |
1230 | } | 1261 | }); |
1231 | } | 1262 | } |
1232 | } | 1263 | } |
1233 | // Client-side utils injection | 1264 | // Client-side utils injection | ... | ... |
modules/querystring.js
0 → 100644
1 | // Copyright Joyent, Inc. and other Node contributors. | ||
2 | // | ||
3 | // Permission is hereby granted, free of charge, to any person obtaining a | ||
4 | // copy of this software and associated documentation files (the | ||
5 | // "Software"), to deal in the Software without restriction, including | ||
6 | // without limitation the rights to use, copy, modify, merge, publish, | ||
7 | // distribute, sublicense, and/or sell copies of the Software, and to permit | ||
8 | // persons to whom the Software is furnished to do so, subject to the | ||
9 | // following conditions: | ||
10 | // | ||
11 | // The above copyright notice and this permission notice shall be included | ||
12 | // in all copies or substantial portions of the Software. | ||
13 | // | ||
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | ||
17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | ||
20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | |||
22 | // Query String Utilities | ||
23 | |||
24 | var QueryString = exports; | ||
25 | //var urlDecode = process.binding('http_parser').urlDecode; // phantomjs incompatible | ||
26 | |||
27 | |||
28 | // If obj.hasOwnProperty has been overridden, then calling | ||
29 | // obj.hasOwnProperty(prop) will break. | ||
30 | // See: https://github.com/joyent/node/issues/1707 | ||
31 | function hasOwnProperty(obj, prop) { | ||
32 | return Object.prototype.hasOwnProperty.call(obj, prop); | ||
33 | } | ||
34 | |||
35 | |||
36 | function charCode(c) { | ||
37 | return c.charCodeAt(0); | ||
38 | } | ||
39 | |||
40 | |||
41 | // a safe fast alternative to decodeURIComponent | ||
42 | QueryString.unescapeBuffer = function(s, decodeSpaces) { | ||
43 | var out = new Buffer(s.length); | ||
44 | var state = 'CHAR'; // states: CHAR, HEX0, HEX1 | ||
45 | var n, m, hexchar; | ||
46 | |||
47 | for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) { | ||
48 | var c = s.charCodeAt(inIndex); | ||
49 | switch (state) { | ||
50 | case 'CHAR': | ||
51 | switch (c) { | ||
52 | case charCode('%'): | ||
53 | n = 0; | ||
54 | m = 0; | ||
55 | state = 'HEX0'; | ||
56 | break; | ||
57 | case charCode('+'): | ||
58 | if (decodeSpaces) c = charCode(' '); | ||
59 | // pass thru | ||
60 | default: | ||
61 | out[outIndex++] = c; | ||
62 | break; | ||
63 | } | ||
64 | break; | ||
65 | |||
66 | case 'HEX0': | ||
67 | state = 'HEX1'; | ||
68 | hexchar = c; | ||
69 | if (charCode('0') <= c && c <= charCode('9')) { | ||
70 | n = c - charCode('0'); | ||
71 | } else if (charCode('a') <= c && c <= charCode('f')) { | ||
72 | n = c - charCode('a') + 10; | ||
73 | } else if (charCode('A') <= c && c <= charCode('F')) { | ||
74 | n = c - charCode('A') + 10; | ||
75 | } else { | ||
76 | out[outIndex++] = charCode('%'); | ||
77 | out[outIndex++] = c; | ||
78 | state = 'CHAR'; | ||
79 | break; | ||
80 | } | ||
81 | break; | ||
82 | |||
83 | case 'HEX1': | ||
84 | state = 'CHAR'; | ||
85 | if (charCode('0') <= c && c <= charCode('9')) { | ||
86 | m = c - charCode('0'); | ||
87 | } else if (charCode('a') <= c && c <= charCode('f')) { | ||
88 | m = c - charCode('a') + 10; | ||
89 | } else if (charCode('A') <= c && c <= charCode('F')) { | ||
90 | m = c - charCode('A') + 10; | ||
91 | } else { | ||
92 | out[outIndex++] = charCode('%'); | ||
93 | out[outIndex++] = hexchar; | ||
94 | out[outIndex++] = c; | ||
95 | break; | ||
96 | } | ||
97 | out[outIndex++] = 16 * n + m; | ||
98 | break; | ||
99 | } | ||
100 | } | ||
101 | |||
102 | // TODO support returning arbitrary buffers. | ||
103 | |||
104 | return out.slice(0, outIndex - 1); | ||
105 | }; | ||
106 | |||
107 | |||
108 | QueryString.unescape = function(s, decodeSpaces) { | ||
109 | return QueryString.unescapeBuffer(s, decodeSpaces).toString(); | ||
110 | }; | ||
111 | |||
112 | |||
113 | QueryString.escape = function(str) { | ||
114 | return encodeURIComponent(str); | ||
115 | }; | ||
116 | |||
117 | var stringifyPrimitive = function(v) { | ||
118 | switch (typeof v) { | ||
119 | case 'string': | ||
120 | return v; | ||
121 | |||
122 | case 'boolean': | ||
123 | return v ? 'true' : 'false'; | ||
124 | |||
125 | case 'number': | ||
126 | return isFinite(v) ? v : ''; | ||
127 | |||
128 | default: | ||
129 | return ''; | ||
130 | } | ||
131 | }; | ||
132 | |||
133 | |||
134 | QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) { | ||
135 | sep = sep || '&'; | ||
136 | eq = eq || '='; | ||
137 | obj = (obj === null) ? undefined : obj; | ||
138 | |||
139 | switch (typeof obj) { | ||
140 | case 'object': | ||
141 | return Object.keys(obj).map(function(k) { | ||
142 | if (Array.isArray(obj[k])) { | ||
143 | return obj[k].map(function(v) { | ||
144 | return QueryString.escape(stringifyPrimitive(k)) + | ||
145 | eq + | ||
146 | QueryString.escape(stringifyPrimitive(v)); | ||
147 | }).join(sep); | ||
148 | } else { | ||
149 | return QueryString.escape(stringifyPrimitive(k)) + | ||
150 | eq + | ||
151 | QueryString.escape(stringifyPrimitive(obj[k])); | ||
152 | } | ||
153 | }).join(sep); | ||
154 | |||
155 | default: | ||
156 | if (!name) return ''; | ||
157 | return QueryString.escape(stringifyPrimitive(name)) + eq + | ||
158 | QueryString.escape(stringifyPrimitive(obj)); | ||
159 | } | ||
160 | }; | ||
161 | |||
162 | // Parse a key=val string. | ||
163 | QueryString.parse = QueryString.decode = function(qs, sep, eq) { | ||
164 | sep = sep || '&'; | ||
165 | eq = eq || '='; | ||
166 | var obj = {}; | ||
167 | |||
168 | if (typeof qs !== 'string' || qs.length === 0) { | ||
169 | return obj; | ||
170 | } | ||
171 | |||
172 | qs.split(sep).forEach(function(kvp) { | ||
173 | var x = kvp.split(eq); | ||
174 | var k = QueryString.unescape(x[0], true); | ||
175 | var v = QueryString.unescape(x.slice(1).join(eq), true); | ||
176 | |||
177 | if (!hasOwnProperty(obj, k)) { | ||
178 | obj[k] = v; | ||
179 | } else if (!Array.isArray(obj[k])) { | ||
180 | obj[k] = [obj[k], v]; | ||
181 | } else { | ||
182 | obj[k].push(v); | ||
183 | } | ||
184 | }); | ||
185 | |||
186 | return obj; | ||
187 | }; |
... | @@ -5,7 +5,7 @@ | ... | @@ -5,7 +5,7 @@ |
5 | t.assertEquals(self.fetchText('ul li'), 'onetwothree', 'Casper.fetchText() can retrieve text contents'); | 5 | t.assertEquals(self.fetchText('ul li'), 'onetwothree', 'Casper.fetchText() can retrieve text contents'); |
6 | }); | 6 | }); |
7 | 7 | ||
8 | casper.run(function(self) { | 8 | casper.run(function() { |
9 | t.done(); | 9 | t.done(); |
10 | }); | 10 | }); |
11 | })(casper.test); | 11 | })(casper.test); | ... | ... |
... | @@ -31,7 +31,7 @@ | ... | @@ -31,7 +31,7 @@ |
31 | self.options.onAlert = function(self, message) { | 31 | self.options.onAlert = function(self, message) { |
32 | t.assertEquals(message, 'plop', 'Casper.options.onAlert() can intercept an alert message'); | 32 | t.assertEquals(message, 'plop', 'Casper.options.onAlert() can intercept an alert message'); |
33 | }; | 33 | }; |
34 | }).thenOpen('tests/site/alert.html').click('button', function(self) { | 34 | }).thenOpen('tests/site/alert.html').thenClick('button', function(self) { |
35 | self.options.onAlert = null; | 35 | self.options.onAlert = null; |
36 | }); | 36 | }); |
37 | 37 | ... | ... |
tests/suites/casper/open.js
0 → 100644
1 | (function(t) { | ||
2 | var current = 0, tests = [ | ||
3 | function(settings) { | ||
4 | t.assertEquals(settings, { | ||
5 | method: "get" | ||
6 | }, "Casper.open() used the expected GET settings"); | ||
7 | }, | ||
8 | function(settings) { | ||
9 | t.assertEquals(settings, { | ||
10 | method: "post", | ||
11 | data: "plop=42&chuck=norris" | ||
12 | }, "Casper.open() used the expected POST settings"); | ||
13 | }, | ||
14 | function(settings) { | ||
15 | t.assertEquals(settings, { | ||
16 | method: "put", | ||
17 | data: "plop=42&chuck=norris" | ||
18 | }, "Casper.open() used the expected PUT settings"); | ||
19 | }, | ||
20 | function(settings) { | ||
21 | t.assertEquals(settings, { | ||
22 | method: "get", | ||
23 | username: 'bob', | ||
24 | password: 'sinclar' | ||
25 | }, "Casper.open() used the expected HTTP auth settings"); | ||
26 | } | ||
27 | ]; | ||
28 | |||
29 | casper.start(); | ||
30 | |||
31 | casper.on('open', function(url, settings) { | ||
32 | tests[current++](settings); | ||
33 | }); | ||
34 | |||
35 | // GET | ||
36 | casper.open('tests/site/index.html').then(function() { | ||
37 | t.pass("Casper.open() can open and load a location using GET"); | ||
38 | }); | ||
39 | |||
40 | // POST | ||
41 | casper.open('tests/site/index.html', { | ||
42 | method: 'post', | ||
43 | data: { | ||
44 | plop: 42, | ||
45 | chuck: 'norris' | ||
46 | } | ||
47 | }).then(function() { | ||
48 | t.pass("Casper.open() can open and load a location using POST"); | ||
49 | }); | ||
50 | |||
51 | // PUT | ||
52 | casper.open('tests/site/index.html', { | ||
53 | method: 'put', | ||
54 | data: { | ||
55 | plop: 42, | ||
56 | chuck: 'norris' | ||
57 | } | ||
58 | }).then(function() { | ||
59 | t.pass("Casper.open() can open and load a location using PUT"); | ||
60 | }); | ||
61 | |||
62 | // HTTP Auth | ||
63 | casper.open('tests/site/index.html', { | ||
64 | method: 'get', | ||
65 | username: 'bob', | ||
66 | password: 'sinclar' | ||
67 | }).then(function() { | ||
68 | t.pass("Casper.open() can open and load a location using HTTP auth"); | ||
69 | }); | ||
70 | |||
71 | casper.run(function() { | ||
72 | this.removeAllListeners('open'); | ||
73 | t.done(); | ||
74 | }); | ||
75 | })(casper.test); |
... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
28 | self.test.assertEquals(i, item - 1, 'Casper.each() passes a contextualized index'); | 28 | self.test.assertEquals(i, item - 1, 'Casper.each() passes a contextualized index'); |
29 | }); | 29 | }); |
30 | 30 | ||
31 | casper.run(function(self) { | 31 | casper.run(function() { |
32 | t.done(); | 32 | t.done(); |
33 | }); | 33 | }); |
34 | })(casper.test); | 34 | })(casper.test); | ... | ... |
-
Please register or sign in to post a comment