Skip to content

Commit

Permalink
feat: cli command to remove unused screens (#415)
Browse files Browse the repository at this point in the history
* refactor: works with sqlite utils from on place

* feat: add server API with ability to work with the databases

* feat: add cli command "remove-unused-screens"
  • Loading branch information
DudaGod authored Mar 1, 2022
1 parent 787f555 commit c4a3d38
Show file tree
Hide file tree
Showing 45 changed files with 33,474 additions and 5,198 deletions.
51 changes: 44 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,40 @@ Example of usage:
npx hermione gui
```
### remove-unused-screens
Command that adds ability to remove all unused reference images. On first step it looks for screenshots for which there are no tests on the file system. On the second step it looks for screenshots that were not used in a successful test (the result of the tests is taken from the sqlite database). For the correct execution of the second step html-report should exist on the file system and contain the result of the tests. It means you should run tests locally or download report from CI.
```
npx hermione remove-unused-screens --help
```
shows the following:
```
Usage: remove-unused-screens [options]
remove screenshots which were not used when running tests
Options:
-p, --pattern <pattern> pattern for searching screenshots on the file system
--skip-questions do not ask questions during execution (default values will be used)
-h, --help output usage information
Example of usage:
Specify the folder in which all reference screenshots are located:
npx hermione remove-unused-screens -p hermione-screens-folder
Specify the mask by which all reference screenshots will be found:
npx hermione remove-unused-screens -p 'screens/**/*.png'
Specify few masks by which all reference screenshots will be found:
npx hermione remove-unused-screens -p 'screens/**/chrome/*.png' -p 'screens/**/firefox/*.png'
Don't ask me about anything and just delete unused reference screenshots:
npx hermione remove-unused-screens -p 'hermione-screens-folder' --skip-questions
```
### merge-reports
Command that adds ability to merge reports which are created after running the tests.
Expand Down Expand Up @@ -476,13 +510,16 @@ Html-reporter adds to `hermione` object with its own API.
**Properties of the `hermione.htmlReporter` object**
Property name | Description
------------------------- | -------------
`events` | Events list for subscription.
`extraItems` | Items list which will be added to the menu bar.
`metaInfoExtenders` | Items list which will be added to the meta info.
`imagesSaver` | Interface to save image into user storage.
`reportsSaver` | Interface to save sqlite database into user storage.
Property name | Description
-------------------------- | -------------
`events` | Events list for subscription.
`extraItems` | Items list which will be added to the menu bar.
`metaInfoExtenders` | Items list which will be added to the meta info.
`imagesSaver` | Interface to save image into user storage.
`reportsSaver` | Interface to save sqlite database into user storage.
`downloadDatabases` | Method to download all databases from `databaseUrls.json` file.
`mergeDatabases` | Method to merge passed databases to passed report path.
`getTestsTreeFromDatabase` | Method to get tests tree from passed database.
**Available events which are triggered in the main process**
Expand Down
3 changes: 2 additions & 1 deletion lib/cli-commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

module.exports = {
GUI: 'gui',
MERGE_REPORTS: 'merge-reports'
MERGE_REPORTS: 'merge-reports',
REMOVE_UNUSED_SCREENS: 'remove-unused-screens'
};
194 changes: 194 additions & 0 deletions lib/cli-commands/remove-unused-screens/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use strict';

const path = require('path');
const fs = require('fs-extra');
const _ = require('lodash');
const ora = require('ora');
const chalk = require('chalk');
const filesize = require('filesize');
const Promise = require('bluebird');

const {REMOVE_UNUSED_SCREENS: commandName} = require('..');
const {getTestsFromFs, findScreens, askQuestion, identifyOutdatedScreens, identifyUnusedScreens, removeScreens} = require('./utils');
const {DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} = require('../../constants/database');
const {logger} = require('../../common-utils');

module.exports = (program, pluginConfig, hermione) => {
program
.command(commandName)
.description('remove screenshots which were not used when running tests')
.option('-p, --pattern <pattern>', 'pattern for searching screenshots on the file system', collect)
.option('--skip-questions', 'do not ask questions during execution (default values will be used)')
.on('--help', () => logger.log(getHelpMessage()))
.action(async (options) => {
try {
const {pattern: userPatterns} = options;

if (_.isEmpty(userPatterns)) {
throw new Error(`option "pattern" is required. See examples of usage: ${chalk.green(`npx hermione ${commandName} --help`)}`);
}

const userScreenPatterns = transformPatternOption(userPatterns);
const spinner = ora({spinner: 'point'});

spinner.start('Reading hermione tests from file system');
const fsTests = await getTestsFromFs(hermione);
spinner.succeed();

logger.log(`${chalk.green(Object.keys(fsTests.byId).length)} tests were read`);

spinner.start(`Searching reference images on the file system by specified patterns:\n${userScreenPatterns.join('\n')}`);
const foundScreenPaths = await findScreens(userScreenPatterns);
spinner.succeed();

logger.log(`Found ${chalk.green(foundScreenPaths.length)} reference images`);

const shouldIdentifyOutdated = await askQuestion({
name: 'identifyOutdated',
type: 'confirm',
message: 'Identify outdated reference images (tests for them are removed)?',
default: true
}, options);

if (shouldIdentifyOutdated) {
await handleOutdatedScreens(foundScreenPaths, fsTests.screenPatterns, {spinner, cliOpts: options});
}

const shouldIdentifyUnused = await askQuestion({
name: 'identifyUnused',
type: 'confirm',
message: 'Identify unused reference images (tests passed successfully for them, but they were not used during execution)?',
default: true
}, options);

if (shouldIdentifyUnused) {
await handleUnusedScreens(foundScreenPaths, fsTests, {hermione, pluginConfig, spinner, cliOpts: options});
}
} catch (err) {
logger.error(err.stack || err);
process.exit(1);
}
});
};

async function handleOutdatedScreens(screenPaths, screenPatterns, opts = {}) {
const {spinner} = opts;

spinner.start(`Identifying outdated reference images (tests for these images do not exist)`);
const outdatedScreenPaths = identifyOutdatedScreens(screenPaths, screenPatterns);
spinner.succeed();

await handleScreens(screenPaths, {paths: outdatedScreenPaths, type: 'outdated'}, opts);
}

async function handleUnusedScreens(screenPaths, fsTests, opts = {}) {
const {hermione, pluginConfig, spinner, cliOpts} = opts;
const mainDatabaseUrls = path.resolve(pluginConfig.path, DATABASE_URLS_JSON_NAME);

const isReportPathExists = await fs.pathExists(pluginConfig.path);
if (!isReportPathExists) {
throw new Error(`Can't find html-report in "${pluginConfig.path}" folder. You should run tests or download report from CI`);
}

spinner.start('Loading databases with the test results in order to identify unused screenshots in tests');
const dbPaths = await hermione.htmlReporter.downloadDatabases([mainDatabaseUrls], {pluginConfig});
spinner.succeed();

if (_.isEmpty(dbPaths)) {
throw new Error(`Databases were not loaded from "${mainDatabaseUrls}" file`);
}

const mergedDbPath = path.resolve(pluginConfig.path, LOCAL_DATABASE_NAME);
const srcDbPaths = dbPaths.filter((dbPath) => dbPath !== mergedDbPath);

if (!_.isEmpty(srcDbPaths)) {
spinner.start('Merging databases');
await hermione.htmlReporter.mergeDatabases(srcDbPaths, pluginConfig.path);
spinner.succeed();

logger.log(`${chalk.green(srcDbPaths.length)} databases were merged to ${chalk.green(mergedDbPath)}`);
}

spinner.start(`Identifying unused reference images (tests passed successfully for them, but they were not used during execution)`);
const unusedScreenPaths = identifyUnusedScreens(fsTests, {hermione, mergedDbPath});
spinner.succeed();

await handleScreens(screenPaths, {paths: unusedScreenPaths, type: 'unused'}, {spinner, cliOpts});
}

async function handleScreens(allScreenPaths, screensToRemove, {spinner, cliOpts} = {}) {
const screenPathsToRemoveLen = screensToRemove.paths.length;

logger.log(
`Found ${chalk[screenPathsToRemoveLen > 0 ? 'red' : 'green'](screenPathsToRemoveLen)} ` +
`${screensToRemove.type} reference images out of ${chalk.bold(allScreenPaths.length)}`
);

if (_.isEmpty(screensToRemove.paths)) {
return;
}

spinner.start(`Calculating total size of ${screensToRemove.type} reference images`);
const bytes = (await Promise.map(screensToRemove.paths, (screenPath) => fs.stat(screenPath))).reduce((acc, {size}) => acc + size, 0);
spinner.succeed();

logger.log(`Total size of ${screensToRemove.type} reference images = ${chalk.red(filesize(bytes))}`);

const shouldShowList = await askQuestion({
name: 'shouldShowList',
type: 'confirm',
message: `Show list of ${screensToRemove.type} reference images? (${screenPathsToRemoveLen} paths)`,
default: false
}, cliOpts);

if (shouldShowList) {
logger.log(`List of ${screensToRemove.type} reference images:\n${screensToRemove.paths.map((p) => chalk.red(p)).join('\n')}`);
}

const shouldRemove = await askQuestion({
name: 'shouldRemove',
type: 'confirm',
message: `Remove ${screensToRemove.type} reference images?`,
default: true
}, cliOpts);

if (!shouldRemove) {
return;
}

spinner.start(`Removing ${screensToRemove.type} reference images`);
await removeScreens(screensToRemove.paths);
spinner.succeed();

logger.log(`${chalk.green(screenPathsToRemoveLen)} reference images were removed`);
}

function collect(newValue, array = []) {
return array.concat(newValue);
}

function getHelpMessage() {
const rmUnusedScreens = `npx hermione ${commandName}`;

return `
Example of usage:
Specify the folder in which all reference screenshots are located:
${chalk.green(`${rmUnusedScreens} -p 'hermione-screens-folder'`)}
Specify the mask by which all reference screenshots will be found:
${chalk.green(`${rmUnusedScreens} -p 'screens/**/*.png'`)}
Specify few masks by which all reference screenshots will be found:
${chalk.green(`${rmUnusedScreens} -p 'screens/**/chrome/*.png' -p 'screens/**/firefox/*.png'`)}
Don't ask me about anything and just delete unused reference screenshots:
${chalk.green(`${rmUnusedScreens} -p 'hermione-screens-folder' --skip-questions`)}`;
}

function transformPatternOption(patterns) {
return patterns.map(p => {
const resolvedPattern = path.resolve(process.cwd(), p);

return resolvedPattern.endsWith('.png') ? resolvedPattern : path.join(resolvedPattern, '*.png');
});
}
Loading

0 comments on commit c4a3d38

Please sign in to comment.