diff --git a/ci.js b/ci.js index c3bb2970..ba403668 100644 --- a/ci.js +++ b/ci.js @@ -1,11 +1,12 @@ const path = require("path"), - port = 3000, + fs = require('fs'), + port = 5555, colors = { "passed" : "\x1B[32m", "failed": "\x1B[31m", "pending": "\x1B[33m", "none": "\x1B[0m" - }; + }, symbols = { "passed" : ".", "failed": "F", @@ -13,138 +14,192 @@ const path = require("path"), "none": "" }, host = `http://localhost:${port}`, - exitCode = 0; + useSauce = process.env.USE_SAUCE === 'true'; +let driver, server; -(async () => { - const html = await (() => { - console.log("Generating index.html for express app..."); - const pug = require("pug"), - fg = require("fast-glob"), - patterns = [ - "lib/jasmine-core/jasmine.js", - "lib/jasmine-core/json2.js", - "lib/jasmine-core/jasmine-html.js", - "lib/jasmine-core/boot.js", - "src/core/requireCore.js", - "src/core/base.js", - "src/core/util.js", - "src/core/Spec.js", - "src/core/Env.js", - "src/**/*.js", - "spec/helpers/*.js", - "spec/**/*[Ss]pec.js" - ], - ignore = [ - "spec/helpers/nodeDefineJasmineUnderTest.js", - "spec/npmPackage/**/*", - "lib/jasmine-core/node_boot.js" - ]; - files = fg.sync(patterns, {ignore}); - return Promise.resolve(pug.compileFile(path.resolve(__dirname, "index.pug"), { pretty: true })({files})); - })(); +function pageGenerator() { + const ejs = require("ejs"), + fg = require("fast-glob"), + templatePath = path.resolve(__dirname, 'spec/support/index.html.ejs'), + template = ejs.compile(fs.readFileSync(templatePath).toString()), + patterns = [ + "lib/jasmine-core/jasmine.js", + "lib/jasmine-core/json2.js", + "lib/jasmine-core/jasmine-html.js", + "lib/jasmine-core/boot.js", + "src/core/requireCore.js", + "src/core/base.js", + "src/core/util.js", + "src/core/Spec.js", + "src/core/Env.js", + "src/**/*.js", + "spec/helpers/*.js", + "spec/**/*[Ss]pec.js" + ], + ignore = [ + "spec/helpers/nodeDefineJasmineUnderTest.js", + "spec/npmPackage/**/*", + "lib/jasmine-core/node_boot.js" + ]; - 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 = ''; }\n" + + "try { JSON.stringify(expectation.actual); } catch (e) { expectation.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...") const express = require("express"), - app = express(); + app = express(), + html = pageGenerator(); app.use(express.static(__dirname)); - app.get("/", (req, res) => res.send(html)); - app.listen(port, resolve); - }))(); + app.get("/", (req, res) => res.send(html())); + 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 isFinished = await driver.executeScript("return jsApiReporter && jsApiReporter.finished") if (isFinished) { clearInterval(intervalId) - resolve(driver) + resolve(); } }, 500) - })()))(); + }); - await (() => new Promise(resolve => (async () => { - // output the summary - (await driver.executeScript("return jsApiReporter && jsApiReporter.specs().map(function(spec) { return spec.status })")) - .map(status => `${colors[status]}${symbols[status]}`).join("") + colors["none"]; - resolve(); - })()))(); + const {specResults, failedSuiteResults} = await getResults(driver); + console.log(specResults.map(spec => `${colors[spec.status]}${symbols[spec.status]}`).join("") + colors["none"]); - await (() => new Promise(resolve => (async () => { - // output the pending and failure details - const result = (await driver.executeScript("return jsApiReporter && jsApiReporter.specs().filter(function(spec) { return spec.status !== \"passed\" })")) - .reduce((result, spec) => { - result[spec.status] = result[spec.status] || []; - result[spec.status] = [...result[spec.status], spec]; - return result; - }, {}); - - if ((result["pending"] || []).length) { - console.log(`${colors["pending"]}Pending:`); - result["pending"].forEach((spec, index) => { - console.log(`${colors["none"]}${index}) ${spec.fullName}`) + const result = specResults.reduce((result, spec) => { + result[spec.status] = [...result[spec.status], spec]; + return result; + }, {pending: [], failed: [], passed: []}); + + if (result.pending.length) { + console.log(`${colors["pending"]}Pending:`); + result.pending.forEach((spec, index) => { + console.log(`${colors["none"]}${index}) ${spec.fullName}`) + console.group(); + console.log(`${colors["pending"]}${spec.pendingReason || "no reason given"}`); + console.groupEnd(); + 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.log(`${colors["pending"]}${spec.pendingReason || "no reason given"}`); + console.log(`${colors["failed"]}${expect.message}`); console.groupEnd(); - console.log(); - }); - } - - if ((result["failed"] || []).length) { - exitCode = 1 - console.log(`${colors["failed"]}Failed:`); - result["failed"].forEach((spec, index) => { - console.log(`${colors["none"]}${index}) ${spec.fullName}`) + console.log(`${colors["none"]}Stack:`); console.group(); - spec.failedExpectations.forEach((expect) => { - 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.log(`${colors["failed"]}${expect.stack}`); console.groupEnd(); - console.log(); - }); - } - - resolve(); - })()))(); + console.groupEnd(); + }) + console.groupEnd(); + console.log(); + }); + } - await (() => new Promise(resolve => (async () => { - const result = await driver.executeScript(` + const details = await driver.executeScript(` return { executionTime: jsApiReporter.executionTime(), random: jsApiReporter.runDetails.order.random, - 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 + seed: jsApiReporter.runDetails.order.seed }`); - - 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(); - process.exit(exitCode); -})() + if (useSauce) { + driver.executeScript(`sauce:job-result=${exitCode === 0}`); + } +})().finally(() => { + return Promise.all([driver.close(), new Promise(resolve => server.close(resolve))]); +}); diff --git a/index.pug b/index.pug deleted file mode 100644 index bf0902a6..00000000 --- a/index.pug +++ /dev/null @@ -1,10 +0,0 @@ - -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 \ No newline at end of file diff --git a/package.json b/package.json index 209195c6..fe62b5df 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "homepage": "https://jasmine.github.io", "main": "./lib/jasmine-core.js", "devDependencies": { + "ejs": "^1.0.0", "express": "^4.16.4", "fast-glob": "^2.2.6", "glob": "^7.1.3", @@ -33,7 +34,6 @@ "jsdom": "^13.1.0", "load-grunt-tasks": "^4.0.0", "node-sass": "^4.11.0", - "pug": "^2.0.3", "selenium-webdriver": "^3.6.0", "shelljs": "^0.8.3", "temp": "^0.9.0" diff --git a/spec/support/index.html.ejs b/spec/support/index.html.ejs new file mode 100644 index 00000000..972f09be --- /dev/null +++ b/spec/support/index.html.ejs @@ -0,0 +1,12 @@ + + + + Jasmine suite + + + <% files.forEach(function(file) { %> + + <% }) %> + + +