SodaThe Selenium Node Adapter or Soda provides a unified client to both Selenium RC and Saucelabs OnDemand. | |
| lib/soda/client.js |
112 |
113 | Module dependencies.
114 |
115 | |
116 |
117 | var http = require('http')
118 | , qs = require('querystring')
119 | , EventEmitter = require('events').EventEmitter;
120 | |
121 |
122 |
123 |
124 | Initialize a Client with the given options .
125 |
126 | Options
127 |
128 | host Hostname defaulting to localhostport Port number defaulting to 4444browser Browser nameurl URL string
129 |
130 |
131 |
132 | params: Object options api: public
133 | |
134 |
135 | var Client = exports = module.exports = function Client(options) {
136 | this.host = options.host || 'localhost';
137 | this.port = options.port || 4444;
138 | this.browser = options.browser || 'firefox';
139 | this.url = options.url;
140 |
141 |
142 | if (this.browser[0] !== '*') {
143 | this.browser = '*' + this.browser;
144 | }
145 |
146 | EventEmitter.call(this);
147 | };
148 | |
149 |
150 |
151 |
152 | Interit from EventEmitter .
153 |
154 | |
155 |
156 | Client.prototype.__proto__ = EventEmitter.prototype;
157 | |
158 |
159 |
160 |
161 | Initialize a new session, then callback fn(err, sid)
162 |
163 |
164 |
165 | param: Function fn return: Client api: public
166 | |
167 |
168 | Client.prototype.session = function(fn){
169 | var self = this;
170 | if (!this.browser) throw new Error('browser required');
171 | if (!this.url) throw new Error('browser url required');
172 | if (this.queue) {
173 | return this.enqueue('getNewBrowserSession', [this.browser, this.url], function(body){
174 | self.sid = body;
175 | });
176 | } else {
177 | this.command('getNewBrowserSession', [this.browser, this.url], function(err, body){
178 | if (err) return fn(err);
179 | fn(null, self.sid = body);
180 | });
181 | }
182 | };
183 | |
184 |
185 |
186 |
187 | Execute the given cmd / args , then callback fn(err, body, res) .
188 |
189 |
190 |
191 |
192 | |
193 |
194 | Client.prototype.command = function(cmd, args, fn){
195 | this.emit('command', cmd, args);
196 |
197 |
198 | var client = http.createClient(this.port, this.host);
199 |
200 |
201 | var path = this.commandPath(cmd, args);
202 |
203 |
204 | var req = client.request('GET'
205 | , path
206 | , { Host: this.host + (this.port ? ':' + this.port : '') });
207 |
208 | req.on('response', function(res){
209 | res.body = '';
210 | res.setEncoding('utf8');
211 | res.on('data', function(chunk){ res.body += chunk; });
212 | res.on('end', function(){
213 | if (res.body.indexOf('ERROR') === 0) {
214 | var err = res.body.replace(/^ERROR: */, '');
215 | err = cmd + '(' + args.join(', ') + '): ' + err;
216 | fn(new Error(err), res.body, res);
217 | } else {
218 | if (res.body.indexOf('OK') === 0) {
219 | res.body = res.body.replace(/^OK,?/, '');
220 | }
221 | fn(null, res.body, res);
222 | }
223 | });
224 | });
225 | req.end();
226 | return this;
227 | };
228 | |
229 |
230 |
231 |
232 | Construct a cmd path with the given args .
233 |
234 |
235 |
236 | param: String name param: Array args return: String api: private
237 | |
238 |
239 | Client.prototype.commandPath = function(cmd, args){
240 | var obj = { cmd: cmd };
241 |
242 |
243 | if (args) {
244 | args.forEach(function(arg, i){
245 | obj[i+1] = arg;
246 | });
247 | }
248 |
249 | if (this.sid && cmd !== 'getNewBrowserSession') {
250 | obj.sessionId = this.sid;
251 | }
252 |
253 | return '/selenium-server/driver/?' + qs.stringify(obj);
254 | };
255 | |
256 |
257 |
258 |
259 | Indicate that commands should be queued.
260 |
261 | Example
262 |
263 | browser
264 | .chain
265 | .session()
266 | .open('/')
267 | .type('q', 'Hello World')
268 | .clickAndWait('btnG')
269 | .assertTitle('Hello World - Google')
270 | .testComplete()
271 | .end(function(err){ ... });
272 |
273 |
274 | |
275 |
276 | Client.prototype.__defineGetter__('chain', function(){
277 | this.queue = [];
278 | return this;
279 | });
280 | |
281 |
282 |
283 |
284 | Callback fn(err) when the queue is complete, or
285 | when an exception has occurred.
286 |
287 |
288 |
289 | param: Function fn api: public
290 | |
291 |
292 | Client.prototype.end = function(fn){
293 | this._done = fn;
294 | this.queue.shift()();
295 | };
296 | |
297 |
298 |
299 |
300 | Enqueue the given cmd and array of args for execution.
301 |
302 |
303 |
304 | param: String cmd param: Array args return: Client api: private
305 | |
306 |
307 | Client.prototype.enqueue = function(cmd, args, fn){
308 | var self = this
309 | , len = args.length;
310 |
311 |
312 | if (typeof args[len - 1] === 'function') {
313 | fn = args.pop();
314 | }
315 |
316 | this.queue.push(function(){
317 | self.command(cmd, args, function(err, body, res){
318 |
319 | if (!err && fn) {
320 | try {
321 | fn(body, res);
322 | } catch (err) {
323 | return self._done(err, body, res);
324 | }
325 | }
326 |
327 | if (err) {
328 | self._done(err, body, res);
329 | } else if (self.queue.length) {
330 | self.queue.shift()();
331 | } else {
332 | self._done(null, body, res);
333 | }
334 | });
335 | });
336 | return this;
337 | };
338 | |
339 |
340 |
341 |
342 | Arbitrary callback fn(this) when using the chaining api.
343 |
344 |
345 |
346 | param: Function fn return: Client api: public
347 | |
348 |
349 | Client.prototype.and = function(fn){
350 | fn.call(this, this);
351 | return this;
352 | };
353 | |
354 |
355 |
356 |
357 | Shortcut for new soda.Client() .
358 |
359 |
360 |
361 | param: Object options return: Client api: public
362 | |
363 |
364 | exports.createClient = function(options){
365 | return new Client(options);
366 | };
367 | |
368 |
369 |
370 |
371 | Command names.
372 |
373 |
374 | |
375 |
376 | exports.commands = [
377 |
378 | 'getNewBrowserSession'
379 | , 'setContext'
380 | , 'testComplete'
381 |
382 | , 'addLocationStrategy'
383 | , 'addScript'
384 | , 'addSelection'
385 | , 'allowNativeXpath'
386 | , 'altKeyDown'
387 | , 'altKeyUp'
388 | , 'answerOnNextPrompt'
389 | , 'assignId'
390 | , 'break'
391 | , 'captureEntirePageScreenshot'
392 | , 'check'
393 | , 'chooseCancelOnNextConfirmation'
394 | , 'chooseOkOnNextConfirmation'
395 | , 'click'
396 | , 'clickAndWait'
397 | , 'clickAt'
398 | , 'clickAtAndWait'
399 | , 'close'
400 | , 'contextMenu'
401 | , 'contextMenuAt'
402 | , 'controlKeyDown'
403 | , 'controlKeyUp'
404 | , 'createCookie'
405 | , 'deleteAllVisibleCookies'
406 | , 'deleteCookie'
407 | , 'deselectPopUp'
408 | , 'doubleClick'
409 | , 'doubleClickAt'
410 | , 'dragAndDrop'
411 | , 'dragAndDropToObject'
412 | , 'echo'
413 | , 'fireEvent'
414 | , 'focus'
415 | , 'goBack'
416 | , 'highlight'
417 | , 'ignoreAttributesWithoutValue'
418 | , 'keyDown'
419 | , 'keyPress'
420 | , 'keyUp'
421 | , 'metaKeyDown'
422 | , 'metaKeyUp'
423 | , 'mouseDown'
424 | , 'mouseDownAt'
425 | , 'mouseDownRight'
426 | , 'mouseDownRightAt'
427 | , 'mouseMove'
428 | , 'mouseMoveAt'
429 | , 'mouseOut'
430 | , 'mouseOver'
431 | , 'mouseUp'
432 | , 'mouseUpAt'
433 | , 'mouseUpRight'
434 | , 'mouseUpRightAt'
435 | , 'open'
436 | , 'openWindow'
437 | , 'refresh'
438 | , 'removeAllSelections'
439 | , 'removeScript'
440 | , 'removeSelection'
441 | , 'rollup'
442 | , 'runScript'
443 | , 'select'
444 | , 'selectFrame'
445 | , 'selectPopUp'
446 | , 'selectWindow'
447 | , 'setBrowserLogLevel'
448 | , 'setCursorPosition'
449 | , 'setMouseSpeed'
450 | , 'setSpeed'
451 | , 'setTimeout'
452 | , 'shiftKeyDown'
453 | , 'shiftKeyUp'
454 | , 'submit'
455 | , 'type'
456 | , 'typeKeys'
457 | , 'uncheck'
458 | , 'useXpathLibrary'
459 | , 'waitForCondition'
460 | , 'waitForFrameToLoad'
461 | , 'waitForPageToLoad'
462 | , 'waitForPopUp'
463 | , 'windowFocus'
464 | , 'windowMaximize'
465 | ];
466 | |
467 |
468 |
469 |
470 | Accessor names.
471 |
472 |
473 | |
474 |
475 | exports.accessors = [
476 | 'ErrorOnNext'
477 | , 'FailureOnNext'
478 | , 'Alert'
479 | , 'AllButtons'
480 | , 'AllFields'
481 | , 'AllLinks'
482 | , 'AllWindowIds'
483 | , 'AllWindowNames'
484 | , 'AllWindowTitles'
485 | , 'Attribute'
486 | , 'AttributeFromAllWindows'
487 | , 'BodyText'
488 | , 'Confirmation'
489 | , 'Cookie'
490 | , 'CookieByName'
491 | , 'CursorPosition'
492 | , 'ElementHeight'
493 | , 'ElementIndex'
494 | , 'ElementPositionLeft'
495 | , 'ElementPositionTop'
496 | , 'ElementWidth'
497 | , 'Eval'
498 | , 'Expression'
499 | , 'HtmlSource'
500 | , 'Location'
501 | , 'LogMessages'
502 | , 'MouseSpeed'
503 | , 'Prompt'
504 | , 'SelectedId'
505 | , 'SelectedIds'
506 | , 'SelectedIndex'
507 | , 'SelectedIndexes'
508 | , 'SelectedLabel'
509 | , 'SelectedLabels'
510 | , 'SelectedValue'
511 | , 'SelectedValues'
512 | , 'SelectOptions'
513 | , 'Speed'
514 | , 'Table'
515 | , 'Text'
516 | , 'Title'
517 | , 'Value'
518 | , 'WhetherThisFrameMatchFrameExpression'
519 | , 'WhetherThisWindowMatchWindowExpression'
520 | , 'XpathCount'
521 | , 'AlertPresent'
522 | , 'Checked'
523 | , 'ConfirmationPresent'
524 | , 'CookiePresent'
525 | , 'Editable'
526 | , 'ElementPresent'
527 | , 'ElementNotPresent'
528 | , 'Ordered'
529 | , 'PromptPresent'
530 | , 'SomethingSelected'
531 | , 'TextPresent'
532 | , 'Visible'
533 | ];
534 | |
535 |
536 |
537 |
538 | Generate commands via accessors.
539 |
540 | All accessors get prefixed with:
541 |
542 | - get
- assert
- assertNot
- verify
- verifyNot
- waitFor
- waitForNot
543 |
544 | For example providing us with:
545 |
546 | - getTitle
- assertTitle
- verifyTitle
...
547 | |
548 |
549 | exports.accessors.map(function(cmd){
550 | exports.commands.push(
551 | 'get' + cmd
552 | , 'assert' + cmd
553 | , 'assertNot' + cmd
554 | , 'verify' + cmd
555 | , 'verifyNot' + cmd
556 | , 'waitFor' + cmd
557 | , 'waitForNot' + cmd);
558 | });
559 | |
560 |
561 |
562 |
563 | Generate command methods.
564 |
565 | |
566 |
567 | exports.commands.map(function(cmd){
568 | Client.prototype[cmd] = function(){
569 |
570 | if (this.queue) {
571 | var args = Array.prototype.slice.call(arguments);
572 | return this.enqueue(cmd, args);
573 |
574 | } else {
575 | var len = arguments.length
576 | , fn = arguments[len - 1]
577 | , args = Array.prototype.slice.call(arguments, 0, len - 1);
578 | return this.command(cmd, args, fn);
579 | }
580 | };
581 | });
582 | |
583 |
| lib/soda/index.js |
584 |
585 | Export all of ./client.
586 |
587 | |
588 |
589 | exports = module.exports = require('./client');
590 | |
591 |
592 |
593 |
594 | Export sauce client.
595 |
596 | |
597 |
598 | exports.SauceClient = require('./sauce');
599 | exports.createSauceClient = require('./sauce').createClient;
600 | |
601 |
602 |
603 |
604 | Library version.
605 |
606 |
607 | |
608 |
609 | exports.version = '0.2.0';
610 |
611 | |
612 |
| lib/soda/sauce.js |
613 |
614 | Module dependencies.
615 |
616 | |
617 |
618 | var Client = require('./client');
619 | |
620 |
621 |
622 |
623 | Initialize a SauceClient with the given options . A suite of environment
624 | variables are also supported in place of the options described below.
625 |
626 | Options
627 |
628 | username Saucelabs usernameaccess-key Account access keyos Operating system ex "Linux"browser Browser name, ex "firefox"browser-version Browser version, ex "3.0.", "7."max-duration Maximum test duration in seconds, ex 300 (5 minutes)
629 |
630 | Environment Variables
631 |
632 | SAUCE_HOST Defaulting to "saucelabs.com"SAUCE_PORT Defaulting to 4444SAUCE_OS SAUCE_BROWSER SAUCE_USERNAME SAUCE_ACCESS_KEY SAUCE_BROWSER_VERSION
633 |
634 |
635 |
636 | params: Object options api: public
637 | |
638 |
639 | var SauceClient = exports = module.exports = function SauceClient(options) {
640 | options = options || {};
641 | this.host = process.env.SAUCE_HOST || 'saucelabs.com';
642 | this.port = process.env.SAUCE_PORT || 4444;
643 |
644 |
645 | options.os = options.os || process.env.SAUCE_OS || 'Linux';
646 | options.url = options.url || process.env.SAUCE_BROWSER_URL;
647 | options.browser = options.browser || process.env.SAUCE_BROWSER || 'firefox';
648 | options.username = options.username || process.env.SAUCE_USERNAME;
649 | options['access-key'] = options['access-key'] || process.env.SAUCE_ACCESS_KEY;
650 |
651 |
652 | options['browser-version'] = options['browser-version'] == undefined
653 | ? (process.env.SAUCE_BROWSER_VERSION || '')
654 | : (options['browser-version'] || '');
655 |
656 | this.url = options.url;
657 | this.username = options.username;
658 | this.accessKey = options['access-key'];
659 | this.options = options;
660 | this.browser = JSON.stringify(options);
661 | };
662 | |
663 |
664 |
665 |
666 | Interit from Client .
667 |
668 | |
669 |
670 | SauceClient.prototype.__proto__ = Client.prototype;
671 | |
672 |
673 |
674 |
675 | Return saucelabs video flv url.
676 |
677 |
678 |
679 | return: String api: public
680 | |
681 |
682 | SauceClient.prototype.__defineGetter__('videoUrl', function(){
683 | return exports.url(this.username, this.sid, 'video.flv');
684 | });
685 | |
686 |
687 |
688 |
689 | Return saucelabs log file url.
690 |
691 |
692 |
693 | return: String api: public
694 | |
695 |
696 | SauceClient.prototype.__defineGetter__('logUrl', function(){
697 | return exports.url(this.username, this.sid, 'selenium-server.log');
698 | });
699 | |
700 |
701 |
702 |
703 | Return saucelabs video embed script.
704 |
705 |
706 |
707 | return: String api: public
708 | |
709 |
710 | SauceClient.prototype.__defineGetter__('video', function(){
711 | return exports.video(this.username, this.accessKey, this.sid);
712 | });
713 | |
714 |
715 |
716 |
717 | Shortcut for new soda.SauceClient() .
718 |
719 |
720 |
721 | param: Object options return: Client api: public
722 | |
723 |
724 | exports.createClient = function(options){
725 | return new SauceClient(options);
726 | };
727 | |
728 |
729 |
730 |
731 | Return saucelabs url to jobId 's filename .
732 |
733 |
734 |
735 | param: String username param: String jobId param: String filename return: String api: public
736 | |
737 |
738 | exports.url = function(username, jobId, filename){
739 | return 'https://saucelabs.com/rest/'
740 | + username + '/jobs/'
741 | + jobId + '/results/'
742 | + filename;
743 | };
744 | |
745 |
746 |
747 |
748 | Return saucelabs video embed script.
749 |
750 |
751 |
752 | param: String username param: String accessKey param: String jobId return: String api: public
753 | |
754 |
755 | exports.video = function(username, accessKey, jobId){
756 | return '<script src="http://saucelabs.com/video-embed/'
757 | + jobId + '.js?username='
758 | + username + '&access_key='
759 | + accessKey + '"/>';
760 | };
761 | |
762 |
763 |