UNPKG

polyfill-service

Version:
337 lines (292 loc) 10.5 kB
'use strict'; const useragentToSauce = { 'chrome/48': ['chrome', '48.0', 'Windows 7'], 'chrome/46': ['chrome', '46.0', 'Windows 7'], 'chrome/42': ['chrome', '42.0', 'Windows 7'], 'chrome/40': ['chrome', '40.0', 'OS X 10.11'], 'chrome/35': ['chrome', '35.0', 'OS X 10.11'], 'chrome/30': ['chrome', '30.0', 'Windows 7'], 'firefox/44': ['firefox', '44.0', 'Linux'], 'firefox/42': ['firefox', '42.0', 'Linux'], 'firefox/41': ['firefox', '41', 'Linux'], 'firefox/33': ['firefox', '33', 'Linux'], 'firefox/30': ['firefox', '30', 'OSX 10.11'], 'firefox/20': ['firefox', '20', 'Linux'], 'ie/13': ['MicrosoftEdge', '13.10586', 'Windows 10'], 'ie/12': ['microsoftedge', '20.10240', 'Windows 10'], 'ie/11': ['internet explorer', '11', 'Windows 10'], 'ie/10': ['internet explorer', '10', 'Windows 7'], 'ie/9': ['internet explorer', '9', 'Windows 7'], 'ie/8': ['internet explorer', '8', 'Windows XP'], 'ie/7': ['internet explorer', '7', 'Windows XP'], 'ie/6': ['internet explorer', '6', 'Windows XP'], 'safari/9': ['safari', '9.0', 'OSX 10.11'], 'safari/8': ['safari', '8.0', 'OSX 10.10'], 'safari/5.1': ['safari', '5.1', 'Windows 7'], 'android/4.4': ['android', '4.4', 'linux', 'Android Emulator'], 'android/4.3': ['android', '4.3', 'linux', 'Android Emulator'], 'android/4.2': ['android', '4.2', 'linux', 'Android Emulator'], 'android/4.1': ['android', '4.1', 'linux', 'Android Emulator'], 'ios_saf/9.1': ['iphone', '9.1', 'OS X 10.10', 'iPhone 6'], 'ios_saf/9.2': ['iphone', '9.2', 'OS X 10.10', 'iPhone 6 Plus'] }; const promiseTimer = function(time) { return new Promise(resolve => setTimeout(resolve, time)); }; module.exports = function(grunt) { const wd = require('wd'); const Batch = require('batch'); const SauceTunnel = require('sauce-tunnel'); const mkdirp = require('mkdirp'); const path = require('path'); const fs = require('fs'); const testResultsPath = path.join(__dirname, '../test/results'); const pollTick = 1000; const retryLimit = 50; const sauceConnectPort = 4445; grunt.registerMultiTask('saucelabs', 'Perform tests on Sauce', function() { const gruntDone = this.async(); const nameSuffix = process.env.TRAVIS_BUILD_NUMBER ? "-travis-build:" + process.env.TRAVIS_BUILD_NUMBER : ':' + (new Date()).toUTCString(); let tunnel; const tunnelId = 'SauceTunnel' + nameSuffix; if (!process.env.SAUCE_USER_NAME || !process.env.SAUCE_API_KEY) { grunt.fail.warn(new Error('SAUCE_USER_NAME and SAUCE_API_KEY must be set in the environment')); } const options = this.options({ name: 'grunt-test' + nameSuffix, build: null, username: process.env.SAUCE_USER_NAME, key: process.env.SAUCE_API_KEY, browsers: [], url: {}, concurrency: 3, tags: [], video: true, screenshots: true, cibuild: false }); options.browsers = options.browsers.map(b => { const ua = b.split('/'); const sauce = useragentToSauce[b]; const def = { browserName:ua[0], browserVersion:ua[1], sauce: { browserName: sauce[0], version: sauce[1], platform: sauce[2] }}; if (sauce[3]) def['sauce']['deviceName'] = sauce[3]; return def; }); const batch = new Batch(); batch.concurrency(options.concurrency); batch.throws(false); // Create a new job to pass into the Batch runner, returns a function function runTestJob(url, urlName, ua) { // Variadic like console.log(...); function log() { const leader = (new Date()).toString() + ' ' + ua.browserName + "/" + ua.browserVersion + ": "; const args = Array.prototype.slice.call(arguments); args.unshift(leader); grunt.log.writeln.apply(grunt.log, args); } const browser = wd.promiseRemote("127.0.0.1", sauceConnectPort, options.username, options.key); const conf = Object.assign({ "name": options.name, "record-video": options.video, "record-screenshots": options.screenshots, "tunnel-identifier": tunnelId }, ua.sauce); if (options.tags && options.tags.length) conf['tags'] = options.tags; if (options.build) conf['build'] = options.build; let remainingCount = null; let retryCount = 0; function waitOnResults() { return browser.eval('window.global_test_results || window.remainingCount'); } function processResults(data) { // Result polling code if (typeof data !== 'object' || data === null) { if (retryCount > retryLimit) { log('Timed out'); browser.quit(); throw new Error('Timeout waiting on ' + ua.browserName + '/' +ua.browserVersion); } if (typeof data === 'number' && data !== remainingCount) { remainingCount = data; retryCount = 0; log(remainingCount + ' test pages remaining'); } else { retryCount++; log('no changes, waiting'); } // Recurse waitOnResults and processResults return promiseTimer(pollTick).then(waitOnResults).then(processResults); } log("results received (" + data.passed + " passed, " + data.failed + " failed), closing remote browser"); // Close the browser as soon as we have the // results. No need to keep it hanging around // until the rest of the job is complete. browser.quit(); log("Browser closing"); return { id: browser.sessionID, ua: ua, urlName: urlName, results: data }; } log("requested browser"); return browser.init(conf).then(() => { log("browser launched"); return browser .get(url) .then(() => browser.refresh()) .then(() => { log("kicked"); // Once refreshed, wait `pollTick` before continuing return promiseTimer(pollTick); }) .then(waitOnResults) .then(processResults) ; }); } function readResultsFile(cb) { const file = path.join(testResultsPath, 'results.json'); mkdirp(testResultsPath, err => { if (err) { grunt.warn('Cannot write test results - '+testResultsPath+' cannot be written'); cb({}); } else { fs.readFile(file, (err, filedata) => { if (err) { // If ENOENT, continue as writeFile will create it later if (err.code !== 'ENOENT') { throw err; } } cb((filedata && filedata.length) ? JSON.parse(filedata) : {}); }); } }); } function writeResultsFile(data) { const file = path.join(testResultsPath, 'results.json'); const output = JSON.stringify(data, null, 2); console.log('Wrote '+output.length+' bytes to result file'); fs.writeFile(file, output, err => { if (err) { grunt.warn(err); throw err; } }); } grunt.log.writeln("travis_fold:start:Sauce test progress"); grunt.log.writeln("Sauce job name: "+options.name); readResultsFile(testResults => { let noop = true; options.browsers.forEach(ua => { Object.keys(options.urls).forEach(urlName => { const url = options.urls[urlName]; let state; try { testResults[ua.browserName][ua.browserVersion][urlName].length; state = 'skipped - data already in result file'; } catch(e) { batch.push(done => { runTestJob(url, urlName, ua) .then(data => done(null, data)) .catch(e => { console.log(e.stack || e); done(e); }) ; }); state = 'queued'; noop = false; } grunt.log.writeln(ua.browserName + '/' + ua.browserVersion + ' (' + urlName + ' mode): ' + state); }); }); if (noop) { grunt.log.oklns("Nothing to do"); return gruntDone(); } grunt.log.writeln('Opening tunnel to Sauce Labs'); tunnel = new SauceTunnel(options.username, options.key, tunnelId, true); tunnel.start(status => { let cumFailCount = 0; grunt.log.writeln("Tunnel Started"); if (status !== true) { gruntDone(status); } grunt.log.writeln("Starting test jobs"); batch.on('progress', function(e) { if (e.error) { throw e.error; } if (e.value.ua) { if (!testResults[e.value.ua.browserName]) { testResults[e.value.ua.browserName] = {}; } if (!testResults[e.value.ua.browserName][e.value.ua.browserVersion]) { testResults[e.value.ua.browserName][e.value.ua.browserVersion] = {}; } testResults[e.value.ua.browserName][e.value.ua.browserVersion][e.value.urlName] = { passed: e.value.results.passed, failed: e.value.results.failed, failingSuites: e.value.results.failingSuites ? Object.keys(e.value.results.failingSuites) : [], testedSuites: Array.from(e.value.results.testedSuites) }; cumFailCount += e.value.results.failed; writeResultsFile(testResults); } // Pending count appears to have an off by one error grunt.log.writeln("Progress (browsers): " + e.complete + ' / ' + e.total + ' (' + (e.pending-1) + ' browsers remaining, ' + cumFailCount + ' test failures so far)'); }); batch.end((err, jobresults) => { grunt.log.writeln('All jobs complete'); tunnel.stop(() => { const passingUAs = []; let failed = false; grunt.log.writeln('Sauce tunnel stopped'); grunt.log.writeln("travis_fold:end:Sauce test progress"); grunt.log.writeln("Failed tests:"); jobresults.forEach(job => { if (!job.results) { grunt.warn('No results reported for '+ job.ua.browserName+'/'+job.ua.browserVersion+' '+job.urlName); return true; } if (job.results.failed) { grunt.log.writeln(' - '+job.ua.browserName+' '+job.ua.browserVersion+' (Sauce results: https://saucelabs.com/tests/' + job.id+')'); if (job.results.failingSuites) { Object.keys(job.results.failingSuites).forEach(feature => { const url = options.urls[job.urlName].replace(/test\/director/, 'test/tests')+'&feature='+feature; grunt.log.writeln(' -> '+feature); grunt.log.writeln(' '+url); }); } failed = true; } else { passingUAs.push(job.ua.browserName+'/'+job.ua.browserVersion); } }); if (!failed) { grunt.log.writeln(' - None!'); } if (passingUAs.length) { grunt.log.writeln('Browsers tested with no failures: '+passingUAs.join(', ')); } process.nextTick(() => { // Always report the grunt task as successful if not running as CI if (!options.cibuild) { gruntDone(null, true); } else { gruntDone(!failed, failed); } }); }); }); }); }); }); };