-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,555 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use strict'; | ||
|
||
const runGui = require('../gui'); | ||
const Api = require('../gui/api'); | ||
const {GUI: commandName} = require('./'); | ||
|
||
module.exports = (program, pluginConfig, tool) => { | ||
// must be executed here because it adds `gui` field in `gemini` and `hermione tool`, | ||
// which is available to other plugins and is an API for interacting with the current plugin | ||
const guiApi = Api.create(tool); | ||
|
||
program | ||
.command(`${commandName} [paths...]`) | ||
.allowUnknownOption() | ||
.description('update the changed screenshots or gather them if they does not exist') | ||
.option('-p, --port <port>', 'Port to launch server on', 8000) | ||
.option('--hostname <hostname>', 'Hostname to launch server on', 'localhost') | ||
.option('-a, --auto-run', 'auto run immediately') | ||
.option('-O, --no-open', 'not to open a browser window after starting the server') | ||
.action((paths, options) => { | ||
runGui({paths, tool, guiApi, configs: {options, program, pluginConfig}}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
'use strict'; | ||
|
||
module.exports = { | ||
GUI: 'gui', | ||
MERGE_REPORTS: 'merge-reports' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use strict'; | ||
|
||
const {MERGE_REPORTS: commandName} = require('./'); | ||
const mergeReports = require('../merge-reports'); | ||
const {logError} = require('../server-utils'); | ||
|
||
module.exports = (program, {path}) => { | ||
program | ||
.command(`${commandName} [paths...]`) | ||
.allowUnknownOption() | ||
.description('merge reports') | ||
.option('-d, --destination <destination>', 'path to directory with merged report', path) | ||
.action(async (paths, options) => { | ||
try { | ||
await mergeReports(paths, options); | ||
} catch (err) { | ||
logError(err); | ||
process.exit(1); | ||
} | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const _ = require('lodash'); | ||
const Promise = require('bluebird'); | ||
const fs = Promise.promisifyAll(require('fs-extra')); | ||
const {isSkippedStatus} = require('../common-utils'); | ||
const {findNode, setStatusForBranch} = require('../static/modules/utils'); | ||
const {getDataFrom, getStatNameForStatus, getImagePaths} = require('./utils'); | ||
|
||
module.exports = class DataTree { | ||
static create(initialData, destPath) { | ||
return new DataTree(initialData, destPath); | ||
} | ||
|
||
constructor(initialData, destPath) { | ||
this._data = initialData; | ||
this._destPath = destPath; | ||
} | ||
|
||
async mergeWith(dataCollection) { | ||
// make it serially in order to perform correct merge/permutation of images and datas | ||
await Promise.each(_.toPairs(dataCollection), async ([path, data]) => { | ||
this._srcPath = path; | ||
this._mergeSkips(data.skips); | ||
|
||
await this._mergeSuites(data.suites); | ||
}); | ||
|
||
return this._data; | ||
} | ||
|
||
_mergeSkips(srcSkips) { | ||
srcSkips.forEach((skip) => { | ||
if (!_.find(this._data.skips, {suite: skip.suite, browser: skip.browser})) { | ||
this._data.skips.push(skip); | ||
} | ||
}); | ||
} | ||
|
||
async _mergeSuites(srcSuites) { | ||
await Promise.map(srcSuites, async (suite) => { | ||
await this._mergeSuiteResult(suite); | ||
}); | ||
} | ||
|
||
async _mergeSuiteResult(suite) { | ||
const existentSuite = findNode(this._data.suites, suite.suitePath); | ||
|
||
if (!existentSuite) { | ||
return await this._addSuiteResult(suite); | ||
} | ||
|
||
if (suite.children) { | ||
await Promise.map(suite.children, (childSuite) => this._mergeSuiteResult(childSuite)); | ||
} else { | ||
await this._mergeBrowserResult(suite); | ||
} | ||
} | ||
|
||
async _mergeBrowserResult(suite) { | ||
await Promise.map(suite.browsers, async (bro) => { | ||
const existentBro = this._findBrowserResult(suite.suitePath, bro.name); | ||
|
||
if (!existentBro) { | ||
return await this._addBrowserResult(bro, suite.suitePath); | ||
} | ||
|
||
this._moveTestResultToRetries(existentBro); | ||
await this._addTestRetries(existentBro, bro.retries); | ||
await this._changeTestResult(existentBro, bro.result, suite.suitePath); | ||
}); | ||
} | ||
|
||
async _addSuiteResult(suite) { | ||
if (suite.suitePath.length === 1) { | ||
this._data.suites.push(suite); | ||
} else { | ||
const existentParentSuite = findNode(this._data.suites, suite.suitePath.slice(0, -1)); | ||
existentParentSuite.children.push(suite); | ||
} | ||
|
||
this._mergeStatistics(suite); | ||
await this._moveImages(suite, {fromFields: ['result', 'retries']}); | ||
} | ||
|
||
async _addBrowserResult(bro, suitePath) { | ||
const existentParentSuite = findNode(this._data.suites, suitePath); | ||
existentParentSuite.browsers.push(bro); | ||
|
||
this._mergeStatistics(bro); | ||
await this._moveImages(bro, {fromFields: ['result', 'retries']}); | ||
} | ||
|
||
_moveTestResultToRetries(existentBro) { | ||
existentBro.retries.push(existentBro.result); | ||
|
||
this._data.retries += 1; | ||
const statName = getStatNameForStatus(existentBro.result.status); | ||
this._data[statName] -= 1; | ||
} | ||
|
||
async _addTestRetries(existentBro, retries) { | ||
await Promise.mapSeries(retries, (retry) => this._addTestRetry(existentBro, retry)); | ||
} | ||
|
||
async _addTestRetry(existentBro, retry) { | ||
const newAttempt = existentBro.retries.length; | ||
|
||
await this._moveImages(retry, {newAttempt}); | ||
retry = this._changeFieldsWithAttempt(retry, {newAttempt}); | ||
|
||
existentBro.retries.push(retry); | ||
this._data.retries += 1; | ||
} | ||
|
||
async _changeTestResult(existentBro, result, suitePath) { | ||
await this._moveImages(result, {newAttempt: existentBro.retries.length}); | ||
existentBro.result = this._changeFieldsWithAttempt(result, {newAttempt: existentBro.retries.length}); | ||
|
||
const statName = getStatNameForStatus(existentBro.result.status); | ||
this._data[statName] += 1; | ||
|
||
if (!isSkippedStatus(existentBro.result.status)) { | ||
setStatusForBranch(this._data.suites, suitePath, existentBro.result.status); | ||
} | ||
} | ||
|
||
_mergeStatistics(node) { | ||
const testResultStatuses = getDataFrom(node, {fieldName: 'status', fromFields: 'result'}); | ||
|
||
testResultStatuses.forEach((testStatus) => { | ||
const statName = getStatNameForStatus(testStatus); | ||
if (this._data.hasOwnProperty(statName)) { | ||
this._data.total += 1; | ||
this._data[statName] += 1; | ||
} | ||
}); | ||
|
||
const testRetryStatuses = getDataFrom(node, {fieldName: 'status', fromFields: 'retries'}); | ||
this._data.retries += testRetryStatuses.length; | ||
} | ||
|
||
async _moveImages(node, {newAttempt, fromFields}) { | ||
await Promise.map(getImagePaths(node, fromFields), async (imgPath) => { | ||
const srcImgPath = path.resolve(this._srcPath, imgPath); | ||
const destImgPath = path.resolve( | ||
this._destPath, | ||
_.isNumber(newAttempt) ? imgPath.replace(/\d+(?=.png$)/, newAttempt) : imgPath | ||
); | ||
|
||
await fs.moveAsync(srcImgPath, destImgPath); | ||
}); | ||
} | ||
|
||
_changeFieldsWithAttempt(testResult, {newAttempt}) { | ||
const imagesInfo = testResult.imagesInfo.map((imageInfo) => { | ||
return _.mapValues(imageInfo, (val, key) => { | ||
return ['expectedPath', 'actualPath', 'diffPath'].includes(key) | ||
? val.replace(/\d+(?=.png)/, newAttempt) | ||
: val; | ||
}); | ||
}); | ||
|
||
return _.extend({}, testResult, {attempt: newAttempt, imagesInfo}); | ||
} | ||
|
||
_findBrowserResult(suitePath, browserId) { | ||
const existentNode = findNode(this._data.suites, suitePath); | ||
return _.find(_.get(existentNode, 'browsers'), {name: browserId}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use strict'; | ||
|
||
const ReportBuilder = require('./report-builder'); | ||
|
||
module.exports = async (srcPaths, {destination: destPath}) => { | ||
validateOpts(srcPaths, destPath); | ||
|
||
const reportBuilder = ReportBuilder.create(srcPaths, destPath); | ||
|
||
await reportBuilder.build(); | ||
}; | ||
|
||
function validateOpts(srcPaths, destPath) { | ||
if (!srcPaths.length) { | ||
throw new Error('Nothing to merge, no source reports are passed'); | ||
} | ||
|
||
if (srcPaths.includes(destPath)) { | ||
throw new Error(`Destination report path: ${destPath}, exists in source report paths`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const _ = require('lodash'); | ||
const Promise = require('bluebird'); | ||
const fs = Promise.promisifyAll(require('fs-extra')); | ||
const chalk = require('chalk'); | ||
const DataTree = require('./data-tree'); | ||
const serverUtils = require('../server-utils'); | ||
|
||
module.exports = class ReportBuilder { | ||
static create(srcPaths, destPath) { | ||
return new this(srcPaths, destPath); | ||
} | ||
|
||
constructor(srcPaths, destPath) { | ||
this.srcPaths = srcPaths; | ||
this.destPath = destPath; | ||
} | ||
|
||
async build() { | ||
await this._copyToReportDir( | ||
['images', 'index.html', 'report.min.js', 'report.min.css'], | ||
{from: this.srcPaths[0], to: this.destPath} | ||
); | ||
|
||
const srcReportsData = this._loadReportsData(); | ||
const dataTree = DataTree.create(srcReportsData[0], this.destPath); | ||
const srcDataCollection = _.zipObject(this.srcPaths.slice(1), srcReportsData.slice(1)); | ||
|
||
const mergedData = await dataTree.mergeWith(srcDataCollection); | ||
|
||
await this._saveDataFile(mergedData); | ||
} | ||
|
||
_loadReportsData() { | ||
return _(this.srcPaths) | ||
.map((reportPath) => { | ||
const srcDataPath = path.resolve(reportPath, 'data'); | ||
|
||
try { | ||
return serverUtils.require(srcDataPath); | ||
} catch (err) { | ||
serverUtils.logger.warn(chalk.yellow(`Not found data file in passed source report path: ${reportPath}`)); | ||
return {skips: [], suites: []}; | ||
} | ||
}) | ||
.value(); | ||
} | ||
|
||
async _copyToReportDir(files, {from, to}) { | ||
await Promise.map(files, async (dataName) => { | ||
const srcDataPath = path.resolve(from, dataName); | ||
const destDataPath = path.resolve(to, dataName); | ||
|
||
await fs.moveAsync(srcDataPath, destDataPath); | ||
}); | ||
} | ||
|
||
async _saveDataFile(data) { | ||
const formattedData = serverUtils.prepareCommonJSData(data); | ||
const destDataPath = path.resolve(this.destPath, 'data.js'); | ||
|
||
await fs.writeFile(destDataPath, formattedData); | ||
} | ||
}; |
Oops, something went wrong.