Skip to content

Commit

Permalink
[breaks-compat] Natural language browser config & semver
Browse files Browse the repository at this point in the history
Regarding the required browsers for the campaign:

The old config properties `majorVersion`, `minorVersion` and `revision`
were removed. Instead, semver-compliant version matching was put in place.

Browser config based on verbose entries with `browserName`, `os`, version
entries and `name` for display alias, was replaced with simple processing
of natural-language string (like "Chrome 27 on Android").

Close #40.
  • Loading branch information
jakub-g committed Oct 15, 2014
1 parent 78ca513 commit f8d9116
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 38 deletions.
39 changes: 19 additions & 20 deletions README.md
Expand Up @@ -131,28 +131,27 @@ browsers:
# and run each test only once (in whatever browser is connected)
# However, if the browsers section is present, each test will be run once in each browser listed here.
# (and browsers not listed here will have nothing to do if they are connected)
- browserName: 'PhantomJS'
- browserName: 'Chrome'
- 'PhantomJS'
- 'Opera'
# It's possible to distinguish browsers by operating systems, read more below
- browserName: 'Firefox'
os: 'Windows 7'
- browserName: 'Firefox'
os: 'Desktop Linux'
- browserName: 'Firefox'
os: 'Android'
- browserName: 'Opera'
- browserName: 'Safari'
- 'Safari on Mac OS X'
- 'Chrome on Windows 7'
- 'Chrome on Desktop Linux'
- 'Chrome on Android'
# It is also possible to distinguish several versions of the same browser:
- browserName: 'IE'
majorVersion: 7
- browserName: 'IE'
majorVersion: 8
- browserName: 'IE'
majorVersion: 9
- browserName: 'IE'
majorVersion: 10
# Note that 'minorVersion' and 'revision' are also available
# The 'name' property allows to change the display name of the browser in the reports.
- 'IE 7'
- 'IE 8'
- 'IE 9'
- 'IE 10'
# Browser version can be also a semver-compliant string; see https://github.com/isaacs/node-semver
- 'Firefox 3.6'
- 'Firefox >=20'
# In the logs, browser will be identified using the name, version and OS as provided. If you want to change that, add an alias at the end using 'as':
- 'Chrome 28 as Chrome Stable 28'
- 'Chrome 30 as Chrome Canary 30'
# You can mix all of that options, and have whitespace as you see fit.
- 'Firefox >=25 on Windows 7 as Firefox/Windows'
- 'Firefox >=25 on Ubuntu as Firefox/Ubuntu'
```

#### Regarding browser detection by operating system:
Expand Down
61 changes: 49 additions & 12 deletions lib/test-campaign/browser.js
Expand Up @@ -15,18 +15,25 @@

var browserMatch = require('../util/browser-detection.js').browserMatch;

/**
* @see Browser.prototype.parseBrowserConfig
* <li> Usage: </li>
* <li> Input: 'Chrome >=30 on Desktop Windows as Chrome Canary 30' </li>
* <li> Match: [1] = 'Chrome', [2] = '>=30', [3] = 'Desktop Windows', [4] = 'Chrome Canary 30' </li>
* Parts 2, 3, 4 are optional, will be undefined if not found.
*/
var _browserCfgRegex = /^\s*(\S+)(?:\s+(\W*\d\S*))?(?:\s+on\s+(.*?))?(?:\s+as\s+(.*?))?\s*$/i;

var buildNameFromConfig = function (config) {
if (config.displayAs) {
return config.displayAs;
}

var name = "";
if (config.browserName) {
name = config.browserName;
if (config.majorVersion != null) {
name += " " + config.majorVersion;
if (config.minorVersion != null) {
name += "." + config.minorVersion;
if (config.revision != null) {
name += "." + config.revision;
}
}
if (config.browserVersion != null) {
name += " " + config.browserVersion;
}
}
if (config.os) {
Expand All @@ -35,10 +42,15 @@ var buildNameFromConfig = function (config) {
return name;
};

var Browser = function (config) {
this.name = config.name || buildNameFromConfig(config); // the browser name is only used for display in test results
this.config = config;

var Browser = function (cfgString) {
/**
* {browserName, browserVersion, os, displayAs}
*/
this.config = Browser.parseBrowserConfig(cfgString);
/**
* Display name for the logs
*/
this.name = buildNameFromConfig(this.config);
/**
* Contains tasks not yet dispatched.
*/
Expand Down Expand Up @@ -72,4 +84,29 @@ Browser.prototype.onTaskFinished = function () {
this.pendingTasks--;
};

/**
* @static
*/
Browser.parseBrowserConfig = function (cfgString) {
if (cfgString == null || cfgString === "") {
return {}; // default, unrestricted browser
}

var match = _browserCfgRegex.exec(cfgString);
if (match === null) {
return {
browserName: "unparsable browser"
};
} else {
// Some of the entries can be undefined, it's fine.
// Note that garbage input may also produce valid match.
return {
browserName: match[1],
browserVersion: match[2],
os: match[3],
displayAs: match[4]
};
}
};

module.exports = Browser;
2 changes: 1 addition & 1 deletion lib/test-campaign/test-campaign.js
Expand Up @@ -55,7 +55,7 @@ var TestCampaign = function (config, parentLogger) {
} else {
this.logger.logInfo("No specific browsers expected in the campaign");
}
var browsersCfg = config.browsers || [{}]; // if no browsers required, we create one, unrestricted
var browsersCfg = config.browsers || [""];
var browsers = [];
for (var i = 0, l = browsersCfg.length; i < l; i++) {
browsers[i] = new Browser(browsersCfg[i]);
Expand Down
23 changes: 18 additions & 5 deletions lib/util/browser-detection.js
Expand Up @@ -13,14 +13,28 @@
* limitations under the License.
*/

var semver = require('semver');
var uaParser = require('ua-parser-snapshot');

// semver-compliant version must be x.y.z
var toSemVer = function (version) {
var nbDots = (version.match(/\./g) || "").length;
if (nbDots === 0) {
return version + ".0.0";
} else if (nbDots === 1) {
return version + ".0";
} else {
return version;
}
};

exports.detectBrowser = function (data) {
var parsedAgent = uaParser.parse(data.userAgent);
var ua = parsedAgent.ua;
var osFamily = parsedAgent.os.family;
var res = {
displayName: parsedAgent.toString(),
semverString: toSemVer(parsedAgent.toVersionString()),
family: ua.family,
major: ua.major,
minor: ua.minor,
Expand Down Expand Up @@ -49,6 +63,7 @@ exports.detectBrowser = function (data) {
res.minor = 0;
res.patch = null;
res.displayName = "IE " + documentMode + ".0";
res.semverString = documentMode + ".0.0";
}
}
return res;
Expand All @@ -59,11 +74,9 @@ exports.browserMatch = function (config, browserInfo) {
if (config.browserName != null) {
match = match && (config.browserName == browserInfo.family);
}
if (match && config.majorVersion != null) {
match = config.majorVersion == browserInfo.major;
}
if (match && config.minorVersion != null) {
match = config.minorVersion == browserInfo.minor;
if (match && config.browserVersion != null) {
// user may pass version as a number, hence using String()
match = semver.satisfies(browserInfo.semverString, String(config.browserVersion));
}
if (match && config.os != null) {
// a little bit verbose to have readable code
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -40,6 +40,7 @@
"portfinder": "0.2.1",
"q": "1.0.0",
"selenium-java-robot": "0.0.4",
"semver": "2.3.1",
"send": "0.9.3",
"socket.io": "1.1.0",
"ua-parser-snapshot": "0.3.201404031741",
Expand Down
122 changes: 122 additions & 0 deletions spec/config/parseBrowserConfig.spec.js
@@ -0,0 +1,122 @@
/* globals expect, describe, it */
/*
* Copyright 2012 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var Browser = require("../../lib/test-campaign/Browser.js");

describe("parse browser config", function () {

describe("core tests", function () {
it("should read plain browser name", function () {
expect(Browser.parseBrowserConfig("IE")).toEqual({
browserName: "IE",
browserVersion: undefined,
os: undefined,
displayAs: undefined
});
});

it("should read name with version", function () {
expect(Browser.parseBrowserConfig("IE 10")).toEqual({
browserName: "IE",
browserVersion: "10",
os: undefined,
displayAs: undefined
});
});

it("should read name with semver", function () {
expect(Browser.parseBrowserConfig("Firefox >=22")).toEqual({
browserName: "Firefox",
browserVersion: ">=22",
os: undefined,
displayAs: undefined
});
});

it("should read name with os", function () {
expect(Browser.parseBrowserConfig("Chrome on Windows 7")).toEqual({
browserName: "Chrome",
browserVersion: undefined,
os: "Windows 7",
displayAs: undefined
});
});

it("should read name with semver and os", function () {
expect(Browser.parseBrowserConfig("Chrome 30 on Windows 7")).toEqual({
browserName: "Chrome",
browserVersion: "30",
os: "Windows 7",
displayAs: undefined
});
});

it("should read name with semver and alias", function () {
expect(Browser.parseBrowserConfig("Firefox ~25 as Firefox Nightly")).toEqual({
browserName: "Firefox",
browserVersion: "~25",
os: undefined,
displayAs: "Firefox Nightly"
});
});

it("should read name with version, os, and alias", function () {
expect(Browser.parseBrowserConfig("Chrome 30 on Desktop Linux as Chrome Canary Linux")).toEqual({
browserName: "Chrome",
browserVersion: "30",
os: "Desktop Linux",
displayAs: "Chrome Canary Linux"
});
});
});

describe("whitespace and special chars tests", function () {
it("should not care about whitespace", function () {
expect(Browser.parseBrowserConfig(" Chrome 30 on Desktop Linux as Chrome Canary 30 ")).toEqual({
browserName: "Chrome",
browserVersion: "30",
os: "Desktop Linux",
displayAs: "Chrome Canary 30"
});
});

it("should allow special chars in alias", function () {
expect(Browser.parseBrowserConfig("Chrome 30 on Desktop Linux as Chrome/Linux")).toEqual({
browserName: "Chrome",
browserVersion: "30",
os: "Desktop Linux",
displayAs: "Chrome/Linux"
});
});
});

describe("non-standard input tests", function () {
it("should return empty object (unrestricted browser) for empty input string", function () {
expect(Browser.parseBrowserConfig("")).toEqual({});
});

it("should return empty object (unrestricted browser) for null input string", function () {
expect(Browser.parseBrowserConfig(null)).toEqual({});
});

it("should return 'unparsable browser' for no match", function () {
expect(Browser.parseBrowserConfig("I have no idea what I'm passing")).toEqual({
browserName: "unparsable browser"
});
});
});

});

0 comments on commit f8d9116

Please sign in to comment.