Permalink
Browse files

Merge selenium grid support

It is now possible to run tests on multiple
browsers via selenium grid.
It is still possible to run tests locally via
chromedriver or phantomjs.
  • Loading branch information...
Sergey Tatarintsev
Sergey Tatarintsev committed Feb 24, 2014
2 parents 529ec62 + e097eb7 commit b4734844af512df0c2086812b7a73acb8431de2b
Showing with 222 additions and 107 deletions.
  1. +35 −3 lib/browser.js
  2. +1 −1 lib/cli/gather.js
  3. +7 −5 lib/config.js
  4. +5 −9 lib/plan.js
  5. +39 −26 lib/reporters/html-tpl.html
  6. +42 −17 lib/reporters/html.js
  7. +10 −2 lib/reporters/tree.js
  8. +25 −4 lib/runner.js
  9. +12 −32 lib/screen-shooter.js
  10. +26 −0 lib/state.js
  11. +19 −7 lib/tester.js
  12. +1 −1 package.json
View
@@ -2,13 +2,24 @@
var inherit = require('inherit'),
webdriver = require('selenium-webdriver'),
+ elementRect = require('./element-rect'),
Image = require('./image'),
By = webdriver.By;
module.exports = inherit({
- __constructor: function() {
- this._driver = new webdriver.Builder()
- .withCapabilities(webdriver.Capabilities.phantomjs())
+ __constructor: function(config, name) {
+ this.name = name;
+ this.config = config;
+ var builder = new webdriver.Builder();
+
+ if (config.gridUrl) {
+ builder.usingServer(config.gridUrl);
+ }
+
+ var capabilities = webdriver.Capabilities[name];
+
+ this._driver = builder
+ .withCapabilities(capabilities())
.build();
},
@@ -28,6 +39,27 @@ module.exports = inherit({
return this._driver.findElement(By.css(selector));
},
+ captureState: function (state) {
+ var _this = this;
+ return this.open(this.config.getAbsoluteUrl(state.getUrl()))
+ .then(function() {
+ return _this.findElements(state.getElementsSelectors());
+ })
+ .then(function(elements) {
+ return state.activate(_this, elements)
+ .then(function() {
+ return _this.takeScreenshot();
+ })
+ .then(function(image) {
+ return elementRect.getMultiple(elements)
+ .then(function(rect) {
+ return image.crop(rect);
+ });
+ });
+ });
+
+ },
+
takeScreenshot: function() {
return this._driver.takeScreenshot()
.then(function (base64) {
View
@@ -21,7 +21,7 @@ module.exports = function() {
var shooter = new ScreenShooter(config);
shooter.on('endCapture', function(data) {
- console.log(chalk.blue(data.name) + ': ' + chalk.green(data.state));
+ console.log(chalk.blue(data.plan) + ': ' + chalk.green(data.state));
});
return shooter.runPlans(plans);
View
@@ -12,6 +12,9 @@ var Config = module.exports = inherit({
var config = yaml.load(configText);
this.root = path.dirname(configPath);
this.rootUrl = config.rootUrl;
+ this.gridUrl = config.gridUrl;
+ this.browsers = config.browsers || ['phantomjs'];
+
this.screenshotsDir = path.resolve(this.root,
config.screenshotsDir || DEFAULT_SCREENSHOTS_DIR);
},
@@ -20,14 +23,13 @@ var Config = module.exports = inherit({
return url.resolve(this.rootUrl, relUrl);
},
- getScreenshotsDir: function(name) {
- return path.resolve(this.screenshotsDir, name);
+ getScreenshotsDir: function(name, state) {
+ return path.resolve(this.screenshotsDir, name, state);
},
- getScreenshotPath: function getScrenshotPath(name, state) {
- return path.join(this.getScreenshotsDir(name), state + '.png');
+ getScreenshotPath: function getScrenshotPath(name, state, browser) {
+ return path.join(this.getScreenshotsDir(name, state), browser + '.png');
}
-
}, {
read: function read(configPath) {
return fs.read(configPath).then(function(content) {
View
@@ -2,15 +2,16 @@
var path = require('path'),
q = require('q'),
- inherit = require('inherit');
+ inherit = require('inherit'),
+ State = require('./state');
function nothing() {
return q.resolve();
}
var Plan = module.exports = inherit({
__constructor: function() {
- this._states = {};
+ this._states = [];
},
setName: function setName(name) {
@@ -30,18 +31,13 @@ var Plan = module.exports = inherit({
addState: function state(name, cb) {
cb = cb || nothing;
- this._states[name] = cb;
+ this._states.push(new State(this, name, cb));
return this;
},
getStates: function getStates() {
- return Object.keys(this._states);
- },
-
- toState: function toState(name, actions, elements) {
- return this._states[name].call(this, actions, elements);
+ return this._states;
}
-
}, {
read: function(file) {
var plan = new Plan();
@@ -3,6 +3,14 @@
<head>
<title>Shooter report</title>
<style>
+ .plan {
+ padding-left: 30px;
+ }
+
+ .state {
+ padding-left: 30px;
+ }
+
.success::before {
content: '\2713 ';
color: green;
@@ -27,32 +35,37 @@
<body>
<h1>Shooter report</h1>
- <% plans.forEach(function(plan) { %>
- <h2><%- plan.name %></h2>
- <% plan.tests.forEach(function(test) { %>
- <h3 class="<%= test.equal? 'success' : 'fail' %>"><%- test.state %></h3>
- <% if (test.equal) { %>
- <img src="<%= attach(plan.name, test.state, 'current') %>">
- <%} else {%>
- <div class="image-box">
- <div>Reference</div>
- <img class="ref-image" src="<%= attach(plan.name, test.state, 'ref') %>">
- </div>
-
-
- <div class="image-box">
- <div>Current</div>
- <img class="current-image" src="<%= attach(plan.name, test.state, 'current') %>">
- </div>
-
-
- <div class="image-box">
- <div>Diff</div>
- <img class="ref-image" src="<%= attach(plan.name, test.state, 'diff') %>">
- </div>
+ {{#each plans}}
+ <h2>{{name}}</h2>
+ <div class="plan">
+ {{#each states}}
+ <h3>{{name}}</h3>
+ <div class="state">
+ {{#each browsers}}
+ <h4 class="{{status}}">{{browser}}</h4>
+ <div class="browser">
+ {{#if equal}}
+ {{image "current"}}
+ {{else}}
+ <div class="image-box">
+ <div class="image-title">Reference</div>
+ {{image "ref"}}
+ </div>
+ <div class="image-box">
+ <div class="image-title">Current</div>
+ {{image "current"}}
+ </div>
+ <div class="image-box">
+ <div class="image-title">Diff</div>
+ {{image "diff"}}
+ </div>
- <%} %>
- <%});%>
- <% });%>
+ {{/if}}
+ </div>
+ {{/each}}
+ </div>
+ {{/each}}
+ </div>
+ {{/each}}
</body>
</html>
View
@@ -2,24 +2,41 @@
var fs = require('q-io/fs'),
path = require('path'),
- _ = require('lodash'),
+ Handlebars = require('handlebars'),
Image = require('../image');
var REPORT_DIR = 'shooter-report',
REPORT_INDEX = path.join(REPORT_DIR, 'index.html'),
REPORT_ATTACHMENTS = path.join(REPORT_DIR, 'attach');
-function attachmentsPath(plan) {
- return path.join(REPORT_ATTACHMENTS, plan);
+function attachmentStateDir(planName, stateName) {
+ return path.join('attach', planName, stateName);
}
-function attachmentPath(plan, state, kind) {
- return path.join(attachmentsPath(plan), state + '~' + kind + '.png');
+function attachmentPath(result, kind) {
+ return path.resolve(REPORT_DIR, attachmentRelPath(result, kind));
}
+// Path to attachments relative to root report
+function attachmentRelPath(result, kind) {
+ return path.join(attachmentStateDir(result.plan, result.state), result.browser + '~' + kind + '.png');
+}
+
+Handlebars.registerHelper('status', function() {
+ return this.equal? 'success' : 'fail';
+});
+
+Handlebars.registerHelper('image', function(kind) {
+ return new Handlebars.SafeString(
+ '<img src="' +
+ encodeURIComponent(attachmentRelPath(this, kind)) +
+ '">');
+});
+
module.exports = function htmlReporter(tester) {
var plans,
currentPlan,
+ currentState,
attachmentsQueue;
tester.on('beginTests', function() {
@@ -28,27 +45,35 @@ module.exports = function htmlReporter(tester) {
});
tester.on('beginPlan', function(plan) {
- currentPlan = {name: plan, tests: []};
+ currentPlan = {name: plan, states: []};
plans.push(currentPlan);
+ });
+
+ tester.on('beginState', function(state) {
+ currentState = {name: state, browsers: []};
+ currentPlan.states.push(currentState);
attachmentsQueue.then(function() {
- return fs.makeDirectory(attachmentsPath(plan));
+ var dir = path.resolve(REPORT_DIR, attachmentStateDir(currentPlan.name, currentState.name));
+ return fs.makeTree(dir);
});
});
tester.on('endTest', function(result) {
- result.diffPath = attachmentPath(result.name, result.state, 'diff');
- currentPlan.tests.push(result);
+ result.diffPath = attachmentPath(result, 'diff');
+ currentState.browsers.push(result);
+
attachmentsQueue.then(function() {
- var copyCurrent = fs.copy(result.currentPath, attachmentPath(result.name, result.state, 'current'));
+ var copyCurrent = fs.copy(result.currentPath, attachmentPath(result, 'current'));
if (result.equal) {
return copyCurrent;
}
return copyCurrent
.then(function() {
- return fs.copy(result.previousPath, attachmentPath(result.name, result.state, 'ref'));
+ return fs.copy(result.previousPath, attachmentPath(result, 'ref'));
})
.then(function() {
- return Image.buildDiff(result.previousPath, result.currentPath, result.diffPath);
+ return Image.buildDiff(result.previousPath, result.currentPath,
+ attachmentPath(result, 'diff'));
})
.done();
});
@@ -60,12 +85,12 @@ module.exports = function htmlReporter(tester) {
return fs.read(path.resolve(__dirname, './html-tpl.html'));
})
.then(function(tpl) {
- var tplFunc = _.template(tpl);
+ var tplFunc = Handlebars.compile(tpl);
return tplFunc({
- plans: plans,
- attach: function(plan, state, kind) {
- return encodeURIComponent(path.join('attach', plan, state + '~' + kind + '.png'));
- }
+ plans: plans
+ //attach: function(result, kind) {
+ //return encodeURIComponent(attachmentRelPath(result, kind));
+ //}
});
})
.then(function(index) {
View
@@ -6,6 +6,10 @@ var chalk = require('chalk');
var CHECK = '\u2713',
XMARK = '\u2718';
+function tab(number) {
+ return new Array(number + 1).join(' ');
+}
+
module.exports = function treeReporter(tester) {
var failed, passed;
@@ -17,12 +21,16 @@ module.exports = function treeReporter(tester) {
console.log(chalk.underline(planName));
});
+ tester.on('beginState', function(state) {
+ console.log(tab(1) + state);
+ });
+
tester.on('endTest', function(result) {
if (result.equal) {
- console.log(' ' + chalk.green(CHECK) + ' ' + result.state);
+ console.log(tab(2) + chalk.green(CHECK) + ' ' + result.browser);
passed++;
} else {
- console.log(' ' + chalk.red(XMARK + ' ' + result.state));
+ console.log(tab(2) + chalk.red(XMARK + ' ' + result.browser));
failed++;
}
});
View
@@ -3,7 +3,9 @@ var EventEmitter = require('events').EventEmitter,
q = require('q'),
inherit = require('inherit'),
- promiseUtils = require('./promise-util');
+ promiseUtils = require('./promise-util'),
+
+ Browser = require('./browser');
module.exports = inherit(EventEmitter, {
@@ -30,7 +32,7 @@ module.exports = inherit(EventEmitter, {
runPlan: function(plan) {
var states = plan.getStates(),
- runState = this._runState.bind(this, plan);
+ runState = this._runState.bind(this);
return this._beforePlan(plan)
.then(function() {
return promiseUtils.seqMap(states, runState);
@@ -46,7 +48,26 @@ module.exports = inherit(EventEmitter, {
return q.resolve();
},
- _runState: function(plan, state) {
+ _runState: function(state) {
+ var _this = this;
+ return this._beforeState(state)
+ .then(function() {
+ return promiseUtils.seqMap(_this.config.browsers, function(browser) {
+ return _this._runStateInBrowser(state, new Browser(_this.config, browser));
+ });
+ })
+ .then(this._afterState.bind(this, state));
+ },
+
+ _beforeState: function(state) {
return q.resolve();
- }
+ },
+
+ _afterState: function(state) {
+ return q.resolve();
+ },
+
+ _runStateInBrowser: function(state, browser) {
+ return q.resolve();
+ },
});
Oops, something went wrong.

0 comments on commit b473484

Please sign in to comment.