├── 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 |
3 | many elements
4 | many elements
5 | many elements
6 | many elements
7 | many elements
8 | many elements
9 | many elements
10 | many elements
11 | many elements
12 | many elements
13 | many elements
14 | many elements
15 | many elements
16 | many elements
17 | many elements
18 | many elements
19 |
--------------------------------------------------------------------------------
/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 [](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('' +
238 | escapeHtml(fileName) + ' ');
239 | }
240 | });
241 | res.write(' ');
242 | res.end();
243 | };
244 |
245 | // Must be last,
246 | main(process.argv);
247 |
--------------------------------------------------------------------------------