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

Commit

Permalink
Merge pull request #166 from gemini-testing/feature/sizzle
Browse files Browse the repository at this point in the history
Allow to use Sizzle for browsers without CSS3
  • Loading branch information
Sergej Tatarincev committed May 18, 2015
2 parents 9ea8107 + a57069f commit 2ea871a
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 115 deletions.
82 changes: 82 additions & 0 deletions lib/browser/client-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

var util = require('util'),
q = require('q'),

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

NO_CLIENT_FUNC = 'ERRNOFUNC';

/**
* @param {Browser} browser
* @param {String} script
* @constructor
*/
function ClientBridge(browser, script) {
this._browser = browser;
this._script = script;
}

/**
* @param {String} name
* @param {Array} [args]
*/
ClientBridge.prototype.call = function(name, args) {
args = args || [];
return this._callCommand(this._clientMethodCommand(name, args), true);
};

/**
* @param {String} command
* @param {Boolean} shouldInject
*/
ClientBridge.prototype._callCommand = function(command, injectAllowed) {
var _this = this;
return this._browser.evalScript(command)
.then(function(result) {
if (!result || !result.error) {
return q.resolve(result);
}

if (result.error !== NO_CLIENT_FUNC) {
return q.reject(new StateError(result.message));
}

if (injectAllowed) {
return _this._inject()
.then(function() {
return _this._callCommand(command, false);
});
}
return q.reject(new StateError('Unable to inject gemini client script'));
});
};

/**
* @param {String} name
* @param {Array} args
*/
ClientBridge.prototype._clientMethodCommand = function(name, args) {
var call = util.format('__gemini.%s(%s)',
name,
args.map(JSON.stringify).join(', ')
);
return this._guardClientCall(call);
};

/**
* @param {String} call
*/
ClientBridge.prototype._guardClientCall = function(call) {
return util.format(
'typeof __gemini !== "undefined"? %s : {error: "%s"})',
call,
NO_CLIENT_FUNC
);
};

ClientBridge.prototype._inject = function() {
return this._browser.evalScript(this._script);
};

module.exports = ClientBridge;
38 changes: 28 additions & 10 deletions lib/browser/client-scripts/gemini.calibrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,34 @@
document.body.appendChild(div);
}

var bodyStyle = document.body.style;
bodyStyle.margin = 0;
bodyStyle.padding = 0;
function createPattern() {
var bodyStyle = document.body.style;
bodyStyle.margin = 0;
bodyStyle.padding = 0;

if (needsResetBorder()) {
bodyStyle.border = 0;
if (needsResetBorder()) {
bodyStyle.border = 0;
}
bodyStyle.backgroundColor = '#00ff00';
bodyStyle.width = '100%';
bodyStyle.height = '100%';
createRedStripe('left');
createRedStripe('right');
}
bodyStyle.backgroundColor = '#00ff00';
bodyStyle.width = '100%';
bodyStyle.height = '100%';
createRedStripe('left');
createRedStripe('right');

function getBrowserFeatures() {
var features = {
hasCSS3Selectors: true
};
try {
document.querySelector('body:nth-child(1)');
} catch (e) {
features.hasCSS3Selectors = false;
}

return features;
}

createPattern();
return getBrowserFeatures();
}(window));
7 changes: 4 additions & 3 deletions lib/browser/client-scripts/gemini.coverage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

var util = require('./util'),
rect = require('./rect');
rect = require('./rect'),
query = require('./query');

exports.collectCoverage = function collectCoverage(rect) {
var coverage = {},
Expand Down Expand Up @@ -66,14 +67,14 @@ function coverageForRule(rule, area, ctx) {

util.each(rule.selectorText.split(','), function(selector) {
var within,
matches = document.querySelectorAll(selector);
matches = query.all(selector);

selector = selector.trim();

var re = /:{1,2}(?:after|before|first-letter|first-line|selection)(:{1,2}\w+)?$/;
// if selector contains pseudo-elements cut it off and try to find element without it
if (matches.length === 0 && re.test(selector)) {
matches = document.querySelectorAll(selector.replace(re, '$1'));
matches = query.all(selector.replace(re, '$1'));
}

if (matches.length > 0) {
Expand Down
7 changes: 5 additions & 2 deletions lib/browser/client-scripts/gemini.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

var util = require('./util'),
rect = require('./rect'),
query = require('./query'),
Rect = rect.Rect;

global.__gemini = exports;

exports.query = query;

// Terminology
// - clientRect - the result of calling getBoundingClientRect of the element
// - extRect - clientRect + outline + box shadow
Expand Down Expand Up @@ -69,7 +72,7 @@ function prepareScreenshotUnsafe(selectors, opts) {
function getCaptureRect(selectors) {
var element, elementRect, rect;
for (var i = 0; i < selectors.length; i++) {
element = document.querySelector(selectors[i]);
element = query.first(selectors[i]);
if (!element) {
return {
error: 'NOTFOUND',
Expand All @@ -89,7 +92,7 @@ function getCaptureRect(selectors) {
function findIgnoreAreas(selectors) {
var result = [];
util.each(selectors, function(selector) {
var element = document.querySelector(selector);
var element = query.first(selector);
if (element) {
result.push(getElementCaptureRect(element).round().serialize());
}
Expand Down
9 changes: 9 additions & 0 deletions lib/browser/client-scripts/query.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

exports.first = function(selector) {
return document.querySelector(selector);
};

exports.all = function(selector) {
return document.querySelectorAll(selector);
};
12 changes: 12 additions & 0 deletions lib/browser/client-scripts/query.sizzle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';
/*jshint newcap:false*/
var Sizzle = require('sizzle');

exports.first = function(selector) {
var elems = Sizzle(selector + ':first');
return elems.length > 0? elems[0] : null;
};

exports.all = function(selector) {
return Sizzle(selector);
};
92 changes: 52 additions & 40 deletions lib/browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ var path = require('path'),
Image = require('../image'),
Actions = require('./actions'),

GeminiError = require('../errors/gemini-error'),
StateError = require('../errors/state-error');
ClientBridge = require('./client-bridge'),

module.exports = inherit({
GeminiError = require('../errors/gemini-error');

var Browser = inherit({
__constructor: function(config, id) {
this.config = config;
this._capabilities = config.browsers[id];
Expand Down Expand Up @@ -81,6 +82,9 @@ module.exports = inherit({
.then(function() {
return _this.buildScripts();
})
.then(function() {
return _this.chooseLocator();
})
.fail(function(e) {
if (e.code === 'ECONNREFUSED') {
return q.reject(new GeminiError(
Expand Down Expand Up @@ -132,12 +136,9 @@ module.exports = inherit({
return this._browser.get(url);
},

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

inject: function(script) {
return this._browser.execute(script);
evalScript: function(script) {
/*jshint evil:true*/
return this._browser.eval(script);
},

buildScripts: function() {
Expand All @@ -151,16 +152,32 @@ module.exports = inherit({
}

script.transform({sourcemap: false, global: true}, 'uglifyify');
var queryLib = this._needsSizzle? './query.sizzle.js' : './query.native.js';
script.transform({
aliases: {
'./query': {relative: queryLib}
},
verbose: false
}, 'aliasify');

var _this = this;

return q.nfcall(script.bundle.bind(script))
.then(function(buf) {
_this._scripts = _this._polyfill + '\n' + buf.toString();
return _this._scripts;
var scripts = _this._polyfill + '\n' + buf.toString();
_this._clientBridge = new ClientBridge(_this, scripts);
return scripts;
});
},

get _needsSizzle() {
return this._calibration && !this._calibration.hasCSS3Selectors;
},

chooseLocator: function() {
this.findElement = this._needsSizzle? this._findElementScript : this._findElementWd;
},

reset: function() {
var _this = this;
return this.findElement('body')
Expand Down Expand Up @@ -193,49 +210,40 @@ module.exports = inherit({
});
},

_findElements: function(selectorsList) {
var _this = this;
return q.all(selectorsList.map(function(selector) {
return _this.findElement(selector, true);
}));
findElement: function(selector) {
throw new Error('findElement is called before appropriate locator is chosen');
},

findElement: function(selector) {
_findElementWd: function(selector) {
return this._browser.elementByCssSelector(selector)
.then(function(wdElement) {
return wdElement;
})
.fail(function(error) {
if (error.status === 7) {
if (error.status === Browser.ELEMENT_NOT_FOUND) {
error.selector = selector;
}
return q.reject(error);
});
},

prepareScreenshot: function(selectors, opts) {
/*jshint evil:true*/
var _this = this;
opts = opts || {};
return this._browser.eval(this._prepareScreenshotCommand(selectors, opts))
.then(function(data) {
if (data.error) {
if (data.error !== 'ERRNOFUNC') {
return q.reject(new StateError(data.message));
}

return _this.injectScripts()
.then(function() {
return _this.prepareScreenshot(selectors, opts);
});
_findElementScript: function(selector) {
return this._clientBridge.call('query.first', [selector])
.then(function(element) {
if (element) {
return element;
}
return q.resolve(data);

var error = new Error('Unable to find element');
error.status = Browser.ELEMENT_NOT_FOUND;
error.selector = selector;
return q.reject(error);
});
},

_prepareScreenshotCommand: function(selectors, opts) {
return 'typeof(__gemini) !== "undefined"? __gemini.prepareScreenshot(' + JSON.stringify(selectors) + ', ' +
JSON.stringify(opts) + ') : {error: "ERRNOFUNC"}';
prepareScreenshot: function(selectors, opts) {
opts = opts || {};
return this._clientBridge.call('prepareScreenshot', [
selectors,
opts
]);
},

captureFullscreenImage: function() {
Expand Down Expand Up @@ -264,4 +272,8 @@ module.exports = inherit({
return new Actions(this);
}

}, {
ELEMENT_NOT_FOUND: 7
});

module.exports = Browser;
Loading

0 comments on commit 2ea871a

Please sign in to comment.