Skip to content

Commit 362a457

Browse files
feat: add testplane support
1 parent 5440798 commit 362a457

File tree

47 files changed

+851
-823
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+851
-823
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ hot
1010
/test/func/**/reports
1111
/test/func/packages/*/plugin.js
1212
/hermione-report
13+
/testplane-report
1314
tmp
1415
**/playwright-report

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ tmp
2424

2525
**/playwright-report
2626
hermione-report
27+
testplane-report
2728
test/func/**/report
2829
test/func/**/report-backup
2930
test/func/**/reports

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ lib/static/js/
55
lib/static/report.css
66

77
hermione-report
8+
testplane-report
89
.hermione.conf.js
10+
.testplane.conf.js

gemini.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

hermione.ts

Lines changed: 2 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,3 @@
1-
import os from 'os';
2-
import path from 'path';
3-
import Hermione, {TestResult as HermioneTestResult} from 'hermione';
4-
import _ from 'lodash';
5-
import PQueue from 'p-queue';
6-
import {CommanderStatic} from '@gemini-testing/commander';
1+
import pluginHandler from './testplane';
72

8-
import {cliCommands} from './lib/cli-commands';
9-
import {parseConfig} from './lib/config';
10-
import {ToolName} from './lib/constants';
11-
import {HtmlReporter} from './lib/plugin-api';
12-
import {StaticReportBuilder} from './lib/report-builder/static';
13-
import {formatTestResult, logPathToHtmlReport, logError, getExpectedCacheKey} from './lib/server-utils';
14-
import {SqliteClient} from './lib/sqlite-client';
15-
import {HtmlReporterApi, ReporterOptions, TestSpecByPath} from './lib/types';
16-
import {createWorkers, CreateWorkersRunner} from './lib/workers/create-workers';
17-
import {SqliteImageStore} from './lib/image-store';
18-
import {Cache} from './lib/cache';
19-
import {ImagesInfoSaver} from './lib/images-info-saver';
20-
import {getStatus} from './lib/test-adapter/hermione';
21-
22-
export = (hermione: Hermione, opts: Partial<ReporterOptions>): void => {
23-
if (hermione.isWorker()) {
24-
return;
25-
}
26-
27-
const config = parseConfig(opts);
28-
29-
if (!config.enabled) {
30-
return;
31-
}
32-
33-
const htmlReporter = HtmlReporter.create(config, {toolName: ToolName.Hermione});
34-
35-
(hermione as Hermione & HtmlReporterApi).htmlReporter = htmlReporter;
36-
37-
let isCliCommandLaunched = false;
38-
let handlingTestResults: Promise<void>;
39-
let staticReportBuilder: StaticReportBuilder;
40-
41-
const withMiddleware = <T extends (...args: unknown[]) => unknown>(fn: T):
42-
(...args: Parameters<T>) => ReturnType<T> | undefined => {
43-
return (...args: unknown[]) => {
44-
// If any CLI command was launched, e.g. merge-reports, we need to interrupt regular flow
45-
if (isCliCommandLaunched) {
46-
return;
47-
}
48-
49-
return fn.call(undefined, ...args) as ReturnType<T>;
50-
};
51-
};
52-
53-
hermione.on(hermione.events.CLI, (commander: CommanderStatic) => {
54-
_.values(cliCommands).forEach((command: string) => {
55-
// eslint-disable-next-line @typescript-eslint/no-var-requires
56-
require(path.resolve(__dirname, 'lib/cli-commands', command))(commander, config, hermione);
57-
58-
commander.prependListener(`command:${command}`, () => {
59-
isCliCommandLaunched = true;
60-
});
61-
});
62-
});
63-
64-
hermione.on(hermione.events.INIT, withMiddleware(async () => {
65-
const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path});
66-
const imageStore = new SqliteImageStore(dbClient);
67-
const expectedPathsCache = new Cache<[TestSpecByPath, string | undefined], string>(getExpectedCacheKey);
68-
69-
const imagesInfoSaver = new ImagesInfoSaver({
70-
imageFileSaver: htmlReporter.imagesSaver,
71-
expectedPathsCache,
72-
imageStore,
73-
reportPath: htmlReporter.config.path
74-
});
75-
76-
staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, imagesInfoSaver});
77-
78-
handlingTestResults = Promise.all([
79-
staticReportBuilder.saveStaticFiles(),
80-
handleTestResults(hermione, staticReportBuilder)
81-
]).then(async () => {
82-
await staticReportBuilder.finalize();
83-
}).then(async () => {
84-
await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: config.path});
85-
});
86-
87-
htmlReporter.emit(htmlReporter.events.DATABASE_CREATED, dbClient.getRawConnection());
88-
}));
89-
90-
hermione.on(hermione.events.RUNNER_START, withMiddleware((runner) => {
91-
staticReportBuilder.registerWorkers(createWorkers(runner as unknown as CreateWorkersRunner));
92-
}));
93-
94-
hermione.on(hermione.events.RUNNER_END, withMiddleware(async () => {
95-
try {
96-
await handlingTestResults;
97-
98-
logPathToHtmlReport(config);
99-
} catch (e: unknown) {
100-
logError(e as Error);
101-
}
102-
}));
103-
};
104-
105-
async function handleTestResults(hermione: Hermione, reportBuilder: StaticReportBuilder): Promise<void> {
106-
return new Promise((resolve, reject) => {
107-
const queue = new PQueue({concurrency: os.cpus().length});
108-
const promises: Promise<unknown>[] = [];
109-
110-
[
111-
{eventName: hermione.events.TEST_PASS},
112-
{eventName: hermione.events.RETRY},
113-
{eventName: hermione.events.TEST_FAIL},
114-
{eventName: hermione.events.TEST_PENDING}
115-
].forEach(({eventName}) => {
116-
type AnyHermioneTestEvent = typeof hermione.events.TEST_PASS;
117-
118-
hermione.on(eventName as AnyHermioneTestEvent, (testResult: HermioneTestResult) => {
119-
promises.push(queue.add(async () => {
120-
const formattedResult = formatTestResult(testResult, getStatus(eventName, hermione.events, testResult));
121-
122-
await reportBuilder.addTestResult(formattedResult);
123-
}).catch(reject));
124-
});
125-
});
126-
127-
hermione.on(hermione.events.RUNNER_END, () => {
128-
return Promise.all(promises).then(() => resolve(), reject);
129-
});
130-
});
131-
}
3+
export = pluginHandler;

lib/cli-commands/gui.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ const {Api} = require('../gui/api');
66

77
const {GUI: commandName} = cliCommands;
88

9-
module.exports = (program, pluginConfig, hermione) => {
10-
// must be executed here because it adds `gui` field in `gemini` and `hermione tool`,
9+
module.exports = (program, pluginConfig, testplane) => {
10+
// must be executed here because it adds `gui` field in `gemini`, `testplane` and `hermione tool`,
1111
// which is available to other plugins and is an API for interacting with the current plugin
12-
const guiApi = Api.create(hermione);
12+
const guiApi = Api.create(testplane);
1313

1414
program
1515
.command(`${commandName} [paths...]`)
@@ -20,6 +20,6 @@ module.exports = (program, pluginConfig, hermione) => {
2020
.option('-a, --auto-run', 'auto run immediately')
2121
.option('-O, --no-open', 'not to open a browser window after starting the server')
2222
.action((paths, options) => {
23-
runGui({paths, hermione, guiApi, configs: {options, program, pluginConfig}});
23+
runGui({paths, testplane, guiApi, configs: {options, program, pluginConfig}});
2424
});
2525
};

lib/cli-commands/merge-reports.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {logError} = require('../server-utils');
66

77
const {MERGE_REPORTS: commandName} = cliCommands;
88

9-
module.exports = (program, pluginConfig, hermione) => {
9+
module.exports = (program, pluginConfig, testplane) => {
1010
program
1111
.command(`${commandName} [paths...]`)
1212
.allowUnknownOption()
@@ -17,7 +17,7 @@ module.exports = (program, pluginConfig, hermione) => {
1717
try {
1818
const {destination: destPath, header: headers} = options;
1919

20-
await mergeReports(pluginConfig, hermione, paths, {destPath, headers});
20+
await mergeReports(pluginConfig, testplane, paths, {destPath, headers});
2121
} catch (err) {
2222
logError(err);
2323
process.exit(1);

lib/cli-commands/remove-unused-screens/index.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const {logger} = require('../../common-utils');
1616
const {REMOVE_UNUSED_SCREENS: commandName} = cliCommands;
1717

1818
// TODO: remove hack after add ability to add controllers from plugin in silent mode
19-
function proxyHermione() {
19+
function proxyTestplane() {
2020
const proxyHandler = {
2121
get(target, prop) {
2222
return prop in target ? target[prop] : new Proxy(() => {}, this);
@@ -26,31 +26,33 @@ function proxyHermione() {
2626
}
2727
};
2828

29-
global.hermione = new Proxy(global.hermione || {}, proxyHandler);
29+
global.hermione = global.testplane = new Proxy(global.testplane || global.hermione || {}, proxyHandler);
3030
}
3131

32-
module.exports = (program, pluginConfig, hermione) => {
32+
module.exports = (program, pluginConfig, testplane) => {
33+
const toolName = program.name?.() || 'hermione';
34+
3335
program
3436
.command(commandName)
3537
.description('remove screenshots which were not used when running tests')
3638
.option('-p, --pattern <pattern>', 'pattern for searching screenshots on the file system', collect)
3739
.option('--skip-questions', 'do not ask questions during execution (default values will be used)')
38-
.on('--help', () => logger.log(getHelpMessage()))
40+
.on('--help', () => logger.log(getHelpMessage(toolName)))
3941
.action(async (options) => {
4042
try {
41-
proxyHermione();
43+
proxyTestplane();
4244

4345
const {pattern: userPatterns} = options;
4446

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

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

52-
spinner.start('Reading hermione tests from file system');
53-
const fsTests = await getTestsFromFs(hermione);
54+
spinner.start(`Reading ${toolName} tests from file system`);
55+
const fsTests = await getTestsFromFs(testplane);
5456
spinner.succeed();
5557

5658
logger.log(`${chalk.green(fsTests.count)} uniq tests were read in browsers: ${[...fsTests.browserIds].join(', ')}`);
@@ -80,7 +82,7 @@ module.exports = (program, pluginConfig, hermione) => {
8082
}, options);
8183

8284
if (shouldIdentifyUnused) {
83-
await handleUnusedScreens(foundScreenPaths, fsTests, {hermione, pluginConfig, spinner, cliOpts: options});
85+
await handleUnusedScreens(foundScreenPaths, fsTests, {testplane, pluginConfig, spinner, cliOpts: options});
8486
}
8587
} catch (err) {
8688
logger.error(err.stack || err);
@@ -100,7 +102,7 @@ async function handleOutdatedScreens(screenPaths, screenPatterns, opts = {}) {
100102
}
101103

102104
async function handleUnusedScreens(screenPaths, fsTests, opts = {}) {
103-
const {hermione, pluginConfig, spinner, cliOpts} = opts;
105+
const {testplane, pluginConfig, spinner, cliOpts} = opts;
104106
const mainDatabaseUrls = path.resolve(pluginConfig.path, DATABASE_URLS_JSON_NAME);
105107

106108
const isReportPathExists = await fs.pathExists(pluginConfig.path);
@@ -109,7 +111,7 @@ async function handleUnusedScreens(screenPaths, fsTests, opts = {}) {
109111
}
110112

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

115117
if (_.isEmpty(dbPaths)) {
@@ -121,14 +123,14 @@ async function handleUnusedScreens(screenPaths, fsTests, opts = {}) {
121123

122124
if (!_.isEmpty(srcDbPaths)) {
123125
spinner.start('Merging databases');
124-
await hermione.htmlReporter.mergeDatabases(srcDbPaths, pluginConfig.path);
126+
await testplane.htmlReporter.mergeDatabases(srcDbPaths, pluginConfig.path);
125127
spinner.succeed();
126128

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

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

134136
await handleScreens(screenPaths, {paths: unusedScreenPaths, type: 'unused'}, {spinner, cliOpts});
@@ -204,13 +206,13 @@ function collect(newValue, array = []) {
204206
return array.concat(newValue);
205207
}
206208

207-
function getHelpMessage() {
208-
const rmUnusedScreens = `npx hermione ${commandName}`;
209+
function getHelpMessage(toolName) {
210+
const rmUnusedScreens = `npx ${toolName} ${commandName}`;
209211

210212
return `
211213
Example of usage:
212214
Specify the folder in which all reference screenshots are located:
213-
${chalk.green(`${rmUnusedScreens} -p 'hermione-screens-folder'`)}
215+
${chalk.green(`${rmUnusedScreens} -p '${toolName}-screens-folder'`)}
214216
215217
Specify the mask by which all reference screenshots will be found:
216218
${chalk.green(`${rmUnusedScreens} -p 'screens/**/*.png'`)}
@@ -219,7 +221,7 @@ function getHelpMessage() {
219221
${chalk.green(`${rmUnusedScreens} -p 'screens/**/chrome/*.png' -p 'screens/**/firefox/*.png'`)}
220222
221223
Don't ask me about anything and just delete unused reference screenshots:
222-
${chalk.green(`${rmUnusedScreens} -p 'hermione-screens-folder' --skip-questions`)}`;
224+
${chalk.green(`${rmUnusedScreens} -p '${toolName}-screens-folder' --skip-questions`)}`;
223225
}
224226

225227
function transformPatternOption(patterns) {

lib/cli-commands/remove-unused-screens/utils.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const inquirer = require('inquirer');
99

1010
const {SUCCESS} = require('../../constants/test-statuses');
1111

12-
exports.getTestsFromFs = async (hermione) => {
12+
exports.getTestsFromFs = async (testplane) => {
1313
const tests = {
1414
byId: {},
1515
screenPatterns: [],
@@ -18,12 +18,12 @@ exports.getTestsFromFs = async (hermione) => {
1818
};
1919
const uniqTests = new Set();
2020

21-
const testCollection = await hermione.readTests([], {silent: true});
21+
const testCollection = await testplane.readTests([], {silent: true});
2222

2323
testCollection.eachTest((test, browserId) => {
2424
const fullTitle = test.fullTitle();
2525
const id = `${fullTitle} ${browserId}`;
26-
const screenPattern = hermione.config.browsers[browserId].getScreenshotPath(test, '*');
26+
const screenPattern = testplane.config.browsers[browserId].getScreenshotPath(test, '*');
2727

2828
tests.byId[id] = {screenPattern, ...test};
2929
tests.browserIds.add(browserId);
@@ -70,8 +70,8 @@ exports.identifyOutdatedScreens = (screenPaths, screenPatterns) => {
7070
return outdatedScreens;
7171
};
7272

73-
exports.identifyUnusedScreens = (fsTests, {hermione, mergedDbPath} = {}) => {
74-
const dbTree = hermione.htmlReporter.getTestsTreeFromDatabase(mergedDbPath);
73+
exports.identifyUnusedScreens = (fsTests, {testplane, mergedDbPath} = {}) => {
74+
const dbTree = testplane.htmlReporter.getTestsTreeFromDatabase(mergedDbPath);
7575
const screenPathsBySuccessTests = getScreenPathsBySuccessTests(dbTree);
7676
const unusedScreens = [];
7777

lib/common-utils.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import axios, {AxiosRequestConfig} from 'axios';
55
import {
66
ERROR,
77
FAIL,
8-
HERMIONE_TITLE_DELIMITER,
8+
TESTPLANE_TITLE_DELIMITER,
99
IDLE, PWT_TITLE_DELIMITER,
1010
QUEUED,
1111
RUNNING,
@@ -240,13 +240,7 @@ export const isCheckboxUnchecked = (status: number): boolean => Number(status) =
240240
export const getToggledCheckboxState = (status: number): number => isCheckboxChecked(status) ? UNCHECKED : CHECKED;
241241

242242
export const getTitleDelimiter = (toolName: ToolName): string => {
243-
if (toolName === ToolName.Hermione) {
244-
return HERMIONE_TITLE_DELIMITER;
245-
} else if (toolName === ToolName.Playwright) {
246-
return PWT_TITLE_DELIMITER;
247-
} else {
248-
return HERMIONE_TITLE_DELIMITER;
249-
}
243+
return toolName === ToolName.Playwright ? PWT_TITLE_DELIMITER : TESTPLANE_TITLE_DELIMITER;
250244
};
251245

252246
export function getDetailsFileName(testId: string, browserId: string, attempt: number): string {

0 commit comments

Comments
 (0)