UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

289 lines (247 loc) 6.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _path = _interopRequireDefault(require("path")); var _fs = require("fs"); var _util = require("util"); var _events = require("events"); var _types = require("../../types"); var _pool = _interopRequireDefault(require("./pool")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const copyFileAsync = (0, _util.promisify)(_fs.copyFile); const mkdirAsync = (0, _util.promisify)(_fs.mkdir); class Runner extends _events.EventEmitter { get isRunning() { return Object.values(this.pools).some(pool => pool.isRunning); } constructor(config) { super(); _defineProperty(this, "screenDir", void 0); _defineProperty(this, "reportDir", void 0); _defineProperty(this, "browsers", void 0); _defineProperty(this, "pools", {}); _defineProperty(this, "tests", {}); _defineProperty(this, "handlePoolMessage", message => { const { id, status, result } = message; const test = this.tests[id]; if (!test) return; const { browser, testName, storyPath, storyId } = test; test.status = status; if (!result) { this.sendUpdate({ tests: { [id]: { id, browser, testName, storyPath, status, storyId } } }); return; } if (!test.results) { test.results = []; } test.results.push(result); this.sendUpdate({ tests: { [id]: { id, browser, testName, storyPath, status, results: [result], storyId } } }); }); _defineProperty(this, "handlePoolStop", () => { if (!this.isRunning) { this.sendUpdate({ isRunning: false }); this.emit('stop'); } }); this.screenDir = config.screenDir; this.reportDir = config.reportDir; this.browsers = Object.keys(config.browsers); this.browsers.map(browser => this.pools[browser] = new _pool.default(config, browser)).map(pool => pool.on('test', this.handlePoolMessage)); } async init() { await Promise.all(Object.values(this.pools).map(pool => pool.init())); } updateTests(testsDiff) { const tests = {}; const removedTests = []; Object.entries(testsDiff).forEach(([id, newTest]) => { const oldTest = this.tests[id]; if (newTest) { if (oldTest) { this.tests[id] = { ...newTest, retries: oldTest.retries, results: oldTest.results, approved: oldTest.approved }; } else this.tests[id] = newTest; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { story, fn, ...restTest } = newTest; tests[id] = { ...restTest, status: 'unknown' }; } else if (oldTest) { const { id, browser, testName, storyPath, storyId } = oldTest; removedTests.push({ id, browser, testName, storyPath, storyId }); delete this.tests[id]; } }); this.sendUpdate({ tests, removedTests }); } start(ids) { if (this.isRunning) return; const testsToStart = ids.map(id => this.tests[id]).filter(_types.isDefined).filter(test => !test.skip); if (testsToStart.length == 0) return; this.sendUpdate({ isRunning: true, tests: testsToStart.reduce((update, { id, storyId, browser, testName, storyPath }) => ({ ...update, [id]: { id, browser, testName, storyPath, status: 'pending', storyId } }), {}) }); const testsByBrowser = testsToStart.reduce((tests, test) => { const { id, browser, testName, storyPath } = test; const restPath = [...storyPath, testName].filter(_types.isDefined); test.status = 'pending'; return { ...tests, [browser]: [...(tests[browser] || []), { id, path: restPath }] }; }, {}); this.browsers.forEach(browser => { const pool = this.pools[browser]; const tests = testsByBrowser[browser]; if (tests && tests.length > 0 && pool.start(tests)) { pool.once('stop', this.handlePoolStop); } }); } stop() { if (!this.isRunning) return; this.browsers.forEach(browser => this.pools[browser].stop()); } get status() { const tests = {}; Object.values(this.tests).filter(_types.isDefined) // eslint-disable-next-line @typescript-eslint/no-unused-vars .forEach(({ story, fn, ...test }) => tests[test.id] = test); return { isRunning: this.isRunning, tests, browsers: this.browsers }; } async approve({ id, retry, image }) { const test = this.tests[id]; if (!test || !test.results) return; const result = test.results[retry]; if (!result || !result.images) return; const images = result.images[image]; if (!images) return; if (!test.approved) { test.approved = {}; } const { browser, testName, storyPath } = test; const restPath = [...storyPath, testName].filter(_types.isDefined); const testPath = _path.default.join(...restPath, image == browser ? '' : browser); const srcImagePath = _path.default.join(this.reportDir, testPath, images.actual); const dstImagePath = _path.default.join(this.screenDir, testPath, `${image}.png`); await mkdirAsync(_path.default.join(this.screenDir, testPath), { recursive: true }); await copyFileAsync(srcImagePath, dstImagePath); test.approved[image] = retry; this.sendUpdate({ tests: { [id]: { id, browser, testName, storyPath, approved: { [image]: retry }, storyId: test.storyId } } }); } sendUpdate(data) { this.emit('update', data); } } exports.default = Runner;