Skip to content
This repository has been archived by the owner on Sep 21, 2022. It is now read-only.

Commit

Permalink
Merge 332a54f into 8a89a6a
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergej Tatarincev committed Apr 16, 2015
2 parents 8a89a6a + 332a54f commit f3f2bfe
Show file tree
Hide file tree
Showing 23 changed files with 761 additions and 284 deletions.
3 changes: 3 additions & 0 deletions doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ on Selenium Grid.
limit on a number of browser that can be run once at a time. Use this
option to limit the number of browsers that `gemini` will try to run in
parallel.
* `sessionMode` — allows to choose how to launch new WebDriver sessions:
- `perBrowser` (default) — launch 1 session per browser. This session will be reused for all of the suites.
- `perSuite` — launch 1 new session for each browser per suite and finish the session when the suite ends.
* `strictComparison` – test will be considered as failed in case of any kind of error. By default, only noticeable differences are treated
as test failure.
* `referenceImageAbsence` – treat reference image as `error` or `warning`. Default value is `error`.
Expand Down
3 changes: 3 additions & 0 deletions doc/config.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ browsers:
* `debug` (CLI: `--debug`, env: `GEMINI_DEBUG`) – включить отладочный вывод в терминал.
* `parallelLimit` – число браузеров запускаемых `Gemini` параллельно.
По умолчанию запускаются все, но иногда (например, при использовании облачных сервисов вроде Sauce Labs) это число необходимо контролировать.
* `sessionMode` — позволяет указать, как именно запускать новые сессии WebDriver:
- `perBrowser` (по умолчанию) — запуск одной сессии для каждого браузера. Эта сессия будет переиспользована для всех наборов.
- `perSuite` — запуск 1 сессия в каждом браузере для каждого набора. Сессия будет закрыта как только набор протестирован.
* `strictComparison` – считать тест провальным в случае любого рода ошибки. По умолчанию, только заметные различия считаются провалом теста.
* `referenceImageAbsence` – считать отсутствие эталонного шаблона предупреждением (`warning`) или ошибкой (`error`). По-умолчанию ошибка (`error`).
* `tolerance` - определяет предельно допустимое различие между цветами по метрике [CIEDE2000](https://ru.wikipedia.org/wiki/%D4%EE%F0%EC%F3%EB%E0_%F6%E2%E5%F2%EE%E2%EE%E3%EE_%EE%F2%EB%E8%F7%E8%FF#CIEDE2000).
Expand Down
28 changes: 28 additions & 0 deletions lib/browser-pool/basic-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
/**
* @constructor
*/
function BasicPool() {
}

/**
* @param {String} id
* @returns {Promise.<Browser>}
*/
BasicPool.prototype.getBrowser = function(id) {
};

/**
* @param {Browser} browser
* @returns {Promise}
*/
BasicPool.prototype.freeBrowser = function(browser) {
};

/**
* @param {String} id
*/
BasicPool.prototype.finalizeBrowsers = function(id) {
};

module.exports = BasicPool;
46 changes: 46 additions & 0 deletions lib/browser-pool/caching-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';
var util = require('util'),
q = require('q'),
_ = require('lodash'),
BasicPool = require('./basic-pool');

/**
* @constructor
* @extends BasicPool
* @param {BasicPool} underlyingPool
*/
function CachingPool(underlyingPool) {
this._pool = underlyingPool;
this._freeBrowsers = {};
}

util.inherits(CachingPool, BasicPool);

CachingPool.prototype.getBrowser = function(id) {
if (_.isEmpty(this._freeBrowsers[id])) {
return this._pool.getBrowser(id);
}

var browser = this._freeBrowsers[id].pop();
return browser.reset().thenResolve(browser);
};

CachingPool.prototype.freeBrowser = function(browser) {
var _this = this;
return q.fcall(function() {
var id = browser.id;
_this._freeBrowsers[id] = _this._freeBrowsers[id] || [];
_this._freeBrowsers[id].push(browser);
});
};

CachingPool.prototype.finalizeBrowsers = function(id) {
if (_.isEmpty(this._freeBrowsers[id])) {
return q();
}
return q.all(this._freeBrowsers[id]
.map(this._pool.freeBrowser.bind(this._pool))
);
};

module.exports = CachingPool;
14 changes: 14 additions & 0 deletions lib/browser-pool/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';
var LimitedPool = require('./limited-pool'),
UnlimitedPool = require('./unlimited-pool'),
CachingPool = require('./caching-pool'),
Calibrator = require('../calibrator');

exports.create = function(config) {
var calibrator = new Calibrator(),
pool = config.parallelLimit? new LimitedPool(config, calibrator) : new UnlimitedPool(config, calibrator);
if (config.sessionMode === 'perBrowser') {
return new CachingPool(pool);
}
return pool;
};
71 changes: 71 additions & 0 deletions lib/browser-pool/limited-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';
var util = require('util'),
Browser = require('../browser'),
BasicPool = require('./basic-pool'),
q = require('q');

/**
* @constructor
* @extends BasicPool
* @param {Config} config
* @param {Calibrator} calibrator
*/
function LimitedPool(config, calibrator) {
this._calibrator = calibrator;
this._config = config;
this._launched = 0;
this._deferQueue = [];
}

util.inherits(LimitedPool, BasicPool);

LimitedPool.prototype.getBrowser = function(id) {
if (this._canLaunchBrowser) {
this._launched++;
return this._newBrowser(id);
}
var defer = q.defer();
this._deferQueue.unshift({
id: id,
defer: defer
});
return defer.promise;
};

Object.defineProperty(LimitedPool.prototype, '_canLaunchBrowser', {
get: function() {
return this._launched < this._config.parallelLimit;
}
});

/**
* @param {String} id
* @returns {Promise.<Browser>}
*/
LimitedPool.prototype._newBrowser = function(id) {
var browser = new Browser(this._config, id, this._config.browsers[id]);
return browser.launch(this._calibrator).thenResolve(browser);
};

LimitedPool.prototype.freeBrowser = function(browser) {
var _this = this;
return browser.quit()
.then(function() {
return _this._launchNextBrowser();
});
};

/**
* @returns {Boolean}
*/
LimitedPool.prototype._launchNextBrowser = function() {
var queued = this._deferQueue.pop();
if (queued) {
return this._newBrowser(queued.id)
.then(queued.defer.resolve);
} else {
this._launched--;
}
};

module.exports = LimitedPool;
28 changes: 28 additions & 0 deletions lib/browser-pool/unlimited-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
var util = require('util'),
Browser = require('../browser'),
BasicPool = require('./basic-pool');

/**
* @constructor
* @extends BasicPool
* @param {Config} config
* @param {Calibrator} calibrator
*/
function UnlimitedPool(config, calibrator) {
this._config = config;
this._calibrator = calibrator;
}

util.inherits(UnlimitedPool, BasicPool);

UnlimitedPool.prototype.getBrowser = function(id) {
var browser = new Browser(this._config, id);
return browser.launch(this._calibrator).thenResolve(browser);
};

UnlimitedPool.prototype.freeBrowser = function(browser) {
return browser.quit();
};

module.exports = UnlimitedPool;
102 changes: 18 additions & 84 deletions lib/browser/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

var fs = require('fs'),
path = require('path'),
var path = require('path'),
util = require('util'),
inherit = require('inherit'),
wd = require('wd'),
Expand All @@ -14,14 +13,12 @@ var fs = require('fs'),
Actions = require('./actions'),

GeminiError = require('../errors/gemini-error'),
StateError = require('../errors/state-error'),

clientScriptCalibrate = fs.readFileSync(path.join(__dirname, 'client-scripts', 'gemini.calibrate.js'), 'utf8');
StateError = require('../errors/state-error');

module.exports = inherit({
__constructor: function(config, id, capabilities) {
__constructor: function(config, id) {
this.config = config;
this._capabilities = capabilities;
this._capabilities = config.browsers[id];
this.id = id;
this._browser = wd.promiseRemote(config.gridUrl);

Expand Down Expand Up @@ -51,7 +48,7 @@ module.exports = inherit({
}
},

launch: function() {
launch: function(calibrator) {
var _this = this;
return this._browser
.configureHttp(_this.config.http)
Expand All @@ -74,9 +71,13 @@ module.exports = inherit({
}
})
.then(function() {
if (!_this._capabilities['--noCalibrate'] && !_this._calibration) {
return _this.calibrate();
if (_this._capabilities['--noCalibrate'] || _this._calibration) {
return;
}
return calibrator.calibrate(_this)
.then(function(result) {
_this._calibration = result;
});
})
.then(function() {
return _this._buildPolyfills();
Expand Down Expand Up @@ -122,15 +123,15 @@ module.exports = inherit({
},

open: function(url) {
var _this = this;
return this._resetCursor()
.then(function() {
return _this._browser.get(url);
});
return this._browser.get(url);
},

injectScripts: function() {
return this._browser.execute(this._scripts);
return this.inject(this._scripts);
},

inject: function(script) {
return this._browser.execute(script);
},

buildScripts: function() {
Expand All @@ -154,74 +155,7 @@ module.exports = inherit({
});
},

/**
* We need this to determine body bounds within captured screenshot for the cases like IE8 and selendroid
* where captured image contains some area outside of the html body. The function determines what area should be
* cropped.
*/
calibrate: function() {
var _this = this;
return this._browser.get('about:blank')
.then(function() {
return _this._browser.execute(clientScriptCalibrate);
})
.then(function() {
return _this.captureFullscreenImage();
})
.then(function(image) {
var find = ['#00ff00', '#00ff00', '#00ff00', '#ff0000'];

/**
* Compare pixel with given x,y to given pixel in the pattern
* @param {Object} coords
* @param {Object} positionData position data
* @param [Boolean] reverse should be true when traversing x from right to left
*/
function checkPattern(coords, positionData, reverse) {
if (Image.RGBToString(image.getRGBA(coords.x, coords.y)) === find[positionData.u]) {
if (++positionData.u === find.length) {
positionData.pos = {x: coords.x - (reverse? -1 : 1) * (find.length - 1), y: coords.y};
}

return;
}

positionData.u = 0;
}

var width = image.getSize().width,
height = image.getSize().height,
start = {u: 0},
end = {u: 0};

for (var y = 0; y < height && (!start.pos || !end.pos); y++) {
for (var x = 0; x < width; x++) {
if (!start.pos) {
checkPattern({x: x, y: y}, start);
}
if (!end.pos) {
checkPattern({x: width - x - 1, y: height - y - 1}, end, true);
}
}
}

if (!start.pos || !end.pos) {
return q.reject(new GeminiError(
'Could not calibrate. This could be due to calibration page has failed to open properly'
));
}

_this._calibration = {
top: start.pos.y,
left: start.pos.x,
right: width - 1 - end.pos.x,
bottom: height - 1 - end.pos.y
};
return _this._calibration;
});
},

_resetCursor: function() {
reset: function() {
var _this = this;
return this.findElement('body')
.then(function(body) {
Expand Down

0 comments on commit f3f2bfe

Please sign in to comment.