gemini
Version:
UI Screenshot testing utility
279 lines (244 loc) • 9.11 kB
JavaScript
;
const _ = require('lodash');
const url = require('url');
const option = require('gemini-configparser').option;
const GeminiError = require('../errors/gemini-error');
const util = require('./util');
const assertNonNegative = util.assertNonNegative;
const booleanOption = util.booleanOption;
const is = util.is;
const positiveIntegerOption = util.positiveIntegerOption;
const resolveWithProjectRoot = util.resolveWithProjectRoot;
const isOptionalObject = (value) => value === null || _.isPlainObject(value);
const getTopLevel = () => {
const defaults = {
gridUrl: 'http://localhost:4444/wd/hub',
calibrate: true,
httpTimeout: 'default',
sessionRequestTimeout: null,
sessionQuitTimeout: null,
screenshotsDir: 'gemini/screens',
tolerance: 2.3,
antialiasingTolerance: 0,
compareOpts: {
shouldCluster: false,
clustersSize: 10,
stopOnFirstFail: false
},
buildDiffOpts: {
ignoreAntialiasing: true,
ignoreCaret: true
},
sessionsPerBrowser: 1,
suitesPerSession: Infinity,
windowSize: null,
retry: 0,
screenshotMode: 'auto',
compositeImage: false,
screenshotDelay: 0,
orientation: null
};
const provideDefault = (key) => defaults[key];
return buildBrowserOptions(provideDefault, {
desiredCapabilities: option({
defaultValue: null,
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: (value) => {
if (!isOptionalObject(value)) {
throw new GeminiError('Top-level desiredCapabilities should be null or object');
}
}
})
});
};
const provideTopLevelDefault = (name) => {
return (config) => {
const value = config[name];
if (_.isUndefined(value)) {
throw new GeminiError(`${name} should be set at top level or per-browser option`);
}
return value;
};
};
const getPerBrowser = () => {
return buildBrowserOptions(provideTopLevelDefault, {
desiredCapabilities: option({
parseEnv: JSON.parse,
parseCli: JSON.parse,
defaultValue: null,
validate: (value, config) => {
if (value === null && config.desiredCapabilities === null) {
throw new GeminiError('Browser must have desired capabilities set');
} else if (!isOptionalObject(value)) {
throw new GeminiError('desiredCapabilities should be null or object');
}
},
map: (value, config) => _.extend({}, config.desiredCapabilities, value)
})
});
};
const parseTimeout = (value) => value === 'default' ? value : +value;
function buildBrowserOptions(defaultFactory, extra) {
return _.extend(extra, {
rootUrl: option({
validate: is('string'),
defaultValue: defaultFactory('rootUrl'),
map: (value, config) => {
return config.rootUrl && !value.match(/^https?:\/\//)
? url.resolve(config.rootUrl, value.replace(/^\//, ''))
: value;
}
}),
gridUrl: option({
validate: is('string'),
defaultValue: defaultFactory('gridUrl')
}),
calibrate: booleanOption(defaultFactory('calibrate')),
httpTimeout: option({
parseEnv: parseTimeout,
parseCli: parseTimeout,
validate: (value) => {
if (value === 'default') {
return;
}
assertNonNegative(value, 'httpTimeout');
},
defaultValue: defaultFactory('httpTimeout')
}),
sessionRequestTimeout: option({
parseEnv: Number,
parseCli: Number,
validate: (value) => {
if (_.isNull(value)) {
return;
}
assertNonNegative(value, 'sessionRequestTimeout');
},
defaultValue: defaultFactory('sessionRequestTimeout')
}),
sessionQuitTimeout: option({
parseEnv: Number,
parseCli: Number,
validate: (value) => {
if (_.isNull(value)) {
return;
}
assertNonNegative(value, 'sessionQuitTimeout');
},
defaultValue: defaultFactory('sessionQuitTimeout')
}),
screenshotsDir: option({
validate: is('string'),
defaultValue: defaultFactory('screenshotsDir'),
map: resolveWithProjectRoot
}),
tolerance: option({
defaultValue: defaultFactory('tolerance'),
parseEnv: Number,
parseCli: Number,
validate: is('number')
}),
antialiasingTolerance: option({
defaultValue: defaultFactory('antialiasingTolerance'),
parseEnv: Number,
parseCli: Number,
validate: is('number')
}),
windowSize: option({
defaultValue: defaultFactory('windowSize'),
validate: (value) => {
if (_.isObject(value) && _.isNumber(value.width) && _.isNumber(value.height)) {
return;
} else if (value === null) {
//null is valid value for this option, skip any other checks
return;
} else if (!_.isString(value)) {
throw new GeminiError('"windowSize" must be string or null');
} else if (!/^\d+x\d+$/.test(value)) {
throw new GeminiError('"windowSize" should have form of <width>x<height> (i.e. 1600x1200)');
}
},
map: (value) => {
if (value === null) {
return null;
} else if (_.isObject(value)) {
// object with "width" and "height" numeric properties
// which does not needs mapping. Any other object won't
// pass validation.
return value;
}
const size = value.split('x');
return {
width: parseInt(size[0], 10),
height: parseInt(size[1], 10)
};
}
}),
sessionsPerBrowser: positiveIntegerOption(defaultFactory('sessionsPerBrowser')),
suitesPerSession: positiveIntegerOption(defaultFactory('suitesPerSession')),
retry: option({
defaultValue: defaultFactory('retry'),
parseEnv: Number,
parseCli: Number,
validate: (value) => {
is('number')(value);
if (value < 0) {
throw new GeminiError('"retry" must be non-negative');
}
}
}),
screenshotMode: option({
defaultValue: defaultFactory('screenshotMode'),
validate: (value) => {
is('string')(value);
if (!_.includes(['fullpage', 'viewport', 'auto'], value)) {
throw new GeminiError('"screenshotMode" must be one of "fullpage", "viewport" or "auto"');
}
}
}),
screenshotDelay: option({
defaultValue: defaultFactory('screenshotDelay'),
parseEnv: Number,
parseCli: Number,
validate: (value) => {
assertNonNegative(value, 'screenshotDelay');
}
}),
compositeImage: booleanOption(defaultFactory('compositeImage')),
compareOpts: option({
defaultValue: defaultFactory('compareOpts'),
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: (value) => {
if (!isOptionalObject(value)) {
throw new GeminiError('CompareOpts should be object');
}
}
}),
buildDiffOpts: option({
defaultValue: defaultFactory('buildDiffOpts'),
parseEnv: JSON.parse,
parseCli: JSON.parse,
validate: (value) => {
if (!isOptionalObject(value)) {
throw new GeminiError('buildDiffOpts should be object');
}
}
}),
orientation: option({
defaultValue: defaultFactory('orientation'),
validate: (value) => {
if (_.isNull(value)) {
return;
}
is('string', 'orientation')(value);
if (value !== 'landscape' && value !== 'portrait') {
throw new Error('"orientation" must be "landscape" or "portrait"');
}
}
})
});
}
exports.getTopLevel = getTopLevel;
exports.getPerBrowser = getPerBrowser;