├── test ├── app │ ├── app │ │ ├── css │ │ │ ├── .gitkeep │ │ │ └── app.css │ │ ├── partials │ │ │ ├── .gitkeep │ │ │ ├── partial5.html │ │ │ ├── partial1.html │ │ │ ├── partial6.html │ │ │ ├── partial3.html │ │ │ ├── partial2.html │ │ │ └── partial4.html │ │ ├── js │ │ │ ├── services.js │ │ │ ├── directives.js │ │ │ ├── filters.js │ │ │ ├── controllers.js │ │ │ └── app.js │ │ ├── index.html │ │ └── index-manual-bootstrap.html │ ├── bower.json │ ├── LICENSE │ └── scripts │ │ └── web-server.js ├── mocha.opts ├── spec │ ├── regular-client.js │ └── angular-client.js └── bootstrap.js ├── package.json ├── .travis.yml ├── browser-scripts.js ├── readme.md └── index.js /test/app/app/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/app/app/partials/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/app/app/partials/partial5.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 5.

-------------------------------------------------------------------------------- /test/app/app/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

2 | -------------------------------------------------------------------------------- /test/app/app/partials/partial6.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 6.

2 | -------------------------------------------------------------------------------- /test/app/app/partials/partial3.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 3.

2 |
an element
3 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/spec/*.js 2 | --timeout 100000 3 | --require test/bootstrap.js 4 | --reporter spec 5 | --growl -------------------------------------------------------------------------------- /test/app/app/partials/partial2.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 2.

2 |

3 | Showing of 'interpolate' filter: 4 | {{ 'Current version is v%VERSION%.' | interpolate }} 5 |

6 | -------------------------------------------------------------------------------- /test/app/app/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | 6 | // Demonstrate how to register services 7 | // In this case it is a simple value service. 8 | angular.module('myApp.services', []). 9 | value('version', '0.1'); 10 | -------------------------------------------------------------------------------- /test/app/app/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp.directives', []). 7 | directive('appVersion', ['version', function(version) { 8 | return function(scope, elm, attrs) { 9 | elm.text(version); 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /test/app/app/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /test/app/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.2", 4 | "homepage": "https://github.com/webdriverio/webdriverjs-angular", 5 | "authors": [ 6 | "vvo " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "angular": "~1.2.22", 18 | "angular-route": "~1.2.22" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/app/app/css/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | -------------------------------------------------------------------------------- /test/app/app/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | angular.module('myApp.controllers', []). 6 | controller('MyCtrl1', [function() { 7 | 8 | }]) 9 | .controller('MyCtrl2', [function() { 10 | 11 | }]) 12 | .controller('MyCtrl5', function($timeout) { 13 | $timeout(function() { 14 | document.title = 'HELLO MY FRIEND'; 15 | }, 250); 16 | }) 17 | .controller('MyCtrl6', function($timeout) { 18 | $timeout(function() { 19 | window.aglobal = 'a global variable'; 20 | }, 250); 21 | }); -------------------------------------------------------------------------------- /test/spec/regular-client.js: -------------------------------------------------------------------------------- 1 | describe('a regular webdriverio client', function() { 2 | var webdriverio = require('webdriverio'); 3 | var client; 4 | 5 | before(function(done) { 6 | client = webdriverio 7 | .remote(webdriverjsOptions) 8 | .init() 9 | .url('http://localhost:8000/test/app/app/index.html#/view1', done); 10 | }); 11 | 12 | after(function(done) { 13 | client.end(done); 14 | }); 15 | 16 | it('does not wait for angularjs when clicking', function(done) { 17 | client 18 | // wait for page load 19 | .pause(2000) 20 | .click('[href="#/view2"]') 21 | .getText('.ng-scope:nth-child(1)', function(err, text) { 22 | assert.equal(err, null); 23 | assert.equal(text, 'This is the partial for view 1.'); 24 | done(err); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/app/app/partials/partial4.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 4.

2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webdriverjs-angular", 3 | "version": "0.1.3", 4 | "description": "WebdriverIO lib compatible with angular.js apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/webdriverio/webdriverjs-angular" 12 | }, 13 | "keywords": [ 14 | "automation", 15 | "e2e", 16 | "functional testing", 17 | "angular.js", 18 | "webdriver.js", 19 | "webdriverio", 20 | "selenium", 21 | "waitForAngular" 22 | ], 23 | "author": "vvo ", 24 | "license": "BSD-2-Clause", 25 | "bugs": { 26 | "url": "https://github.com/webdriverio/webdriverjs-angular/issues" 27 | }, 28 | "homepage": "https://github.com/webdriverio/webdriverjs-angular", 29 | "devDependencies": { 30 | "mocha": "~1.17.0" 31 | }, 32 | "dependencies": { 33 | "webdriverio": "^2.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/app/app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('myApp', [ 6 | 'ngRoute', 7 | 'myApp.filters', 8 | 'myApp.services', 9 | 'myApp.directives', 10 | 'myApp.controllers' 11 | ]). 12 | config(['$routeProvider', function($routeProvider) { 13 | $routeProvider.when('/view1', {templateUrl: 'partials/partial1.html', controller: 'MyCtrl1'}); 14 | $routeProvider.when('/view2', {templateUrl: 'partials/partial2.html', controller: 'MyCtrl2'}); 15 | $routeProvider.when('/view3', {templateUrl: 'partials/partial3.html', controller: 'MyCtrl2'}); 16 | $routeProvider.when('/view4', {templateUrl: 'partials/partial4.html', controller: 'MyCtrl2'}); 17 | $routeProvider.when('/view5', {templateUrl: 'partials/partial5.html', controller: 'MyCtrl5'}); 18 | $routeProvider.when('/view6', {templateUrl: 'partials/partial6.html', controller: 'MyCtrl6'}); 19 | $routeProvider.otherwise({redirectTo: '/view1'}); 20 | }]); 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | before_script: 7 | - "npm install -g selenium-standalone bower" 8 | - "selenium-standalone install && selenium-standalone start > /dev/null 2>&1 &" 9 | - "node test/app/scripts/web-server.js > /dev/null &" 10 | - "cd test/app && bower install && cd ../../" 11 | 12 | env: 13 | - BROWSER=phantomjs 14 | 15 | addons: 16 | sauce_connect: true 17 | # Wait for saucelab to respond on `timeouts` calls 18 | # http://support.saucelabs.com/forums/21034924-Open-Sauce-Support 19 | # env: 20 | # global: 21 | # - SAUCE_USERNAME=webdriverjs-angular 22 | # - secure: fPeUCYHS1Y/VF7rPcWCtMaRfvhzcuaTzyAa3nqsk1ERHMh+LfxjY3PspRs9EeG2p4y1YSkE5sPpobyB9uU/ColHXfGhyojVsleRQhUCl7ZZOUyGgLD5pbN2wc5lMBrK4SY5BsdfXzhbADzOSnocJQuAC6uYUJZzVZm31hfd5ZQU= 23 | # matrix: 24 | # - BROWSER=chrome 25 | # - BROWSER=firefox 26 | # - BROWSER=explorer 27 | # - BROWSER=iphone 28 | # - BROWSER=ipad 29 | # - BROWSER=phantomjs 30 | # - BROWSER=android 31 | -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | assert = require('assert'); 2 | 3 | if(process.env.TRAVIS_BUILD_NUMBER === undefined) { 4 | webdriverjsOptions = { 5 | desiredCapabilities: { 6 | browserName: process.env.BROWSER 7 | }, 8 | port: 4444 9 | } 10 | } else { 11 | // wait for saucelabs to repond to /timeouts calls 12 | // http://support.saucelabs.com/forums/21034924-Open-Sauce-Support 13 | // webdriverjsOptions = { 14 | // desiredCapabilities: { 15 | // browserName: process.env.BROWSER, 16 | // 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, 17 | // name: 'webdriverjs-angular tests', 18 | // build: process.env.TRAVIS_BUILD_NUMBER, 19 | // username: process.env.SAUCE_USERNAME, 20 | // accessKey: process.env.SAUCE_ACCESS_KEY 21 | // }, 22 | // port: 4445 23 | // } 24 | webdriverjsOptions = { 25 | desiredCapabilities: { 26 | browserName: process.env.BROWSER 27 | }, 28 | port: 4444 29 | } 30 | } 31 | 32 | webdriverjsOptions.desiredCapabilities['webdriver.remote.quietExceptions'] = true; 33 | webdriverjsOptions.host = 'localhost'; 34 | webdriverjsOptions.ngRoot = 'body'; 35 | -------------------------------------------------------------------------------- /test/app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 17 | 18 |
19 | 20 |
Angular seed app: v
21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/app/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/app/app/index-manual-bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 17 | 18 |
19 | 20 |
Angular seed app: v
21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /browser-scripts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Theses functions will be executed in the browser 3 | */ 4 | var scripts = module.exports = {}; 5 | 6 | /** 7 | * To be able to execute a regular function in the browser through selenium, 8 | * the webdriverio does a waitForAngular.toString() then sends the resulting 9 | * string-code through selenium, to the browsers. 10 | * This is why we can't have named arguments, we must access arguments with 11 | * .arguments 12 | */ 13 | scripts.waitForAngular = function(/* ngRoot, cb */) { 14 | var ngRoot = arguments[0]; 15 | var cb = arguments[1]; 16 | var el = document.querySelector(ngRoot); 17 | 18 | try { 19 | angular.element(el).injector().get('$browser'). 20 | notifyWhenNoOutstandingRequests(cb); 21 | } catch (e) { 22 | cb(e); 23 | } 24 | }; 25 | 26 | scripts.testForAngular = function(/* attempts, cb */) { 27 | var attempts = arguments[0]; 28 | var cb = arguments[1]; 29 | 30 | var done = function(res) { 31 | setTimeout(function() { 32 | cb(res); 33 | }, 0); 34 | }; 35 | 36 | check(attempts); 37 | 38 | function check(n) { 39 | if (window.angular && window.angular.resumeBootstrap) { 40 | done([null, true]); 41 | } else if (n < 1) { 42 | if (window.angular) { 43 | done([new Error('angular never provided resumeBootstrap'), false]); 44 | } else { 45 | done([new Error('retries looking for angular exceeded'), false]); 46 | } 47 | } else { 48 | window.setTimeout(check, 500, n - 1); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/spec/angular-client.js: -------------------------------------------------------------------------------- 1 | describe('an angular-compatible webdriverjs client', function() { 2 | var webdriverjsAngular = require('../../index.js'); 3 | 4 | var client; 5 | 6 | before(function(done) { 7 | client = webdriverjsAngular 8 | .remote(webdriverjsOptions) 9 | .init() 10 | .url('http://localhost:8000/test/app/app/index.html#/view1', done); 11 | }); 12 | 13 | after(function(done) { 14 | client.end(done); 15 | }); 16 | 17 | it('waits for angularjs after clicking an element', function(done) { 18 | client 19 | .click('[href="#/view2"]') 20 | .getText('.ng-scope:nth-child(1)', function(err, text) { 21 | assert.equal(err, null); 22 | assert.equal(text, 'This is the partial for view 2.'); 23 | done(err); 24 | }); 25 | }); 26 | 27 | it('waits for angular before searching for element', function(done) { 28 | client 29 | .click('[href="#/view3"]') 30 | .element('#an-element', function(err, res) { 31 | assert.equal(err, null); 32 | assert.ok(res.value); 33 | done(err); 34 | }); 35 | }); 36 | 37 | it('waits for angular before searching for elements', function(done) { 38 | client 39 | .click('[href="#/view4"]') 40 | .elements('.many-elements', function(err, res) { 41 | assert.equal(err, null); 42 | assert.ok(res.value.length > 1); 43 | done(err); 44 | }); 45 | }); 46 | 47 | it('waits for angular before searching for title', function(done) { 48 | client 49 | .click('[href="#/view5"]') 50 | .title(function(err, res) { 51 | assert.equal(err, null); 52 | assert.equal(res.value, 'HELLO MY FRIEND'); 53 | done(err); 54 | }); 55 | }); 56 | 57 | it('waits for angular after switching url manually', function(done) { 58 | client 59 | .url('http://localhost:8000/test/app/app/index.html#/view6') 60 | .execute(function() { 61 | return window.aglobal; 62 | }, function(err, res) { 63 | assert.equal(err, null); 64 | assert.equal(res.value, 'a global variable'); 65 | done(err); 66 | }); 67 | }); 68 | }); -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | __Note:__ webdriverjs-angular isn't yet compatible with WebdriverIO `v3.x`. We are currently working on it to make that happen soon. So stay tuned. 2 | 3 | # webdriverjs-angular [![Build Status](https://travis-ci.org/webdriverio/webdriverjs-angular.png?branch=master)](https://travis-ci.org/webdriverio/webdriverjs-angular) 4 | 5 | Functional test you angularjs application without having to `.pause()` or `.wait()`. 6 | 7 | Based on [WebdriverIO](http://webdriver.io), you access 8 | the same API commands but you never have to `.pause()` between actions. 9 | 10 | ## Usage 11 | 12 | ```js 13 | var webdriverjsAngular = require('webdriverjs-angular'); 14 | var options = { 15 | desiredCapabilities: { 16 | browserName: 'chrome' 17 | }, 18 | ngRoot: 'body' // main application selector 19 | }; 20 | 21 | webdriverjsAngular 22 | .remote(options) 23 | .init() 24 | .url('http://www.google.com') 25 | .title(function(err, res) { 26 | console.log('Title was: ' + res.value); 27 | }) 28 | .end(); 29 | ``` 30 | 31 | For more options, usage and API details, see 32 | [WebdriverIO](http://webdriver.io). 33 | 34 | ## Why another webdriverjs lib? 35 | 36 | 1. webdriverjs-angular is based on an existing lib, it's extending 37 | [WebdriverIO](http://webdriver.io). 38 | 39 | 2. webdriverjs-angular is designed to work well with angularJS applications. 40 | AngularJS applications have a specific behavior that needs to be taken care 41 | of to provide easy e2e testing. 42 | 43 | ## How 44 | 45 | `webdriverjs-angular` automatically waits for angularjs to [be ready](https://github.com/angular/angular.js/blob/cf686285c22d528440e173fdb65ad1052d96df3c/src/ng/browser.js#L70). 46 | 47 | So you just have to worry about what you want to do in your tests, not when 48 | to do it. 49 | 50 | ## Why not use [angular/protractor](https://github.com/angular/protractor)? 51 | 52 | Unlike [angular/protractor](https://github.com/angular/protractor) or 53 | [sebv/wd-tractor](https://github.com/sebv/wd-tractor), 54 | we do not enhance WebdriverIO API with angularJS-related 55 | command. 56 | 57 | You will not find any `elementByNgBinding`, `addMockModule`, 58 | `hasElementByNgRepeaterRow` or any other specific, angularJS-related methods. 59 | 60 | We think your functionnal tests should be as framework-agnostic as possible. 61 | 62 | If you need `elementByNgBinding`, just use regular 63 | [WebdriverIO](http://webdriver.io) 64 | commands like `.element('[ng-binding=model]')`. 65 | 66 | ## Local testing 67 | 68 | See [test/spec](test/spec). 69 | 70 | ```shell 71 | # you need multiple terminal window 72 | 73 | # start a standalone selenium server 74 | npm install selenium-standalone phantomjs -g 75 | start-selenium 76 | 77 | # start web server 78 | node test/app/scripts/web-server.js 79 | 80 | # launch tests 81 | BROWSER=phantomjs npm test 82 | ``` 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = WebdriverjsAngular; 2 | 3 | var webdriverjs = require('webdriverio/lib/webdriverio'); 4 | var inherits = require('util').inherits; 5 | var browserScripts = require('./browser-scripts.js'); 6 | 7 | function WebdriverjsAngular(options) { 8 | webdriverjs.apply(this, arguments); 9 | 10 | var client = this; 11 | var originalUrl = client.url.bind(client); 12 | 13 | patch('init', addTimeout); 14 | client.addCommand('url', function(url, cb) { 15 | if (typeof url === 'function') { 16 | return originalUrl.apply(this, arguments); 17 | } 18 | 19 | originalUrl('about:blank') 20 | .execute(function() { 21 | var url = arguments[0]; 22 | window.name = 'NG_DEFER_BOOTSTRAP!' + window.name; 23 | window.location.assign(url); 24 | }, [url]); 25 | 26 | waitForLoad(function(err) { 27 | if (err) { 28 | return cb(err); 29 | } 30 | 31 | client 32 | .executeAsync(browserScripts.testForAngular, [20], function(err, res) { 33 | if (err) { 34 | return cb(err) 35 | } 36 | 37 | client.execute(function() { 38 | angular.resumeBootstrap(); 39 | }, [], cb); 40 | }); 41 | }); 42 | 43 | function waitForLoad(cb) { 44 | var timeout; 45 | var cancelLoading = setTimeout(function hasTimeout() { 46 | timeout = true; 47 | cb(new Error('Timeout while waiting for page to load')); 48 | }, 10 * 1000); 49 | 50 | hasLoaded(function checkForLoad(err, res) { 51 | if (timeout === true) { 52 | return; 53 | } 54 | 55 | if (err) { 56 | clearTimeout(cancelLoading); 57 | return cb(err); 58 | } 59 | 60 | if (res === true) { 61 | clearTimeout(cancelLoading); 62 | return cb(null) 63 | } 64 | 65 | hasLoaded(checkForLoad); 66 | }); 67 | 68 | function hasLoaded(cb) { 69 | originalUrl(function(err, url) { 70 | if (url === 'about:blank') { 71 | return cb(err, false); 72 | } 73 | 74 | cb(err, true) 75 | }); 76 | } 77 | } 78 | 79 | }); 80 | 81 | [ 'element', 'elements', 'title'].forEach(waitForAngularBefore); 82 | 83 | ['url', 'elementIdClick'].forEach(waitForAngularAfter); 84 | 85 | function waitForAngularBefore (method) { 86 | var original = client[method]; 87 | client.addCommand(method, function(){ 88 | 89 | var originalArgs = arguments; 90 | 91 | waitForAngular(function() { 92 | original.apply(client, originalArgs); 93 | }); 94 | }); 95 | } 96 | 97 | function waitForAngularAfter (method) { 98 | patch(method, waitForAngular); 99 | } 100 | 101 | function patch(method, patchFn) { 102 | var original = client[method]; 103 | 104 | client.addCommand(method, function() { 105 | var originalArgs = Array.prototype.slice.call(arguments); 106 | var cb = originalArgs.pop(); 107 | 108 | originalArgs.push(function() { 109 | var responseArgs = arguments; 110 | 111 | patchFn(function() { 112 | cb.apply(client, responseArgs); 113 | }); 114 | }); 115 | 116 | original.apply(client, originalArgs); 117 | }); 118 | } 119 | 120 | function waitForAngular(cb) { 121 | client.executeAsync( 122 | browserScripts.waitForAngular, 123 | [options.ngRoot], 124 | cb); 125 | } 126 | 127 | function addTimeout(cb) { 128 | client.timeouts('script', 10 * 1000, cb); 129 | } 130 | } 131 | 132 | inherits(WebdriverjsAngular, webdriverjs); 133 | 134 | WebdriverjsAngular.remote = function WebdriverjsAngularRemote(options) { 135 | return require('webdriverio').remote(options, WebdriverjsAngular); 136 | } 137 | -------------------------------------------------------------------------------- /test/app/scripts/web-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var util = require('util'), 4 | http = require('http'), 5 | fs = require('fs'), 6 | url = require('url'), 7 | events = require('events'); 8 | 9 | var DEFAULT_PORT = 8000; 10 | 11 | function main(argv) { 12 | new HttpServer({ 13 | 'GET': createServlet(StaticServlet), 14 | 'HEAD': createServlet(StaticServlet) 15 | }).start(Number(argv[2]) || DEFAULT_PORT); 16 | } 17 | 18 | function escapeHtml(value) { 19 | return value.toString(). 20 | replace('<', '<'). 21 | replace('>', '>'). 22 | replace('"', '"'); 23 | } 24 | 25 | function createServlet(Class) { 26 | var servlet = new Class(); 27 | return servlet.handleRequest.bind(servlet); 28 | } 29 | 30 | /** 31 | * An Http server implementation that uses a map of methods to decide 32 | * action routing. 33 | * 34 | * @param {Object} Map of method => Handler function 35 | */ 36 | function HttpServer(handlers) { 37 | this.handlers = handlers; 38 | this.server = http.createServer(this.handleRequest_.bind(this)); 39 | } 40 | 41 | HttpServer.prototype.start = function(port) { 42 | this.port = port; 43 | this.server.listen(port); 44 | util.puts('Http Server running at http://localhost:' + port + '/'); 45 | }; 46 | 47 | HttpServer.prototype.parseUrl_ = function(urlString) { 48 | var parsed = url.parse(urlString); 49 | parsed.pathname = url.resolve('/', parsed.pathname); 50 | return url.parse(url.format(parsed), true); 51 | }; 52 | 53 | HttpServer.prototype.handleRequest_ = function(req, res) { 54 | var logEntry = req.method + ' ' + req.url; 55 | if (req.headers['user-agent']) { 56 | logEntry += ' ' + req.headers['user-agent']; 57 | } 58 | util.puts(logEntry); 59 | req.url = this.parseUrl_(req.url); 60 | var handler = this.handlers[req.method]; 61 | if (!handler) { 62 | res.writeHead(501); 63 | res.end(); 64 | } else { 65 | handler.call(this, req, res); 66 | } 67 | }; 68 | 69 | /** 70 | * Handles static content. 71 | */ 72 | function StaticServlet() {} 73 | 74 | StaticServlet.MimeMap = { 75 | 'txt': 'text/plain', 76 | 'html': 'text/html', 77 | 'css': 'text/css', 78 | 'xml': 'application/xml', 79 | 'json': 'application/json', 80 | 'js': 'application/javascript', 81 | 'jpg': 'image/jpeg', 82 | 'jpeg': 'image/jpeg', 83 | 'gif': 'image/gif', 84 | 'png': 'image/png', 85 |   'svg': 'image/svg+xml' 86 | }; 87 | 88 | StaticServlet.prototype.handleRequest = function(req, res) { 89 | var self = this; 90 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){ 91 | return String.fromCharCode(parseInt(hex, 16)); 92 | }); 93 | var parts = path.split('/'); 94 | if (parts[parts.length-1].charAt(0) === '.') 95 | return self.sendForbidden_(req, res, path); 96 | fs.stat(path, function(err, stat) { 97 | if (err) 98 | return self.sendMissing_(req, res, path); 99 | if (stat.isDirectory()) 100 | return self.sendDirectory_(req, res, path); 101 | if (/partial/.test(req.url.pathname)) 102 | return setTimeout(self.sendFile_.bind(self), 1000, req, res, path); 103 | return self.sendFile_(req, res, path); 104 | }); 105 | } 106 | 107 | StaticServlet.prototype.sendError_ = function(req, res, error) { 108 | res.writeHead(500, { 109 | 'Content-Type': 'text/html' 110 | }); 111 | res.write('\n'); 112 | res.write('Internal Server Error\n'); 113 | res.write('

Internal Server Error

'); 114 | res.write('
' + escapeHtml(util.inspect(error)) + '
'); 115 | util.puts('500 Internal Server Error'); 116 | util.puts(util.inspect(error)); 117 | }; 118 | 119 | StaticServlet.prototype.sendMissing_ = function(req, res, path) { 120 | path = path.substring(1); 121 | res.writeHead(404, { 122 | 'Content-Type': 'text/html' 123 | }); 124 | res.write('\n'); 125 | res.write('404 Not Found\n'); 126 | res.write('

Not Found

'); 127 | res.write( 128 | '

The requested URL ' + 129 | escapeHtml(path) + 130 | ' was not found on this server.

' 131 | ); 132 | res.end(); 133 | util.puts('404 Not Found: ' + path); 134 | }; 135 | 136 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) { 137 | path = path.substring(1); 138 | res.writeHead(403, { 139 | 'Content-Type': 'text/html' 140 | }); 141 | res.write('\n'); 142 | res.write('403 Forbidden\n'); 143 | res.write('

Forbidden

'); 144 | res.write( 145 | '

You do not have permission to access ' + 146 | escapeHtml(path) + ' on this server.

' 147 | ); 148 | res.end(); 149 | util.puts('403 Forbidden: ' + path); 150 | }; 151 | 152 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { 153 | res.writeHead(301, { 154 | 'Content-Type': 'text/html', 155 | 'Location': redirectUrl 156 | }); 157 | res.write('\n'); 158 | res.write('301 Moved Permanently\n'); 159 | res.write('

Moved Permanently

'); 160 | res.write( 161 | '

The document has moved here.

' 164 | ); 165 | res.end(); 166 | util.puts('301 Moved Permanently: ' + redirectUrl); 167 | }; 168 | 169 | StaticServlet.prototype.sendFile_ = function(req, res, path) { 170 | var self = this; 171 | var file = fs.createReadStream(path); 172 | res.writeHead(200, { 173 | 'Content-Type': StaticServlet. 174 | MimeMap[path.split('.').pop()] || 'text/plain' 175 | }); 176 | if (req.method === 'HEAD') { 177 | res.end(); 178 | } else { 179 | file.on('data', res.write.bind(res)); 180 | file.on('close', function() { 181 | res.end(); 182 | }); 183 | file.on('error', function(error) { 184 | self.sendError_(req, res, error); 185 | }); 186 | } 187 | }; 188 | 189 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) { 190 | var self = this; 191 | if (path.match(/[^\/]$/)) { 192 | req.url.pathname += '/'; 193 | var redirectUrl = url.format(url.parse(url.format(req.url))); 194 | return self.sendRedirect_(req, res, redirectUrl); 195 | } 196 | fs.readdir(path, function(err, files) { 197 | if (err) 198 | return self.sendError_(req, res, error); 199 | 200 | if (!files.length) 201 | return self.writeDirectoryIndex_(req, res, path, []); 202 | 203 | var remaining = files.length; 204 | files.forEach(function(fileName, index) { 205 | fs.stat(path + '/' + fileName, function(err, stat) { 206 | if (err) 207 | return self.sendError_(req, res, err); 208 | if (stat.isDirectory()) { 209 | files[index] = fileName + '/'; 210 | } 211 | if (!(--remaining)) 212 | return self.writeDirectoryIndex_(req, res, path, files); 213 | }); 214 | }); 215 | }); 216 | }; 217 | 218 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { 219 | path = path.substring(1); 220 | res.writeHead(200, { 221 | 'Content-Type': 'text/html' 222 | }); 223 | if (req.method === 'HEAD') { 224 | res.end(); 225 | return; 226 | } 227 | res.write('\n'); 228 | res.write('' + escapeHtml(path) + '\n'); 229 | res.write('\n'); 232 | res.write('

Directory: ' + escapeHtml(path) + '

'); 233 | res.write('
    '); 234 | files.forEach(function(fileName) { 235 | if (fileName.charAt(0) !== '.') { 236 | res.write('
  1. ' + 238 | escapeHtml(fileName) + '
  2. '); 239 | } 240 | }); 241 | res.write('
'); 242 | res.end(); 243 | }; 244 | 245 | // Must be last, 246 | main(process.argv); 247 | --------------------------------------------------------------------------------