update node CI script to work with SauceLabs

This commit is contained in:
Gregg Van Hove
2019-03-25 17:54:40 -07:00
parent 828d14f48e
commit 994d11d4f3
4 changed files with 177 additions and 120 deletions

273
ci.js
View File

@@ -1,11 +1,12 @@
const path = require("path"), const path = require("path"),
port = 3000, fs = require('fs'),
port = 5555,
colors = { colors = {
"passed" : "\x1B[32m", "passed" : "\x1B[32m",
"failed": "\x1B[31m", "failed": "\x1B[31m",
"pending": "\x1B[33m", "pending": "\x1B[33m",
"none": "\x1B[0m" "none": "\x1B[0m"
}; },
symbols = { symbols = {
"passed" : ".", "passed" : ".",
"failed": "F", "failed": "F",
@@ -13,138 +14,192 @@ const path = require("path"),
"none": "" "none": ""
}, },
host = `http://localhost:${port}`, host = `http://localhost:${port}`,
exitCode = 0; useSauce = process.env.USE_SAUCE === 'true';
let driver, server;
(async () => { function pageGenerator() {
const html = await (() => { const ejs = require("ejs"),
console.log("Generating index.html for express app..."); fg = require("fast-glob"),
const pug = require("pug"), templatePath = path.resolve(__dirname, 'spec/support/index.html.ejs'),
fg = require("fast-glob"), template = ejs.compile(fs.readFileSync(templatePath).toString()),
patterns = [ patterns = [
"lib/jasmine-core/jasmine.js", "lib/jasmine-core/jasmine.js",
"lib/jasmine-core/json2.js", "lib/jasmine-core/json2.js",
"lib/jasmine-core/jasmine-html.js", "lib/jasmine-core/jasmine-html.js",
"lib/jasmine-core/boot.js", "lib/jasmine-core/boot.js",
"src/core/requireCore.js", "src/core/requireCore.js",
"src/core/base.js", "src/core/base.js",
"src/core/util.js", "src/core/util.js",
"src/core/Spec.js", "src/core/Spec.js",
"src/core/Env.js", "src/core/Env.js",
"src/**/*.js", "src/**/*.js",
"spec/helpers/*.js", "spec/helpers/*.js",
"spec/**/*[Ss]pec.js" "spec/**/*[Ss]pec.js"
], ],
ignore = [ ignore = [
"spec/helpers/nodeDefineJasmineUnderTest.js", "spec/helpers/nodeDefineJasmineUnderTest.js",
"spec/npmPackage/**/*", "spec/npmPackage/**/*",
"lib/jasmine-core/node_boot.js" "lib/jasmine-core/node_boot.js"
]; ];
files = fg.sync(patterns, {ignore});
return Promise.resolve(pug.compileFile(path.resolve(__dirname, "index.pug"), { pretty: true })({files}));
})();
await (() => new Promise(resolve => { return function toHtml() {
const files = fg.sync(patterns, {ignore});
return template({files});
}
}
function buildWebdriver() {
const webdriver = require("selenium-webdriver"),
Capability = webdriver.Capability;
if (useSauce) {
const username = process.env['SAUCE_USERNAME'],
accessKey = process.env['SAUCE_ACCESS_KEY'];
return new webdriver.Builder()
.withCapabilities({
name: `jasmine-core ${new Date().toISOString()}`,
[Capability.PLATFORM]: process.env['SAUCE_OS'],
[Capability.BROWSER_NAME]: process.env['JASMINE_BROWSER'],
[Capability.VERSION]: process.env['SAUCE_BROWSER_VERSION'],
build: `Core ${process.env['TRAVIS_BUILD_NUMBER'] || 'Ran locally'}`,
tags: ['Jasmine-Core'],
"tunnel-identifier": process.env['TRAVIS_JOB_NUMBER'] ? process.env['TRAVIS_BUILD_NUMBER'].toString() : null
})
.usingServer(`http://${username}:${accessKey}@localhost:4445/wd/hub`)
.build();
} else {
return new webdriver.Builder()
.forBrowser(process.env["JASMINE_BROWSER"] || "firefox")
.build();
}
}
async function resultsWithoutCircularReferences(driver, resultType, index, batchSize) {
return await driver.executeScript(
`var results = jsApiReporter.${resultType}Results(${index}, ${batchSize});\n` +
'for (var i = 0; i < results.length; i++) {\n' +
'var expectations = results[i].failedExpectations;\n' +
'if (results[i].passedExpectations) {\n' +
'expectations = expectations.concat(results[i].passedExpectations);\n' +
'}\n' +
'for (var j = 0; j < expectations.length; j++) {\n' +
'var expectation = expectations[j];\n' +
"try { JSON.stringify(expectation.expected); } catch (e) { expectation.expected = '<circular expected>'; }\n" +
"try { JSON.stringify(expectation.actual); } catch (e) { expectation.actual = '<circular actual>'; }\n" +
'}\n' +
'}\n' +
'return results;'
);
}
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
async function getResults(driver) {
const batchSize = 50,
specResults = [],
failedSuiteResults = [];
let index = 0,
slice = [];
do {
slice = await resultsWithoutCircularReferences(driver, 'spec', index, batchSize);
specResults.push(slice);
index += batchSize;
} while (slice.length === batchSize);
index = 0;
do {
slice = await resultsWithoutCircularReferences(driver, 'suite', index, batchSize);
failedSuiteResults.push(slice.filter(function(suite) { return suite.status === 'failed' }));
index += batchSize;
} while (slice.length === batchSize);
return {specResults: flatten(specResults), failedSuiteResults: flatten(failedSuiteResults)};
}
(async function () {
await new Promise(resolve => {
console.log("Creating an express app for browers to run the tests...") console.log("Creating an express app for browers to run the tests...")
const express = require("express"), const express = require("express"),
app = express(); app = express(),
html = pageGenerator();
app.use(express.static(__dirname)); app.use(express.static(__dirname));
app.get("/", (req, res) => res.send(html)); app.get("/", (req, res) => res.send(html()));
app.listen(port, resolve); server = app.listen(port, resolve);
}))(); });
const driver = await (() => new Promise(resolve => (async () => {
console.log("Running the tests in browser...")
const webdriver = require("selenium-webdriver"),
driver = new webdriver.Builder()
.forBrowser(process.env["JASMINE_BROWSER"] || "firefox")
.build();
await driver.get(`${host}/?throwFailures=false&failFast=false&random=true`) console.log("Running the tests in browser...")
driver = buildWebdriver();
await driver.get(`${host}/?throwFailures=false&failFast=false&random=true`)
await new Promise(resolve => {
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
const isFinished = await driver.executeScript("return jsApiReporter && jsApiReporter.finished") const isFinished = await driver.executeScript("return jsApiReporter && jsApiReporter.finished")
if (isFinished) { if (isFinished) {
clearInterval(intervalId) clearInterval(intervalId)
resolve(driver) resolve();
} }
}, 500) }, 500)
})()))(); });
await (() => new Promise(resolve => (async () => { const {specResults, failedSuiteResults} = await getResults(driver);
// output the summary console.log(specResults.map(spec => `${colors[spec.status]}${symbols[spec.status]}`).join("") + colors["none"]);
(await driver.executeScript("return jsApiReporter && jsApiReporter.specs().map(function(spec) { return spec.status })"))
.map(status => `${colors[status]}${symbols[status]}`).join("") + colors["none"];
resolve();
})()))();
await (() => new Promise(resolve => (async () => { const result = specResults.reduce((result, spec) => {
// output the pending and failure details result[spec.status] = [...result[spec.status], spec];
const result = (await driver.executeScript("return jsApiReporter && jsApiReporter.specs().filter(function(spec) { return spec.status !== \"passed\" })")) return result;
.reduce((result, spec) => { }, {pending: [], failed: [], passed: []});
result[spec.status] = result[spec.status] || [];
result[spec.status] = [...result[spec.status], spec]; if (result.pending.length) {
return result; console.log(`${colors["pending"]}Pending:`);
}, {}); result.pending.forEach((spec, index) => {
console.log(`${colors["none"]}${index}) ${spec.fullName}`)
if ((result["pending"] || []).length) { console.group();
console.log(`${colors["pending"]}Pending:`); console.log(`${colors["pending"]}${spec.pendingReason || "no reason given"}`);
result["pending"].forEach((spec, index) => { console.groupEnd();
console.log(`${colors["none"]}${index}) ${spec.fullName}`) console.log();
});
}
if (result.failed.length) {
process.exitCode = 1
console.log(`${colors["failed"]}Failed:`);
result["failed"].forEach((spec, index) => {
console.log(`${colors["none"]}${index}) ${spec.fullName}`)
console.group();
spec.failedExpectations.forEach((expect) => {
console.log(`${colors["none"]}Message:`);
console.group(); console.group();
console.log(`${colors["pending"]}${spec.pendingReason || "no reason given"}`); console.log(`${colors["failed"]}${expect.message}`);
console.groupEnd(); console.groupEnd();
console.log(); console.log(`${colors["none"]}Stack:`);
});
}
if ((result["failed"] || []).length) {
exitCode = 1
console.log(`${colors["failed"]}Failed:`);
result["failed"].forEach((spec, index) => {
console.log(`${colors["none"]}${index}) ${spec.fullName}`)
console.group(); console.group();
spec.failedExpectations.forEach((expect) => { console.log(`${colors["failed"]}${expect.stack}`);
console.log(`${colors["none"]}Message:`);
console.group();
console.log(`${colors["failed"]}${expect.message}`);
console.groupEnd();
console.log(`${colors["none"]}Stack:`);
console.group();
console.log(`${colors["failed"]}${expect.stack}`);
console.groupEnd();
console.groupEnd();
})
console.groupEnd(); console.groupEnd();
console.log(); console.groupEnd();
}); })
} console.groupEnd();
console.log();
resolve(); });
})()))(); }
await (() => new Promise(resolve => (async () => { const details = await driver.executeScript(`
const result = await driver.executeScript(`
return { return {
executionTime: jsApiReporter.executionTime(), executionTime: jsApiReporter.executionTime(),
random: jsApiReporter.runDetails.order.random, random: jsApiReporter.runDetails.order.random,
seed: jsApiReporter.runDetails.order.seed, seed: jsApiReporter.runDetails.order.seed
specCount: jsApiReporter.specs().length,
failureCount: jsApiReporter.specs().filter(function (spec) { return spec.status === \"failed\" }).length,
pendingCount: jsApiReporter.specs().filter(function (spec) { return spec.status === \"pending\" }).length
}`); }`);
if (result.specCount > 0) {
console.log(`${colors["none"]}${result.specCount} spec(s), ${result.failureCount} failure(s), ${result.pendingCount} pending spec(s)`);
console.log(`Finished in ${result.executionTime / 1000} second(s)`);
console.log(`Randomized with seed ${result.seed} ( ${host}/?random=${result.random}&seed=${result.seed} )`);
} else {
console.log(`No specs found`)
}
resolve(); console.log(`${colors["none"]}${specResults.length} spec(s), ${result.failed.length} failure(s), ${result.pending.length} pending spec(s)`);
})()))(); console.log(`Finished in ${details.executionTime / 1000} second(s)`);
console.log(`Randomized with seed ${details.seed} ( ${host}/?random=${details.random}&seed=${details.seed} )`);
await driver.close(); if (useSauce) {
process.exit(exitCode); driver.executeScript(`sauce:job-result=${exitCode === 0}`);
})() }
})().finally(() => {
return Promise.all([driver.close(), new Promise(resolve => server.close(resolve))]);
});

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
html
head
title Jasmine suite
link(rel="shortcut icon" href="images/jasmine_favicon.png" type="image/png")
link(rel="stylesheet" href="lib/jasmine-core/jasmine.css" type="text/css" media="screen")
-
each file in files
script(src=file type="text/javascript")
body

View File

@@ -19,6 +19,7 @@
"homepage": "https://jasmine.github.io", "homepage": "https://jasmine.github.io",
"main": "./lib/jasmine-core.js", "main": "./lib/jasmine-core.js",
"devDependencies": { "devDependencies": {
"ejs": "^1.0.0",
"express": "^4.16.4", "express": "^4.16.4",
"fast-glob": "^2.2.6", "fast-glob": "^2.2.6",
"glob": "^7.1.3", "glob": "^7.1.3",
@@ -33,7 +34,6 @@
"jsdom": "^13.1.0", "jsdom": "^13.1.0",
"load-grunt-tasks": "^4.0.0", "load-grunt-tasks": "^4.0.0",
"node-sass": "^4.11.0", "node-sass": "^4.11.0",
"pug": "^2.0.3",
"selenium-webdriver": "^3.6.0", "selenium-webdriver": "^3.6.0",
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"temp": "^0.9.0" "temp": "^0.9.0"

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Jasmine suite</title>
<link rel="shortcut icon" href="images/jasmine_favicon.png" type="image/png" />
<link rel="stylesheet" href="lib/jasmine-core/jasmine.css" type="text/css" media="screen" />
<% files.forEach(function(file) { %>
<script src="<%= file %>" type="text/javascript"></script>
<% }) %>
<body>
</body>
</html>