SauceLabs Browser testing via Travis
Thomas Aylott committed
Oct 29, 2013 at 18:21 UTC
3df6942cdee2a9b095663f270e7c2fb4a750c5ae
8 files changed
+250
-7
.travis.yml
+2
@@ -13,3 +13,5 @@ env:
13
- secure: qPvsJ46XzGrdIuPA70b55xQNGF8jcK7N1LN5CCQYYocXLa+fBrl+fTE77QvehOPhqwJXcj6kOxI+sY0KrVwV7gmq2XY2HZGWUSCxTN0SZlNIzqPA80Y7G/yOjA4PUt8LKgP+8tptyhTAY56qf+hgW8BoLiKOdztYF2p+3zXOLuA=
14
# SECRET_TOKEN
15
- secure: dkpPW+VnoqC/okhRdV90m36NcyBFhcwEKL3bNFExAwi0dXnFao8RoFlvnwiPlA23h2faROkMIetXlti6Aju08BgUFV+f9aL6vLyU7gUent4Nd3413zf2fwDtXIWIETg6uLnOpSykGKgCAT/hY3Q2oPLqOoY0OxfgnbqwxkxljrE=
16
+ - secure: EOPQOI/kPyv6Yw4prbFnmH/zexDMyTm9800iiItU8U287pIhD6r5xRqmShJ5astrSNsOMsogfSMGO85HvAkyhOSykmbeQe61V5vjzfcWXd8pR3cCGAS1/7ajBXmkI2CPD3UFNQAAXSQDIipgR5CC6yeuk3kubsVbVc2c3TIlAi4=
17
+ - secure: X8EKc78pIGcaIzAz11VYVZdMHqpg18ykDthtuxwcKsE3N60TBSTqYRA0ptR5QaqfYHm9VElXkWsUNiTZjqQiIjI6JCP7d3EpjmJJF4RjwQDhOFm9jgIqShJdk8RbftkrzPVx4sIaBTJQXoKfVK0CvNWrVHkWnIlUOS79xc7L2ZE=
Gruntfile.js
+12
-6
@@ -17,6 +17,8 @@ module.exports = function(grunt) {
17
browserify: require('./grunt/config/browserify'),
18
populist: require('./grunt/config/populist'),
19
phantom: require('./grunt/config/phantom'),
20
+ connect: require('./grunt/config/server'),
21
+ "saucelabs-jasmine": require('./grunt/config/saucelabs-jasmine'),
22
npm: require('./grunt/config/npm'),
23
clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js'],
24
jshint: require('./grunt/config/jshint'),
@@ -25,11 +27,9 @@ module.exports = function(grunt) {
27
28
grunt.config.set('compress', require('./grunt/config/compress'));
29
28
- grunt.loadNpmTasks('grunt-contrib-jshint');
29
- grunt.loadNpmTasks('grunt-contrib-copy');
30
- grunt.loadNpmTasks('grunt-contrib-clean');
31
- grunt.loadNpmTasks('grunt-compare-size');
32
- grunt.loadNpmTasks('grunt-contrib-compress');
30
+ for (var key in grunt.file.readJSON("package.json").devDependencies) {
31
+ if (key !== "grunt" && key.indexOf("grunt") === 0) grunt.loadNpmTasks(key);
32
+ }
33
34
// Alias 'jshint' to 'lint' to better match the workflow we know
35
grunt.registerTask('lint', ['jshint']);
@@ -74,7 +74,13 @@ module.exports = function(grunt) {
74
'populist:test'
75
]);
76
77
- grunt.registerTask('test', ['build:test', 'build:basic', 'phantom:run']);
77
+ grunt.registerTask('test', [
78
+ 'build:test',
79
+ 'build:basic',
80
+ 'phantom:run',
81
+ 'connect',
82
+ 'saucelabs-jasmine'
83
+ ]);
84
grunt.registerTask('npm:test', ['build', 'npm:pack']);
85
86
// Optimized build task that does all of our builds. The subtasks will be run
grunt/config/saucelabs-jasmine.js
new
+42
@@ -0,0 +1,42 @@
1
+// https://saucelabs.com/docs/platforms
2
+
3
+var browsers = [
4
+ { browserName: 'chrome', platform: 'linux' },
5
+
6
+ { browserName: 'android', platform: 'Linux', version: '4.0', 'device-type': 'tablet', 'device-orientation': 'portrait' },
7
+ { browserName: 'android', platform: 'Linux', version: '4.0' },
8
+
9
+ { browserName: 'ipad', platform: 'OS X 10.6', version: '4' },
10
+ { browserName: 'ipad', platform: 'OS X 10.8', version: '5.1' },
11
+ { browserName: 'iphone', platform: 'OS X 10.8', version: '6.1' },
12
+
13
+ { browserName: 'firefox', version: '24', platform: 'Windows 7' },
14
+ { browserName: 'firefox', version: '19', platform: 'Windows XP' },
15
+ { browserName: 'firefox', version: '3', platform: 'Linux' },
16
+
17
+ { browserName: 'opera', platform: 'Windows 2008', version: '12' },
18
+
19
+ { browserName: 'internet explorer', platform: 'Windows 8', version: '10' },
20
+ { browserName: 'internet explorer', platform: 'Windows 7', version: '9' },
21
+ { browserName: 'internet explorer', platform: 'Windows 7', version: '8' },
22
+ { browserName: 'internet explorer', platform: 'Windows XP', version: '8' },
23
+ { browserName: 'internet explorer', platform: 'Windows XP', version: '7' },
24
+ { browserName: 'internet explorer', platform: 'Windows XP', version: '6' },
25
+
26
+ { browserName: 'safari', platform: 'OS X 10.8', version: '6' },
27
+ { browserName: 'safari', platform: 'OS X 10.6', version: '5' },
28
+];
29
+
30
+module.exports = {
31
+ all: {
32
+ options: {
33
+ urls: ["http://127.0.0.1:9999/test/sauce-harness.html"],
34
+ tunnelTimeout: 5,
35
+ build: process.env.TRAVIS_JOB_ID || 'dev' + Date.now(),
36
+ concurrency: 3,
37
+ browsers: browsers,
38
+ testname: "React",
39
+ tags: ["master"]
40
+ }
41
+ }
42
+}
grunt/config/server.js
new
+8
@@ -0,0 +1,8 @@
1
+module.exports = {
2
+ server: {
3
+ options: {
4
+ base: "",
5
+ port: 9999
6
+ }
7
+ }
8
+}
package.json
+3
-1
@@ -81,7 +81,9 @@
81
"gzip-js": "~0.3.2",
82
"tmp": "~0.0.18",
83
"grunt-contrib-compress": "~0.5.1",
84
- "jasmine-tapreporter": "~0.2.2"
84
+ "jasmine-tapreporter": "~0.2.2",
85
+ "grunt-saucelabs": "~4.1.2",
86
+ "grunt-contrib-connect": "~0.5.0"
87
},
88
"engines": {
89
"node": ">=0.10.0"
test/jasmine-execute.js
+1
@@ -1,5 +1,6 @@
1
;(function(env){
2
env.addReporter(new TAPReporter(console.log.bind(console)));
3
+ env.addReporter(new jasmine.JSReporter());
4
5
// Clean up any nodes the previous test might have added.
6
env.afterEach(function() {
test/sauce-harness.html
new
+21
@@ -0,0 +1,21 @@
1
+<!doctype html>
2
+<meta charset=utf-8>
3
+
4
+<link rel="stylesheet" href="../vendor/jasmine/jasmine.css">
5
+<script>
6
+
7
+var __filename = location.protocol + '//' + location.hostname + ':' + location.port + location.pathname.replace(/^\/?/,'/');
8
+var __dirname = __filename.split('/').reverse().slice(1).reverse().join('/');
9
+
10
+var cacheBust = '?_=' + Date.now().toString(36);
11
+
12
+window.ReactWebWorker_URL = __dirname + '/../src/test/worker.js' + cacheBust;
13
+
14
+</script>
15
+<script src='../build/jasmine.js'></script>
16
+<script src='../build/react.js'></script>
17
+<script src='../build/react-test.js'></script>
18
+<script src='../node_modules/jasmine-tapreporter/src/tapreporter.js'></script>
19
+<script src='../test/the-files-to-test.generated.js'></script>
20
+<script src="../vendor/jasmine-jsreporter/jasmine-jsreporter.js"></script>
21
+<script src='../test/jasmine-execute.js'></script>
vendor/jasmine-jsreporter/jasmine-jsreporter.js
new
+161
@@ -0,0 +1,161 @@
1
+/*
2
+ This file is part of the Jasmine JSReporter project from Ivan De Marino.
3
+
4
+ Copyright (C) 2011 Ivan De Marino (aka detro, aka detronizator), http://blog.ivandemarino.me, ivan.de.marino@gmail.com
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of the <organization> nor the
15
+ names of its contributors may be used to endorse or promote products
16
+ derived from this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL IVAN DE MARINO BE LIABLE FOR ANY
22
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+*/
29
+
30
+(function () {
31
+ // Ensure that Jasmine library is loaded first
32
+ if (typeof jasmine === "undefined") {
33
+ throw new Error("[Jasmine JSReporter] 'Jasmine' library not found");
34
+ }
35
+
36
+ /**
37
+ * Calculate elapsed time, in Seconds.
38
+ * @param startMs Start time in Milliseconds
39
+ * @param finishMs Finish time in Milliseconds
40
+ * @return Elapsed time in Seconds */
41
+ function elapsedSec (startMs, finishMs) {
42
+ return (finishMs - startMs) / 1000;
43
+ }
44
+
45
+ /**
46
+ * Round an amount to the given number of Digits.
47
+ * If no number of digits is given, than '2' is assumed.
48
+ * @param amount Amount to round
49
+ * @param numOfDecDigits Number of Digits to round to. Default value is '2'.
50
+ * @return Rounded amount */
51
+ function round (amount, numOfDecDigits) {
52
+ numOfDecDigits = numOfDecDigits || 2;
53
+ return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits);
54
+ }
55
+
56
+ /**
57
+ * Collect information about a Suite, recursively, and return a JSON result.
58
+ * @param suite The Jasmine Suite to get data from
59
+ */
60
+ function getSuiteData (suite) {
61
+ var suiteData = {
62
+ description : suite.description,
63
+ durationSec : 0,
64
+ specs: [],
65
+ suites: [],
66
+ passed: true
67
+ },
68
+ specs = suite.specs(),
69
+ suites = suite.suites(),
70
+ i, ilen;
71
+
72
+ // Loop over all the Suite's Specs
73
+ for (i = 0, ilen = specs.length; i < ilen; ++i) {
74
+ suiteData.specs[i] = {
75
+ description : specs[i].description,
76
+ durationSec : specs[i].durationSec,
77
+ passed : specs[i].results().passedCount === specs[i].results().totalCount,
78
+ skipped : specs[i].results().skipped,
79
+ passedCount : specs[i].results().passedCount,
80
+ failedCount : specs[i].results().failedCount,
81
+ totalCount : specs[i].results().totalCount
82
+ };
83
+ suiteData.passed = !suiteData.specs[i].passed ? false : suiteData.passed;
84
+ suiteData.durationSec += suiteData.specs[i].durationSec;
85
+ }
86
+
87
+ // Loop over all the Suite's sub-Suites
88
+ for (i = 0, ilen = suites.length; i < ilen; ++i) {
89
+ suiteData.suites[i] = getSuiteData(suites[i]); //< recursive population
90
+ suiteData.passed = !suiteData.suites[i].passed ? false : suiteData.passed;
91
+ suiteData.durationSec += suiteData.suites[i].durationSec;
92
+ }
93
+
94
+ // Rounding duration numbers to 3 decimal digits
95
+ suiteData.durationSec = round(suiteData.durationSec, 4);
96
+
97
+ return suiteData;
98
+ }
99
+
100
+ var JSReporter = function () {
101
+ };
102
+
103
+ JSReporter.prototype = {
104
+ reportRunnerStarting: function (runner) {
105
+ // Nothing to do
106
+ },
107
+
108
+ reportSpecStarting: function (spec) {
109
+ // Start timing this spec
110
+ spec.startedAt = new Date();
111
+ },
112
+
113
+ reportSpecResults: function (spec) {
114
+ // Finish timing this spec and calculate duration/delta (in sec)
115
+ spec.finishedAt = new Date();
116
+ spec.durationSec = elapsedSec(spec.startedAt.getTime(), spec.finishedAt.getTime());
117
+ },
118
+
119
+ reportSuiteResults: function (suite) {
120
+ // Nothing to do
121
+ },
122
+
123
+ reportRunnerResults: function (runner) {
124
+ var suites = runner.suites(),
125
+ i, ilen;
126
+
127
+ // Attach results to the "jasmine" object to make those results easy to scrap/find
128
+ jasmine.runnerResults = {
129
+ suites: [],
130
+ durationSec : 0,
131
+ passed : true
132
+ };
133
+
134
+ // Loop over all the Suites
135
+ for (i = 0, ilen = suites.length; i < ilen; ++i) {
136
+ if (suites[i].parentSuite === null) {
137
+ jasmine.runnerResults.suites[i] = getSuiteData(suites[i]);
138
+ // If 1 suite fails, the whole runner fails
139
+ jasmine.runnerResults.passed = !jasmine.runnerResults.suites[i].passed ? false : jasmine.runnerResults.passed;
140
+ // Add up all the durations
141
+ jasmine.runnerResults.durationSec += jasmine.runnerResults.suites[i].durationSec;
142
+ }
143
+ }
144
+
145
+ // Decorate the 'jasmine' object with getters
146
+ jasmine.getJSReport = function () {
147
+ if (jasmine.runnerResults) {
148
+ return jasmine.runnerResults;
149
+ }
150
+ return null;
151
+ };
152
+ jasmine.getJSReportAsString = function () {
153
+ return JSON.stringify(jasmine.getJSReport());
154
+ };
155
+ }
156
+ };
157
+
158
+ // export public
159
+ jasmine.JSReporter = JSReporter;
160
+})();
161
+