creevey
Version:
Cross-browser screenshot testing tool for Storybook with fancy UI Runner
289 lines (247 loc) • 6.7 kB
JavaScript
"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;