Skip to content
Newer
Older
100644 404 lines (262 sloc) 10.7 KB
3fa6918 @tj Initial commit
tj authored
1
e9739ba @tj docs
tj authored
2 # Tobi
3
ae1484a @tj docs
tj authored
4 Expressive server-side functional testing with jQuery and [jsdom](https://github.com/tmpvar/jsdom).
e9739ba @tj docs
tj authored
5
ae1484a @tj docs
tj authored
6 Tobi allows you to test your web application as if it were a browser. Interactions with your app are performed via jsdom, htmlparser, and jQuery, in combination with Tobi's Cookie Jar, provides a natural JavaScript API for traversing, manipulating and asserting the DOM, and session based browsing.
e9739ba @tj docs
tj authored
7
706ead0 @tj Readme
tj authored
8 ## Example
9
10 In the example below, we have an http server or express app `require()`ed, and we simply create new tobi `Browser` for that app to test against. Then we `GET /login`, receiving a response to assert headers, status codes etc, and the `$` jQuery context.
11
5bfe654 @tj More readme
tj authored
12 We can then use regular css selectors to grab the form, we use tobi's `.fill()` method to fill some inputs (supports textareas, checkboxes, radios, etc), then we proceed to submitting the form, again receiving a response and the jQuery context.
706ead0 @tj Readme
tj authored
13
14 var tobi = require('tobi')
4c95bdc @tj example typo. Closes #27
tj authored
15 , app = require('./my/app')
706ead0 @tj Readme
tj authored
16 , browser = tobi.createBrowser(app);
17
18 browser.get('/login', function(res, $){
19 $('form')
20 .fill({ username: 'tj', password: 'tobi' })
21 .submit(function(res, $){
22 res.should.have.status(200);
23 res.should.have.header('Content-Length');
24 res.should.have.header('Content-Type', 'text/html; charset=utf-8');
25 $('ul.messages').should.have.one('li', 'Successfully authenticated');
26 browser.get('/login', function(res, $){
27 res.should.have.status(200);
28 $('ul.messages').should.have.one('li', 'Already authenticated');
29 // We are finished testing, close the server
30 app.close();
31 });
32 });
33 });
3fa6918 @tj Initial commit
tj authored
34
0296240 @tj Docs
tj authored
35 ## Browser
36
6c01f11 @tj Finished Browser(port, host) support
tj authored
37 Tobi provides the `Browser` object, created via `tobi.createBrowser(app)`, where `app` is a node `http.Server`, so for example Connect or Express apps will work just fine. There is no need to invoke `listen()` as this is handled by Tobi, and requests will be deferred until the server is listening.
38
39 Alternatively you may pass a `port` and `host` to `createBrowser()`, for example:
40
41 var browser = tobi.createBrowser(80, 'lb.dev');
0296240 @tj Docs
tj authored
42
b17bf9d @tj Updated htmlparser submodule
tj authored
43 ### Evaluate External Resources
44
45 To evaluate script tags simply pass the `{ external: true }` option:
46
47 var browser = tobi.createBrowser(app, { external: true });
48
0296240 @tj Docs
tj authored
49 ### Browser#get()
50
51 Perform a `GET` request with optional `options` containing headers, etc:
52
53 browser.get('/login', function(res, $){
54
55 });
56
57 With options:
58
59 browser.get('/login', { headers: { ... }}, function(res, $){
60
61 });
62
63 Aliased as `visit`, and `open`.
64
65 ### Browser#post()
66
67 Perform a `POST` request with optional `options` containing headers, body etc:
68
69 browser.post('/login', function(res, $){
70
71 });
72
73 With options:
74
75 browser.post('/login', { body: 'foo=bar' }, function(res, $){
76
77 });
78
79 ### Browser#back()
80
81 `GET` the previous page:
82
83 browser.get('/', function(){
84 // on /
85 browser.get('/foo', function(){
86 // on /foo
87 browser.back(function(){
88 // on /
89 });
90 });
91 });
92
93 ## Browser locators
94
95 Locators are extended extend selectors, the rules are as following:
96
97 - element text
98 - element id
99 - element value
100 - css selector
101
102 These rules apply to all `Browser` related methods such as `click()`, `fill()`, `type()` etc. Provided the following markup:
103
104 <form>
105 <input id="form-login" type="submit" value="Login" />
106 </form>
107
108 The following locators will match the input:
109
110 .click('Login');
111 .click('form-login');
112 .click('input[type=submit]');
113 .click(':submit');
114
67d2582 @tj Docs
tj authored
115 ### Browser#click(locator[, fn])
116
a2ca84c @sorich87 Allow for button[type=submit] elements to be clicked
sorich87 authored
117 Tobi allows you to `click()` `a` elements, `button[type=submit]` elements, and `input[type=submit]` elements in order to submit a form, or request a url.
67d2582 @tj Docs
tj authored
118
119 Submitting a form:
120
121 browser.click('Login', function(res, $){
122
123 });
124
125 Submitting with jQuery (no locators):
126
127 $('form :submit').click(function(res, $){
128
129 });
130
131 Clicking a link:
132
133 browser.click('Register Account', function(res, $){
134
135 });
136
137 Clicking with jQuery (no locators):
138
139 $('a.register', function(res, $){
140
141 });
0296240 @tj Docs
tj authored
142
633de3a @tj Docs
tj authored
143 ### Browser#submit(locator|fn, fn)
144
145 Submit the first form in context:
146
147 browser.submit(function(res, $){
148
149 });
150
151 browser.submit(function(){
152
153 });
154
7a3b417 @tj Docs
tj authored
155 ### Browser#type(locator, str)
156
157 "Type" the given _str_:
158
159 browser
160 .type('username', 'tj')
161 .type('password', 'foobar');
162
633de3a @tj Docs
tj authored
163
164 ### Browser#{check,uncheck}(locator)
165
166 Check or uncheck the given _locator_:
167
168 browser
169 .check('agreement')
170 .uncheck('agreement');
171
172 ### Browser#select(locator, options)
173
174 Select the given option or options:
175
176 browser
177 .select('colors', 'Red')
178 .select('colors', ['Red', 'Green']);
179
7a3b417 @tj Docs
tj authored
180 ### Browser#fill(locator, fields)
181
182 Fill the given _fields_, supporting all types of inputs. For example we might have the following form:
183
184 <form>
185 <input type="text" name="user[name]" />
186 <input type="text" name="user[email]" />
187 <input type="checkbox" name="user[agreement]" />
188
189 <select name="digest">
190 <option value="none">Never</option>
191 <option value="daily">Daily</option>
192 <option value="weekly">Weekly</option>
193 </select>
194
195 <select name="favorite-colors" multiple>
196 <option value="red">Red</option>
197 <option value="green">Green</option>
198 <option value="blue">Blue</option>
199 </select>
200 </form>
201
202 Which can be filled using locators:
203
204 browser
205 .fill({
206 'user[name]': 'tj'
207 , 'user[email]': 'tj@learnboost.com'
208 , 'user[agreement]': true
209 , 'user[digest]': 'Daily'
210 , 'user[favorite-colors]': ['red', 'Green']
bf79bda @tj Docs
tj authored
211 }).submit(function(){
212
213 });
7a3b417 @tj Docs
tj authored
214
251dd7c @tj more docs
tj authored
215 With jQuery:
216
217 $('form')
218 .fill({
219 'user[name]': 'tj'
220 , 'user[favorite-colors]': 'red'
221 }).submit(function(){
222
223 });
224
b78d289 @tj Added Browser#text(locator)
tj authored
225 ### Browser#text(locator)
226
227 Return text at the given locator. For example if we have the form option somewhere in our markup:
228
229 <option value="once">Once per day</option>
230
231 We can invoke `browser.text('once')` returning "Once per day".
232
85743ad @tj Context docs
tj authored
233 ### Browser#{context,within}(selector, fn)
234
235 Alter the browser context for the duration of the given callback `fn`. For example if you have several forms on a page, an wish to focus on one:
236
237 <div><form id="user-search" method="post" action="/search/users">
238 <input type="text" name="query" />
239 </form></div>
240
241 <div><form id="post-search" method="post" action="/search/posts">
242 <input type="text" name="query" />
243 </form></div>
244
245 Example test using contexts:
246
247 browser.get('/search', function(res, $){
248
249 // Global context has 2 forms
250 $('form').should.have.length(2);
251
252 // Focus on the second div
253 browser.within('div:nth-child(2)', function(){
254
255 // We now have one form, and no direct input children
256 $('> form').should.have.length(1);
257 $('> input').should.have.length(0);
258
259 // Focus on the form, we now have a single direct input child
260 browser.within('form', function(){
261 $('> form').should.have.length(0);
262 $('> input').should.have.length(1);
263 });
264
265 // Back to our div focus, we have one form again
266 $('> form').should.have.length(1);
267 $('> input').should.have.length(0);
268
269 // Methods such as .type() etc work with contexts
270 browser
271 .type('query', 'foo bar')
272 .submit(function(res){
edb7946 @tj docs
tj authored
273
85743ad @tj Context docs
tj authored
274 });
275 });
276
277 // Back to global context
278 $('form').should.have.length(2);
279 });
280
9f42a15 @tj More docs
tj authored
281 ## Assertions
282
6f1ef07 @tj docs
tj authored
283 Tobi extends the [should.js](http://github.com/visionmedia/should.js) assertion library to provide you with DOM and response related assertion methods.
9f42a15 @tj More docs
tj authored
284
5ff4b7f @tj Added assertion docs
tj authored
285 ### Assertion#text(str|regexp)
286
287 Assert element text via regexp or string:
288
289 elem.should.have.text('foo bar');
290 elem.should.have.text(/^foo/);
291 elem.should.not.have.text('rawr');
292
936213a @tj Added .include modifier support to the .text() assertion method
tj authored
293 When asserting a descendant's text amongst a heap of elements, we can utilize the `.include` modifier:
294
295 $('*').should.include.text('My Site');
296
5ff4b7f @tj Added assertion docs
tj authored
297 ### Assertion#many(selector)
298
299 Assert that one or more of the given selector is present:
300
301 ul.should.have.many('li');
302
303 ### Assertion#one(selector)
304
305 Assert that one of the given selector is present:
306
307 p.should.have.one('input');
308
309 ### Assertion#attr(key[, val])
310
311 Assert that the given _key_ exists, with optional _val_:
312
313 p.should.not.have.attr('href');
314 a.should.have.attr('href');
315 a.should.have.attr('href', 'http://learnboost.com');
316
317 Shortcuts are also provided:
318
319 - id()
320 - title()
321 - href()
322 - alt()
323 - src()
324 - rel()
325 - media()
326 - name()
327 - action()
328 - method()
329 - value()
330 - enabled
331 - disabled
332 - checked
333 - selected
334
335 For example:
336
337 form.should.have.id('user-edit');
338 form.should.have.action('/login');
339 form.should.have.method('post');
340 checkbox.should.be.enabled;
341 checkbox.should.be.disabled;
342 option.should.be.selected;
343 option.should.not.be.selected;
344
345 ### Assertion#class(name)
346
347 Assert that the element has the given class _name_.
348
349 form.should.have.class('user-edit');
350
351 ### Assertion#status(code)
352
353 Assert response status code:
354
355 res.should.have.status(200);
356 res.should.not.have.status(500);
357
358 ### Assertion#header(field[, val])
359
360 Assert presence of response header _field_ and optional _val_:
361
362 res.should.have.header('Content-Length');
363 res.should.have.header('Content-Type', 'text/html');
364
df74fc4 @tj docs
tj authored
365 ## Testing
366
aa4bd8c @tj docs
tj authored
367 Install the deps:
df74fc4 @tj docs
tj authored
368
aa4bd8c @tj docs
tj authored
369 $ npm install
df74fc4 @tj docs
tj authored
370
371 and execute:
372
373 $ make test
374
55e50c3 @tj Added tobi picture
tj authored
375 ## WWTD
376
377 What Would Tobi Do:
378
379 ![Tobi](http://sphotos.ak.fbcdn.net/hphotos-ak-snc3/hs234.snc3/22173_446973930292_559060292_10921426_7238463_n.jpg)
380
3fa6918 @tj Initial commit
tj authored
381 ## License
382
383 (The MIT License)
384
df74fc4 @tj docs
tj authored
385 Copyright (c) 2010 LearnBoost &lt;dev@learnboost.com&gt;
3fa6918 @tj Initial commit
tj authored
386
387 Permission is hereby granted, free of charge, to any person obtaining
388 a copy of this software and associated documentation files (the
389 'Software'), to deal in the Software without restriction, including
8591078 @tj Fixed login example
tj authored
390 without limitation te rights to use, copy, modify, merge, publish,
3fa6918 @tj Initial commit
tj authored
391 distribute, sublicense, and/or sell copies of the Software, and to
392 permit persons to whom the Software is furnished to do so, subject to
393 the following conditions:
394
395 The above copyright notice and this permission notice shall be
396 included in all copies or substantial portions of the Software.
397
398 THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
399 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
400 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
401 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
402 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
403 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
404 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Something went wrong with that request. Please try again.