diff --git a/docs/en/html-reporter-events.md b/docs/en/html-reporter-events.md
index b6cc98e07..eeb6029a7 100644
--- a/docs/en/html-reporter-events.md
+++ b/docs/en/html-reporter-events.md
@@ -29,7 +29,7 @@ A database instance is passed to the event handler.
### Usage example
```javascript
-const parseConfig = require('./config');
+const {parseConfig} = require('./config');
module.exports = (hermione, opts) => {
const pluginConfig = parseConfig(opts);
diff --git a/docs/ru/html-reporter-events.md b/docs/ru/html-reporter-events.md
index 299161af1..3b5ec2065 100644
--- a/docs/ru/html-reporter-events.md
+++ b/docs/ru/html-reporter-events.md
@@ -29,7 +29,7 @@ hermione.htmlReporter.on(hermione.htmlReporter.events.DATABASE_CREATED, (db) =>
### Пример использования
```javascript
-const parseConfig = require('./config');
+const {parseConfig} = require('./config');
module.exports = (hermione, opts) => {
const pluginConfig = parseConfig(opts);
diff --git a/hermione.js b/hermione.js
index 7c2032b21..0d9b70d6a 100644
--- a/hermione.js
+++ b/hermione.js
@@ -2,8 +2,8 @@
const os = require('os');
const PQueue = require('p-queue');
-const PluginAdapter = require('./lib/plugin-adapter');
-const createWorkers = require('./lib/workers/create-workers');
+const {PluginAdapter} = require('./lib/plugin-adapter');
+const {createWorkers} = require('./lib/workers/create-workers');
let workers;
diff --git a/lib/cli-commands/gui.js b/lib/cli-commands/gui.js
index f10f74091..155489e5b 100644
--- a/lib/cli-commands/gui.js
+++ b/lib/cli-commands/gui.js
@@ -1,8 +1,10 @@
'use strict';
+const {cliCommands} = require('.');
const runGui = require('../gui').default;
const Api = require('../gui/api');
-const {GUI: commandName} = require('./');
+
+const {GUI: commandName} = cliCommands;
module.exports = (program, pluginConfig, hermione) => {
// must be executed here because it adds `gui` field in `gemini` and `hermione tool`,
diff --git a/lib/cli-commands/index.js b/lib/cli-commands/index.ts
similarity index 71%
rename from lib/cli-commands/index.js
rename to lib/cli-commands/index.ts
index 5ccabff27..f6ec574a7 100644
--- a/lib/cli-commands/index.js
+++ b/lib/cli-commands/index.ts
@@ -1,7 +1,5 @@
-'use strict';
-
-module.exports = {
+export const cliCommands = {
GUI: 'gui',
MERGE_REPORTS: 'merge-reports',
REMOVE_UNUSED_SCREENS: 'remove-unused-screens'
-};
+} as const;
diff --git a/lib/cli-commands/merge-reports.js b/lib/cli-commands/merge-reports.js
index 3701478c1..d7349a2b4 100644
--- a/lib/cli-commands/merge-reports.js
+++ b/lib/cli-commands/merge-reports.js
@@ -1,9 +1,11 @@
'use strict';
-const {MERGE_REPORTS: commandName} = require('./');
+const {cliCommands} = require('.');
const mergeReports = require('../merge-reports');
const {logError} = require('../server-utils');
+const {MERGE_REPORTS: commandName} = cliCommands;
+
module.exports = (program, pluginConfig, hermione) => {
program
.command(`${commandName} [paths...]`)
diff --git a/lib/cli-commands/remove-unused-screens/index.js b/lib/cli-commands/remove-unused-screens/index.js
index b564699b3..9f5186b4e 100644
--- a/lib/cli-commands/remove-unused-screens/index.js
+++ b/lib/cli-commands/remove-unused-screens/index.js
@@ -8,11 +8,13 @@ const chalk = require('chalk');
const filesize = require('filesize');
const Promise = require('bluebird');
-const {REMOVE_UNUSED_SCREENS: commandName} = require('..');
+const {cliCommands} = 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');
+const {REMOVE_UNUSED_SCREENS: commandName} = cliCommands;
+
// TODO: remove hack after add ability to add controllers from plugin in silent mode
function proxyHermione() {
const proxyHandler = {
diff --git a/lib/common-utils.ts b/lib/common-utils.ts
index 5e114172e..37bce0fa3 100644
--- a/lib/common-utils.ts
+++ b/lib/common-utils.ts
@@ -2,14 +2,14 @@ import crypto from 'crypto';
import {pick} from 'lodash';
import url from 'url';
import axios, {AxiosRequestConfig} from 'axios';
-import {SUCCESS, FAIL, ERROR, SKIPPED, UPDATED, IDLE, RUNNING, QUEUED} from './constants/test-statuses';
+import {SUCCESS, FAIL, ERROR, SKIPPED, UPDATED, IDLE, RUNNING, QUEUED, TestStatus} from './constants';
import {UNCHECKED, INDETERMINATE, CHECKED} from './constants/checked-statuses';
export const getShortMD5 = (str: string): string => {
return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7);
};
-const statusPriority: string[] = [
+const statusPriority: TestStatus[] = [
// non-final
RUNNING, QUEUED,
@@ -19,15 +19,15 @@ const statusPriority: string[] = [
export const logger = pick(console, ['log', 'warn', 'error']);
-export const isSuccessStatus = (status: string): boolean => status === SUCCESS;
-export const isFailStatus = (status: string): boolean => status === FAIL;
-export const isIdleStatus = (status: string): boolean => status === IDLE;
-export const isRunningStatus = (status: string): boolean => status === RUNNING;
-export const isErroredStatus = (status: string): boolean => status === ERROR;
-export const isSkippedStatus = (status: string): boolean => status === SKIPPED;
-export const isUpdatedStatus = (status: string): boolean => status === UPDATED;
+export const isSuccessStatus = (status: TestStatus): boolean => status === SUCCESS;
+export const isFailStatus = (status: TestStatus): boolean => status === FAIL;
+export const isIdleStatus = (status: TestStatus): boolean => status === IDLE;
+export const isRunningStatus = (status: TestStatus): boolean => status === RUNNING;
+export const isErroredStatus = (status: TestStatus): boolean => status === ERROR;
+export const isSkippedStatus = (status: TestStatus): boolean => status === SKIPPED;
+export const isUpdatedStatus = (status: TestStatus): boolean => status === UPDATED;
-export const determineStatus = (statuses: string[]): string | null => {
+export const determineStatus = (statuses: TestStatus[]): TestStatus | null => {
if (!statuses.length) {
return SUCCESS;
}
diff --git a/lib/config/custom-gui-asserts.js b/lib/config/custom-gui-asserts.ts
similarity index 55%
rename from lib/config/custom-gui-asserts.js
rename to lib/config/custom-gui-asserts.ts
index 0f41554de..884ed9346 100644
--- a/lib/config/custom-gui-asserts.js
+++ b/lib/config/custom-gui-asserts.ts
@@ -1,10 +1,9 @@
-'use strict';
+import {isUndefined, isArray, isEmpty, isFunction, isPlainObject, isString} from 'lodash';
+import CustomGuiControlTypes from '../gui/constants/custom-gui-control-types';
-const {isUndefined, isArray, isEmpty, isFunction, isPlainObject, isString} = require('lodash');
+const SUPPORTED_CONTROL_TYPES: string[] = Object.values(CustomGuiControlTypes);
-const SUPPORTED_CONTROL_TYPES = Object.values(require('../gui/constants/custom-gui-control-types'));
-
-const assertSectionGroupType = (context, type) => {
+const assertSectionGroupType = (context: string, type: unknown): void => {
if (isUndefined(type)) {
throw new Error(`${context} must contain field "type"`);
}
@@ -16,7 +15,7 @@ const assertSectionGroupType = (context, type) => {
}
};
-const assertSectionGroupControls = (context, controls) => {
+const assertSectionGroupControls = (context: string, controls: unknown): void => {
if (isUndefined(controls)) {
throw new Error(`${context} must contain field "controls"`);
}
@@ -26,14 +25,14 @@ const assertSectionGroupControls = (context, controls) => {
if (isEmpty(controls)) {
throw new Error(`${context} must contain non-empty array in the field "controls"`);
}
- controls.forEach((control) => {
+ controls.forEach((control: unknown) => {
if (!isPlainObject(control)) {
throw new Error(`${context} must contain objects in the array "controls"`);
}
});
};
-const assertSectionGroupAction = (context, action) => {
+const assertSectionGroupAction = (context: string, action: unknown): void => {
if (isUndefined(action)) {
throw new Error(`${context} must contain field "action"`);
}
@@ -42,34 +41,35 @@ const assertSectionGroupAction = (context, action) => {
}
};
-const assertSectionGroup = (sectionName, group, groupIndex) => {
+const assertSectionGroup = (sectionName: string, group: unknown, groupIndex: number): void => {
const context = `customGui["${sectionName}"][${groupIndex}]`;
if (!isPlainObject(group)) {
throw new Error(`${context} must be plain object, but got ${typeof group}`);
}
- assertSectionGroupType(context, group.type);
- assertSectionGroupControls(context, group.controls);
- assertSectionGroupAction(context, group.action);
+ const groupObj = group as Record;
+
+ assertSectionGroupType(context, groupObj.type);
+ assertSectionGroupControls(context, groupObj.controls);
+ assertSectionGroupAction(context, groupObj.action);
};
-const assertSection = (section, sectionName) => {
+const assertSection = (section: unknown, sectionName: string): void => {
if (!isArray(section)) {
throw new Error(`customGui["${sectionName}"] must be an array, but got ${typeof section}`);
}
- section.forEach((group, groupIndex) => assertSectionGroup(sectionName, group, groupIndex));
+ section.forEach((group: unknown, groupIndex: number) => assertSectionGroup(sectionName, group, groupIndex));
};
-const assertCustomGui = (customGui) => {
+export const assertCustomGui = (customGui: unknown): void => {
if (!isPlainObject(customGui)) {
throw new Error(`"customGui" option must be plain object, but got ${typeof customGui}`);
}
- for (const sectionName in customGui) {
- assertSection(customGui[sectionName], sectionName);
- }
-};
-module.exports = {
- assertCustomGui
+ const customGuiObj = customGui as Record;
+
+ for (const sectionName in customGuiObj) {
+ assertSection(customGuiObj[sectionName], sectionName);
+ }
};
diff --git a/lib/config/index.js b/lib/config/index.ts
similarity index 64%
rename from lib/config/index.js
rename to lib/config/index.ts
index 2dc859ade..6e3ae9bb7 100644
--- a/lib/config/index.js
+++ b/lib/config/index.ts
@@ -1,47 +1,43 @@
-'use strict';
+import _ from 'lodash';
+import {root, section, option} from 'gemini-configparser';
+import chalk from 'chalk';
-const _ = require('lodash');
-const configParser = require('gemini-configparser');
-const chalk = require('chalk');
-
-const {logger} = require('../common-utils');
-const {config: configDefaults} = require('../constants/defaults');
-const {DiffModes} = require('../constants/diff-modes');
-const saveFormats = require('../constants/save-formats');
-const {assertCustomGui} = require('./custom-gui-asserts');
-
-const root = configParser.root;
-const section = configParser.section;
-const option = configParser.option;
+import {logger} from '../common-utils';
+import {configDefaults, DiffModeId, DiffModes, SaveFormat, ViewMode} from '../constants';
+import {assertCustomGui} from './custom-gui-asserts';
+import {ErrorPattern, PluginDescription, ReporterConfig, ReporterOptions} from '../types';
const ENV_PREFIX = 'html_reporter_';
const CLI_PREFIX = '--html-reporter-';
const ALLOWED_PLUGIN_DESCRIPTION_FIELDS = new Set(['name', 'component', 'point', 'position', 'config']);
-const assertType = (name, validationFn, type) => {
- return (v) => {
+type TypePredicateFn = (value: unknown) => value is T;
+type AssertionFn = (value: unknown) => asserts value is T;
+
+const assertType = (name: string, validationFn: (value: unknown) => value is T, type: string): AssertionFn => {
+ return (v: unknown): asserts v is T => {
if (!validationFn(v)) {
throw new Error(`"${name}" option must be ${type}, but got ${typeof v}`);
}
};
};
-const assertString = (name) => assertType(name, _.isString, 'string');
-const assertBoolean = (name) => assertType(name, _.isBoolean, 'boolean');
-const assertNumber = (name) => assertType(name, _.isNumber, 'number');
+const assertString = (name: string): AssertionFn => assertType(name, _.isString, 'string');
+const assertBoolean = (name: string): AssertionFn => assertType(name, _.isBoolean, 'boolean');
+const assertNumber = (name: string): AssertionFn => assertType(name, _.isNumber, 'number');
-const assertSaveFormat = saveFormat => {
- const formats = Object.values(saveFormats);
+const assertSaveFormat = (saveFormat: unknown): asserts saveFormat is SaveFormat => {
+ const formats = Object.values(SaveFormat);
if (!_.isString(saveFormat)) {
throw new Error(`"saveFormat" option must be string, but got ${typeof saveFormat}`);
}
- if (!formats.includes(saveFormat)) {
+ if (!formats.includes(saveFormat as SaveFormat)) {
throw new Error(`"saveFormat" must be "${formats.join('", "')}", but got "${saveFormat}"`);
}
};
-const assertErrorPatterns = (errorPatterns) => {
+const assertErrorPatterns = (errorPatterns: unknown): asserts errorPatterns is (string | ErrorPattern)[] => {
if (!_.isArray(errorPatterns)) {
throw new Error(`"errorPatterns" option must be array, but got ${typeof errorPatterns}`);
}
@@ -59,51 +55,54 @@ const assertErrorPatterns = (errorPatterns) => {
}
};
-const assertMetaInfoBaseUrls = (metaInfoBaseUrls) => {
+const assertMetaInfoBaseUrls = (metaInfoBaseUrls: unknown): asserts metaInfoBaseUrls is Record => {
if (!_.isObject(metaInfoBaseUrls)) {
throw new Error(`"metaInfoBaseUrls" option must be object, but got ${typeof metaInfoBaseUrls}`);
}
- for (const key in metaInfoBaseUrls) {
+ for (const keyStr in metaInfoBaseUrls) {
+ const key = keyStr as keyof typeof metaInfoBaseUrls;
if (!_.isString(metaInfoBaseUrls[key])) {
throw new Error(`Value of "${key}" in "metaInfoBaseUrls" option must be string, but got ${typeof metaInfoBaseUrls[key]}`);
}
}
};
-const assertArrayOf = (itemsType, name, assertFn) => {
- return (value) => {
+const assertArrayOf = (itemsType: string, name: string, predicateFn: TypePredicateFn) => {
+ return (value: unknown): asserts value is T[] => {
if (!_.isArray(value)) {
throw new Error(`"${name}" option must be an array, but got ${typeof value}`);
}
for (const item of value) {
- if (!assertFn(item)) {
+ if (!predicateFn(item)) {
throw new Error(`"${name}" option must be an array of ${itemsType} but got ${typeof item} for one of items`);
}
}
};
};
-const assertPluginDescription = (description) => {
- if (!_.isPlainObject(description)) {
+const assertPluginDescription = (description: unknown): description is PluginDescription => {
+ const maybeDescription = description as PluginDescription;
+
+ if (!_.isPlainObject(maybeDescription)) {
throw new Error(`plugin description expected to be an object but got ${typeof description}`);
}
- for (const field of ['name', 'component']) {
- if (!description[field] || !_.isString(description[field])) {
- throw new Error(`"plugins.${field}" option must be non-empty string but got ${typeof description[field]}`);
+ for (const field of ['name', 'component'] as const) {
+ if (!maybeDescription[field] || !_.isString(maybeDescription[field])) {
+ throw new Error(`"plugins.${field}" option must be non-empty string but got ${typeof maybeDescription[field]}`);
}
}
- if (description.point && !_.isString(description.point)) {
- throw new Error(`"plugins.point" option must be string but got ${typeof description.point}`);
+ if (maybeDescription.point && !_.isString(maybeDescription.point)) {
+ throw new Error(`"plugins.point" option must be string but got ${typeof maybeDescription.point}`);
}
- if (description.position && !['after', 'before', 'wrap'].includes(description.position)) {
- throw new Error(`"plugins.position" option got an unexpected value "${description.position}"`);
+ if (maybeDescription.position && !['after', 'before', 'wrap'].includes(maybeDescription.position)) {
+ throw new Error(`"plugins.position" option got an unexpected value "${maybeDescription.position}"`);
}
- if (description.config && !_.isPlainObject(description.config)) {
- throw new Error(`plugin configuration expected to be an object but got ${typeof description.config}`);
+ if (maybeDescription.config && !_.isPlainObject(maybeDescription.config)) {
+ throw new Error(`plugin configuration expected to be an object but got ${typeof maybeDescription.config}`);
}
_.forOwn(description, (value, key) => {
@@ -115,19 +114,19 @@ const assertPluginDescription = (description) => {
return true;
};
-const assertDiffMode = (diffMode) => {
+const assertDiffMode = (diffMode: unknown): asserts diffMode is DiffModeId => {
if (!_.isString(diffMode)) {
throw new Error(`"diffMode" option must be a string, but got ${typeof diffMode}`);
}
const availableValues = Object.values(DiffModes).map(v => v.id);
- if (!availableValues.includes(diffMode)) {
+ if (!availableValues.includes(diffMode as DiffModeId)) {
throw new Error(`"diffMode" must be one of "${availableValues.join('", "')}", but got "${diffMode}"`);
}
};
-const mapErrorPatterns = (errorPatterns) => {
+const mapErrorPatterns = (errorPatterns: (string | ErrorPattern)[]): ErrorPattern[] => {
return errorPatterns.map(patternInfo => {
return _.isString(patternInfo)
? {name: patternInfo, pattern: patternInfo}
@@ -135,12 +134,12 @@ const mapErrorPatterns = (errorPatterns) => {
});
};
-const deprecationWarning = (name) => {
+const deprecationWarning = (name: string): void => {
logger.warn(chalk.red(`Warning: field "${name}" is deprecated and will be removed soon from html-reporter config.`));
};
-const getParser = () => {
- return root(section({
+const getParser = (): ReturnType> => {
+ return root(section({
enabled: option({
defaultValue: true,
parseEnv: JSON.parse,
@@ -152,7 +151,7 @@ const getParser = () => {
validate: assertString('path')
}),
saveFormat: option({
- defaultValue: saveFormats.SQLITE,
+ defaultValue: SaveFormat.SQLITE,
validate: assertSaveFormat
}),
saveErrorDetails: option({
@@ -165,11 +164,11 @@ const getParser = () => {
defaultValue: configDefaults.commandsWithShortHistory,
validate: assertArrayOf('strings', 'commandsWithShortHistory', _.isString)
}),
- defaultView: option({
+ defaultView: option({
defaultValue: configDefaults.defaultView,
validate: assertString('defaultView')
}),
- diffMode: option({
+ diffMode: option({
defaultValue: configDefaults.diffMode,
validate: assertDiffMode
}),
@@ -181,7 +180,7 @@ const getParser = () => {
defaultValue: configDefaults.lazyLoadOffset,
validate: (value) => _.isNull(value) || deprecationWarning('lazyLoadOffset')
}),
- errorPatterns: option({
+ errorPatterns: option<(string | ErrorPattern)[], ErrorPattern[]>({
defaultValue: configDefaults.errorPatterns,
parseEnv: JSON.parse,
validate: assertErrorPatterns,
@@ -197,7 +196,7 @@ const getParser = () => {
defaultValue: configDefaults.customGui,
validate: assertCustomGui
}),
- customScripts: option({
+ customScripts: option<(() => void)[]>({
defaultValue: configDefaults.customScripts,
validate: assertArrayOf('functions', 'customScripts', _.isFunction)
}),
@@ -224,9 +223,10 @@ const getParser = () => {
}), {envPrefix: ENV_PREFIX, cliPrefix: CLI_PREFIX});
};
-module.exports = (options) => {
+export const parseConfig = (options: Partial): ReporterConfig => {
const env = process.env;
const argv = process.argv;
- return getParser()({options, env, argv});
+ // TODO: add support for different types of input and output in gemini-configparser
+ return getParser()({options: options as ReporterConfig, env, argv});
};
diff --git a/lib/constants/browser.js b/lib/constants/browser.js
deleted file mode 100644
index 221371b0b..000000000
--- a/lib/constants/browser.js
+++ /dev/null
@@ -1,3 +0,0 @@
-exports.versions = {
- UNKNOWN: 'unknown'
-};
diff --git a/lib/constants/browser.ts b/lib/constants/browser.ts
new file mode 100644
index 000000000..52f0e7ac3
--- /dev/null
+++ b/lib/constants/browser.ts
@@ -0,0 +1,3 @@
+export enum BrowserVersions {
+ UNKNOWN = 'unknown'
+}
diff --git a/lib/constants/defaults.js b/lib/constants/defaults.js
deleted file mode 100644
index 6bf6ec1ae..000000000
--- a/lib/constants/defaults.js
+++ /dev/null
@@ -1,25 +0,0 @@
-'use strict';
-
-const {DiffModes} = require('./diff-modes');
-const {ViewMode} = require('./view-modes');
-
-module.exports = {
- CIRCLE_RADIUS: 150,
- config: {
- saveErrorDetails: false,
- commandsWithShortHistory: [],
- defaultView: ViewMode.ALL,
- diffMode: DiffModes.THREE_UP.id,
- baseHost: '',
- lazyLoadOffset: null,
- errorPatterns: [],
- metaInfoBaseUrls: {},
- customGui: {},
- customScripts: [],
- yandexMetrika: {
- counterNumber: null
- },
- pluginsEnabled: false,
- plugins: []
- }
-};
diff --git a/lib/constants/defaults.ts b/lib/constants/defaults.ts
new file mode 100644
index 000000000..9bb2d57da
--- /dev/null
+++ b/lib/constants/defaults.ts
@@ -0,0 +1,27 @@
+import {DiffModes} from './diff-modes';
+import {ViewMode} from './view-modes';
+import {ReporterConfig} from '../types';
+import {SaveFormat} from './save-formats';
+
+export const CIRCLE_RADIUS = 150;
+
+export const configDefaults: ReporterConfig = {
+ baseHost: '',
+ commandsWithShortHistory: [],
+ customGui: {},
+ customScripts: [],
+ defaultView: ViewMode.ALL,
+ diffMode: DiffModes.THREE_UP.id,
+ enabled: false,
+ errorPatterns: [],
+ lazyLoadOffset: null,
+ metaInfoBaseUrls: {},
+ path: '',
+ plugins: [],
+ pluginsEnabled: false,
+ saveErrorDetails: false,
+ saveFormat: SaveFormat.SQLITE,
+ yandexMetrika: {
+ counterNumber: null
+ }
+};
diff --git a/lib/constants/diff-modes.ts b/lib/constants/diff-modes.ts
index 594d77006..af9176002 100644
--- a/lib/constants/diff-modes.ts
+++ b/lib/constants/diff-modes.ts
@@ -41,3 +41,5 @@ export const DiffModes = {
export type DiffModes = typeof DiffModes;
export type DiffMode = ValueOf;
+
+export type DiffModeId = DiffModes[keyof DiffModes]['id'];
diff --git a/lib/constants/index.ts b/lib/constants/index.ts
index 3dfdc50ac..be8296aa2 100644
--- a/lib/constants/index.ts
+++ b/lib/constants/index.ts
@@ -1,5 +1,9 @@
+export * from './browser';
export * from './database';
+export * from './defaults';
export * from './diff-modes';
export * from './paths';
+export * from './plugin-events';
+export * from './save-formats';
export * from './test-statuses';
export * from './view-modes';
diff --git a/lib/constants/plugin-events.js b/lib/constants/plugin-events.js
deleted file mode 100644
index 402d90172..000000000
--- a/lib/constants/plugin-events.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const getSyncEvents = () => ({
- DATABASE_CREATED: 'databaseCreated',
- TEST_SCREENSHOTS_SAVED: 'testScreenshotsSaved',
- REPORT_SAVED: 'reportSaved'
-});
-
-const events = getSyncEvents();
-Object.defineProperty(events, 'getSync', {value: getSyncEvents, enumerable: false});
-
-module.exports = events;
diff --git a/lib/constants/plugin-events.ts b/lib/constants/plugin-events.ts
new file mode 100644
index 000000000..077f3c216
--- /dev/null
+++ b/lib/constants/plugin-events.ts
@@ -0,0 +1,5 @@
+export enum PluginEvents {
+ DATABASE_CREATED = 'databaseCreated',
+ TEST_SCREENSHOTS_SAVED = 'testScreenshotsSaved',
+ REPORT_SAVED = 'reportSaved'
+}
diff --git a/lib/constants/save-formats.js b/lib/constants/save-formats.js
deleted file mode 100644
index 42cfe215f..000000000
--- a/lib/constants/save-formats.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-
-module.exports = {
- SQLITE: 'sqlite'
-};
diff --git a/lib/constants/save-formats.ts b/lib/constants/save-formats.ts
new file mode 100644
index 000000000..c3614bf97
--- /dev/null
+++ b/lib/constants/save-formats.ts
@@ -0,0 +1,3 @@
+export enum SaveFormat {
+ SQLITE = 'sqlite'
+}
diff --git a/lib/db-utils/client.js b/lib/db-utils/client.js
index 51da2ed59..d30668488 100644
--- a/lib/db-utils/client.js
+++ b/lib/db-utils/client.js
@@ -2,6 +2,7 @@
/* eslint-env browser */
const {isEmpty} = require('lodash');
+/** @type Record unknown> */
const commonSqliteUtils = require('./common');
const {fetchFile, normalizeUrls} = require('../common-utils');
diff --git a/lib/db-utils/common.js b/lib/db-utils/common.js
deleted file mode 100644
index 3b219ba50..000000000
--- a/lib/db-utils/common.js
+++ /dev/null
@@ -1,76 +0,0 @@
-'use strict';
-
-const _ = require('lodash');
-const {logger} = require('../common-utils');
-const {DB_MAX_AVAILABLE_PAGE_SIZE, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, DB_COLUMN_INDEXES} = require('../constants/database');
-
-exports.selectAllQuery = (tableName) => `SELECT * FROM ${tableName}`;
-exports.selectAllSuitesQuery = () => exports.selectAllQuery(DB_SUITES_TABLE_NAME);
-
-exports.createTablesQuery = () => [
- createTableQuery(DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS)
-];
-
-exports.compareDatabaseRowsByTimestamp = (row1, row2) => {
- return row1[DB_COLUMN_INDEXES.timestamp] - row2[DB_COLUMN_INDEXES.timestamp];
-};
-
-exports.handleDatabases = async (dbJsonUrls, opts = {}) => {
- return _.flattenDeep(
- await Promise.all(
- dbJsonUrls.map(async dbJsonUrl => {
- try {
- const currentJsonResponse = await opts.loadDbJsonUrl(dbJsonUrl);
-
- if (!currentJsonResponse.data) {
- logger.warn(`Cannot get data from ${dbJsonUrl}`);
-
- return opts.formatData ? opts.formatData(dbJsonUrl, currentJsonResponse.status) : [];
- }
-
- // JSON format declare at lib/static/modules/actions.js
- const {dbUrls, jsonUrls} = currentJsonResponse.data;
-
- // paths from databaseUrls.json may be relative or absolute
- const preparedDbUrls = opts.prepareUrls(dbUrls, dbJsonUrl);
- const preparedDbJsonUrls = opts.prepareUrls(jsonUrls, dbJsonUrl);
-
- return await Promise.all([
- exports.handleDatabases(preparedDbJsonUrls, opts),
- ...preparedDbUrls.map(dbUrl => opts.loadDbUrl(dbUrl, opts))
- ]);
- } catch (e) {
- logger.warn(`Error while downloading databases from ${dbJsonUrl}`, e);
-
- return opts.formatData ? opts.formatData(dbJsonUrl) : [];
- }
- })
- )
- );
-};
-
-exports.mergeTables = ({db, dbPaths, getExistingTables = () => {}}) => {
- db.prepare(`PRAGMA page_size = ${DB_MAX_AVAILABLE_PAGE_SIZE}`).run();
-
- for (const dbPath of dbPaths) {
- db.prepare(`ATTACH DATABASE '${dbPath}' AS attached`).run();
-
- const getTablesStatement = db.prepare(`SELECT name FROM attached.sqlite_master WHERE type='table'`);
- const tables = getExistingTables(getTablesStatement);
-
- for (const tableName of tables) {
- db.prepare(`CREATE TABLE IF NOT EXISTS ${tableName} AS SELECT * FROM attached.${tableName} LIMIT 0`).run();
- db.prepare(`INSERT OR IGNORE INTO ${tableName} SELECT * FROM attached.${tableName}`).run();
- }
-
- db.prepare(`DETACH attached`).run();
- }
-};
-
-function createTableQuery(tableName, columns) {
- const formattedColumns = columns
- .map(({name, type}) => `${name} ${type}`)
- .join(', ');
-
- return `CREATE TABLE IF NOT EXISTS ${tableName} (${formattedColumns})`;
-}
diff --git a/lib/db-utils/common.ts b/lib/db-utils/common.ts
new file mode 100644
index 000000000..6183cc358
--- /dev/null
+++ b/lib/db-utils/common.ts
@@ -0,0 +1,85 @@
+import _ from 'lodash';
+import {logger} from '../common-utils';
+import {DB_MAX_AVAILABLE_PAGE_SIZE, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, DB_COLUMN_INDEXES} from '../constants';
+import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types';
+import type {Database, Statement} from 'better-sqlite3';
+
+export const selectAllQuery = (tableName: string): string => `SELECT * FROM ${tableName}`;
+export const selectAllSuitesQuery = (): string => selectAllQuery(DB_SUITES_TABLE_NAME);
+
+export const createTablesQuery = (): string[] => [
+ createTableQuery(DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS)
+];
+
+export const compareDatabaseRowsByTimestamp = (row1: RawSuitesRow, row2: RawSuitesRow): number => {
+ return (row1[DB_COLUMN_INDEXES.timestamp] as number) - (row2[DB_COLUMN_INDEXES.timestamp] as number);
+};
+
+export interface DbLoadResult {
+ url: string; status: string; data: null | unknown
+}
+
+export interface HandleDatabasesOptions {
+ pluginConfig: ReporterConfig;
+ loadDbJsonUrl: (dbJsonUrl: string) => Promise<{data: DbUrlsJsonData | null; status?: string}>;
+ formatData?: (dbJsonUrl: string, status?: string) => DbLoadResult;
+ prepareUrls: (dbUrls: string[], baseUrls: string) => string[];
+ loadDbUrl: (dbUrl: string, opts: HandleDatabasesOptions) => Promise;
+}
+
+export const handleDatabases = async (dbJsonUrls: string[], opts: HandleDatabasesOptions): Promise<(string | DbLoadResult)[]> => {
+ return _.flattenDeep(
+ await Promise.all(
+ dbJsonUrls.map(async dbJsonUrl => {
+ try {
+ const currentJsonResponse = await opts.loadDbJsonUrl(dbJsonUrl);
+
+ if (!currentJsonResponse.data) {
+ logger.warn(`Cannot get data from ${dbJsonUrl}`);
+
+ return opts.formatData ? opts.formatData(dbJsonUrl, currentJsonResponse.status) : [];
+ }
+
+ const {dbUrls, jsonUrls} = currentJsonResponse.data;
+ const preparedDbUrls = opts.prepareUrls(dbUrls, dbJsonUrl);
+ const preparedDbJsonUrls = opts.prepareUrls(jsonUrls, dbJsonUrl);
+
+ return await Promise.all([
+ handleDatabases(preparedDbJsonUrls, opts),
+ ...preparedDbUrls.map((dbUrl: string) => opts.loadDbUrl(dbUrl, opts))
+ ]);
+ } catch (e) {
+ logger.warn(`Error while downloading databases from ${dbJsonUrl}`, e);
+
+ return opts.formatData ? opts.formatData(dbJsonUrl) : [];
+ }
+ })
+ )
+ );
+};
+
+export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => []}: { db: Database, dbPaths: string[], getExistingTables?: (getTablesStatement: Statement<[]>) => string[] }): void => {
+ db.prepare(`PRAGMA page_size = ${DB_MAX_AVAILABLE_PAGE_SIZE}`).run();
+
+ for (const dbPath of dbPaths) {
+ db.prepare(`ATTACH DATABASE '${dbPath}' AS attached`).run();
+
+ const getTablesStatement = db.prepare<[]>(`SELECT name FROM attached.sqlite_master WHERE type='table'`);
+ const tables = getExistingTables(getTablesStatement);
+
+ for (const tableName of tables) {
+ db.prepare(`CREATE TABLE IF NOT EXISTS ${tableName} AS SELECT * FROM attached.${tableName} LIMIT 0`).run();
+ db.prepare(`INSERT OR IGNORE INTO ${tableName} SELECT * FROM attached.${tableName}`).run();
+ }
+
+ db.prepare(`DETACH attached`).run();
+ }
+};
+
+function createTableQuery(tableName: string, columns: { name: string, type: string }[]): string {
+ const formattedColumns = columns
+ .map(({name, type}) => `${name} ${type}`)
+ .join(', ');
+
+ return `CREATE TABLE IF NOT EXISTS ${tableName} (${formattedColumns})`;
+}
diff --git a/lib/db-utils/server.js b/lib/db-utils/server.ts
similarity index 53%
rename from lib/db-utils/server.js
rename to lib/db-utils/server.ts
index 1530e7e61..4d840f7ba 100644
--- a/lib/db-utils/server.js
+++ b/lib/db-utils/server.ts
@@ -1,21 +1,23 @@
-'use strict';
-
-const path = require('path');
-const crypto = require('crypto');
-const axios = require('axios');
-const fs = require('fs-extra');
-const Database = require('better-sqlite3');
-const chalk = require('chalk');
-const NestedError = require('nested-error-stacks');
-
-const StaticTestsTreeBuilder = require('../tests-tree-builder/static');
-const commonSqliteUtils = require('./common');
-const {isUrl, fetchFile, normalizeUrls, logger} = require('../common-utils');
-
-const {DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} = require('../constants/database');
-
-function downloadDatabases(dbJsonUrls, opts) {
- const loadDbJsonUrl = async (dbJsonUrl) => {
+import path from 'path';
+import crypto from 'crypto';
+import axios from 'axios';
+import fs from 'fs-extra';
+import Database from 'better-sqlite3';
+import chalk from 'chalk';
+import NestedError from 'nested-error-stacks';
+
+import {StaticTestsTreeBuilder} from '../tests-tree-builder/static';
+import * as commonSqliteUtils from './common';
+import {isUrl, fetchFile, normalizeUrls, logger} from '../common-utils';
+import {DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} from '../constants';
+import {DbLoadResult, HandleDatabasesOptions} from './common';
+import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types';
+import {Tree} from '../tests-tree-builder/base';
+
+export * from './common';
+
+export async function downloadDatabases(dbJsonUrls: string[], opts: HandleDatabasesOptions): Promise<(string | DbLoadResult)[]> {
+ const loadDbJsonUrl = async (dbJsonUrl: string): Promise<{data: DbUrlsJsonData | null}> => {
if (isUrl(dbJsonUrl)) {
return fetchFile(dbJsonUrl);
}
@@ -24,20 +26,20 @@ function downloadDatabases(dbJsonUrls, opts) {
return {data};
};
- const prepareUrls = (urls, baseUrl) => isUrl(baseUrl) ? normalizeUrls(urls, baseUrl) : urls;
- const loadDbUrl = (dbUrl, opts) => downloadSingleDatabase(dbUrl, opts);
+ const prepareUrls = (urls: string[], baseUrl: string): string[] => isUrl(baseUrl) ? normalizeUrls(urls, baseUrl) : urls;
+ const loadDbUrl = (dbUrl: string, opts: HandleDatabasesOptions): Promise => downloadSingleDatabase(dbUrl, opts);
return commonSqliteUtils.handleDatabases(dbJsonUrls, {...opts, loadDbJsonUrl, prepareUrls, loadDbUrl});
}
-async function mergeDatabases(srcDbPaths, reportPath) {
+export async function mergeDatabases(srcDbPaths: string[], reportPath: string): Promise {
try {
const mainDatabaseUrls = path.resolve(reportPath, DATABASE_URLS_JSON_NAME);
const mergedDbPath = path.resolve(reportPath, LOCAL_DATABASE_NAME);
const mergedDb = new Database(mergedDbPath);
commonSqliteUtils.mergeTables({db: mergedDb, dbPaths: srcDbPaths, getExistingTables: (statement) => {
- return statement.all().map((table) => table.name);
+ return statement.all().map((table) => (table as {name: string}).name);
}});
for (const dbPath of srcDbPaths) {
@@ -47,31 +49,31 @@ async function mergeDatabases(srcDbPaths, reportPath) {
await rewriteDatabaseUrls([mergedDbPath], mainDatabaseUrls, reportPath);
mergedDb.close();
- } catch (err) {
+ } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
throw new NestedError('Error while merging databases', err);
}
}
-function getTestsTreeFromDatabase(dbPath) {
+export function getTestsTreeFromDatabase(dbPath: string): Tree {
try {
const db = new Database(dbPath, {readonly: true, fileMustExist: true});
const testsTreeBuilder = StaticTestsTreeBuilder.create();
- const suitesRows = db.prepare(commonSqliteUtils.selectAllSuitesQuery())
+ const suitesRows = (db.prepare(commonSqliteUtils.selectAllSuitesQuery())
.raw()
- .all()
+ .all() as RawSuitesRow[])
.sort(commonSqliteUtils.compareDatabaseRowsByTimestamp);
const {tree} = testsTreeBuilder.build(suitesRows);
db.close();
return tree;
- } catch (err) {
+ } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
throw new NestedError('Error while getting data from database', err);
}
}
-async function downloadSingleDatabase(dbUrl, {pluginConfig} = {}) {
+async function downloadSingleDatabase(dbUrl: string, {pluginConfig}: {pluginConfig: ReporterConfig}): Promise {
if (!isUrl(dbUrl)) {
return path.resolve(pluginConfig.path, dbUrl);
}
@@ -97,7 +99,7 @@ async function downloadSingleDatabase(dbUrl, {pluginConfig} = {}) {
return dest;
}
-function getUniqueFileNameForLink(link) {
+function getUniqueFileNameForLink(link: string): string {
const fileName = crypto
.createHash('sha256')
.update(link)
@@ -107,7 +109,7 @@ function getUniqueFileNameForLink(link) {
return `${fileName}${fileExt}`;
}
-async function rewriteDatabaseUrls(dbPaths, mainDatabaseUrls, reportPath) {
+async function rewriteDatabaseUrls(dbPaths: string[], mainDatabaseUrls: string, reportPath: string): Promise {
const dbUrls = dbPaths.map(p => path.relative(reportPath, p));
await fs.writeJson(mainDatabaseUrls, {
@@ -115,10 +117,3 @@ async function rewriteDatabaseUrls(dbPaths, mainDatabaseUrls, reportPath) {
jsonUrls: []
});
}
-
-module.exports = {
- ...commonSqliteUtils,
- downloadDatabases,
- mergeDatabases,
- getTestsTreeFromDatabase
-};
diff --git a/lib/gui/tool-runner/report-subscriber.js b/lib/gui/tool-runner/report-subscriber.js
index 03f487b06..696efee1b 100644
--- a/lib/gui/tool-runner/report-subscriber.js
+++ b/lib/gui/tool-runner/report-subscriber.js
@@ -5,7 +5,7 @@ const PQueue = require('p-queue');
const clientEvents = require('../constants/client-events');
const {RUNNING} = require('../../constants/test-statuses');
const {getSuitePath} = require('../../plugin-utils');
-const createWorkers = require('../../workers/create-workers');
+const {createWorkers} = require('../../workers/create-workers');
const {logError} = require('../../server-utils');
let workers;
diff --git a/lib/local-images-saver.js b/lib/local-images-saver.js
deleted file mode 100644
index 466fbc61d..000000000
--- a/lib/local-images-saver.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-const utils = require('./server-utils');
-
-module.exports = {
- saveImg: async (srcCurrPath, {destPath, reportDir}) => {
- await utils.copyFileAsync(srcCurrPath, destPath, {reportDir});
-
- return destPath;
- }
-};
diff --git a/lib/local-images-saver.ts b/lib/local-images-saver.ts
new file mode 100644
index 000000000..dea4dc890
--- /dev/null
+++ b/lib/local-images-saver.ts
@@ -0,0 +1,10 @@
+import {copyFileAsync} from './server-utils';
+import type {ImagesSaver} from './types';
+
+export const LocalImagesSaver: ImagesSaver = {
+ saveImg: async (srcCurrPath, {destPath, reportDir}) => {
+ await copyFileAsync(srcCurrPath, destPath, {reportDir});
+
+ return destPath;
+ }
+};
diff --git a/lib/plugin-adapter.js b/lib/plugin-adapter.js
deleted file mode 100644
index 61309f887..000000000
--- a/lib/plugin-adapter.js
+++ /dev/null
@@ -1,73 +0,0 @@
-'use strict';
-
-const _ = require('lodash');
-const Promise = require('bluebird');
-const parseConfig = require('./config');
-const StaticReportBuilder = require('./report-builder/static');
-const utils = require('./server-utils');
-const cliCommands = require('./cli-commands');
-const PluginApi = require('./plugin-api');
-
-module.exports = class PluginAdapter {
- static create(hermione, opts) {
- return new this(hermione, opts);
- }
-
- constructor(hermione, opts) {
- this._hermione = hermione;
- this._config = parseConfig(opts);
- }
-
- isEnabled() {
- return this._config.enabled;
- }
-
- addApi() {
- this._hermione.htmlReporter = PluginApi.create(this._config);
-
- return this;
- }
-
- addCliCommands() {
- _.values(cliCommands).forEach((command) => {
- this._hermione.on(this._hermione.events.CLI, (commander) => {
- require(`./cli-commands/${command}`)(commander, this._config, this._hermione);
- commander.prependListener(`command:${command}`, () => this._run = _.noop);
- });
- });
-
- return this;
- }
-
- init(prepareData) {
- this._hermione.on(this._hermione.events.INIT, () => this._run(prepareData));
-
- return this;
- }
-
- async _createStaticReportBuilder(prepareData) {
- const staticReportBuilder = StaticReportBuilder.create(this._hermione, this._config);
-
- await staticReportBuilder.init();
-
- return Promise
- .all([
- staticReportBuilder.saveStaticFiles(),
- prepareData(this._hermione, staticReportBuilder, this._config)
- ])
- .then(() => staticReportBuilder.finalize())
- .then(async () => {
- const htmlReporter = this._hermione.htmlReporter;
-
- await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: this._config.path});
- });
- }
-
- async _run(prepareData) {
- const generateReport = this._createStaticReportBuilder(prepareData);
-
- this._hermione.on(this._hermione.events.RUNNER_END, () =>
- generateReport.then(() => utils.logPathToHtmlReport(this._config)).catch(utils.logError)
- );
- }
-};
diff --git a/lib/plugin-adapter.ts b/lib/plugin-adapter.ts
new file mode 100644
index 000000000..dfe710872
--- /dev/null
+++ b/lib/plugin-adapter.ts
@@ -0,0 +1,82 @@
+import _ from 'lodash';
+import type Hermione from 'hermione';
+import {CommanderStatic} from '@gemini-testing/commander';
+
+import {parseConfig} from './config';
+import {StaticReportBuilder} from './report-builder/static';
+import * as utils from './server-utils';
+import {cliCommands} from './cli-commands';
+import {HtmlReporter} from './plugin-api';
+import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './types';
+
+type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise;
+
+export class PluginAdapter {
+ protected _hermione: Hermione & HtmlReporterApi;
+ protected _config: ReporterConfig;
+
+ static create(
+ this: new (hermione: Hermione & HtmlReporterApi, opts: Partial) => T,
+ hermione: Hermione & HtmlReporterApi,
+ opts: Partial
+ ): T {
+ return new this(hermione, opts);
+ }
+
+ constructor(hermione: Hermione & HtmlReporterApi, opts: Partial) {
+ this._hermione = hermione;
+ this._config = parseConfig(opts);
+ }
+
+ isEnabled(): boolean {
+ return this._config.enabled;
+ }
+
+ addApi(): this {
+ this._hermione.htmlReporter = HtmlReporter.create(this._config);
+ return this;
+ }
+
+ addCliCommands(): this {
+ _.values(cliCommands).forEach((command: string) => {
+ this._hermione.on(this._hermione.events.CLI, (commander: CommanderStatic) => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require(`./cli-commands/${command}`)(commander, this._config, this._hermione);
+ commander.prependListener(`command:${command}`, () => this._run = _.noop as typeof this._run);
+ });
+ });
+
+ return this;
+ }
+
+ init(prepareData: PrepareFn): this {
+ this._hermione.on(this._hermione.events.INIT, () => this._run(prepareData));
+ return this;
+ }
+
+ protected async _createStaticReportBuilder(prepareData: PrepareFn): Promise {
+ const staticReportBuilder = StaticReportBuilder.create(this._hermione, this._config);
+
+ await staticReportBuilder.init();
+
+ return Promise
+ .all([
+ staticReportBuilder.saveStaticFiles(),
+ prepareData(this._hermione, staticReportBuilder, this._config)
+ ])
+ .then(() => staticReportBuilder.finalize())
+ .then(async () => {
+ const htmlReporter = this._hermione.htmlReporter;
+
+ await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: this._config.path});
+ });
+ }
+
+ protected async _run(prepareData: PrepareFn): Promise {
+ const generateReport = this._createStaticReportBuilder(prepareData);
+
+ this._hermione.on(this._hermione.events.RUNNER_END, () =>
+ generateReport.then(() => utils.logPathToHtmlReport(this._config)).catch(utils.logError)
+ );
+ }
+}
diff --git a/lib/plugin-api.js b/lib/plugin-api.js
deleted file mode 100644
index d0c0becc3..000000000
--- a/lib/plugin-api.js
+++ /dev/null
@@ -1,84 +0,0 @@
-'use strict';
-
-const EventsEmitter2 = require('eventemitter2');
-const pluginEvents = require('./constants/plugin-events');
-const {downloadDatabases, mergeDatabases, getTestsTreeFromDatabase} = require('./db-utils/server');
-
-module.exports = class HtmlReporter extends EventsEmitter2 {
- static create(config) {
- return new this(config);
- }
-
- constructor(config) {
- super();
-
- this._config = config;
- this._values = {
- extraItems: {},
- metaInfoExtenders: {},
- imagesSaver: require('./local-images-saver'),
- reportsSaver: null
- };
- this._version = require('../package.json').version;
- }
-
- get config() {
- return this._config;
- }
-
- get version() {
- return this._version;
- }
-
- get events() {
- return pluginEvents;
- }
-
- addExtraItem(key, value) {
- this._values.extraItems[key] = value;
- }
-
- get extraItems() {
- return this._values.extraItems;
- }
-
- addMetaInfoExtender(key, value) {
- this._values.metaInfoExtenders[key] = value;
- }
-
- get metaInfoExtenders() {
- return this._values.metaInfoExtenders;
- }
-
- set imagesSaver(imagesSaver) {
- this._values.imagesSaver = imagesSaver;
- }
-
- get imagesSaver() {
- return this._values.imagesSaver;
- }
-
- set reportsSaver(reportsSaver) {
- this._values.reportsSaver = reportsSaver;
- }
-
- get reportsSaver() {
- return this._values.reportsSaver;
- }
-
- get values() {
- return this._values;
- }
-
- downloadDatabases(...args) {
- return downloadDatabases(...args);
- }
-
- mergeDatabases(...args) {
- return mergeDatabases(...args);
- }
-
- getTestsTreeFromDatabase(...args) {
- return getTestsTreeFromDatabase(...args);
- }
-};
diff --git a/lib/plugin-api.ts b/lib/plugin-api.ts
new file mode 100644
index 000000000..42a9d17e5
--- /dev/null
+++ b/lib/plugin-api.ts
@@ -0,0 +1,96 @@
+import EventsEmitter2 from 'eventemitter2';
+import {PluginEvents} from './constants';
+import {downloadDatabases, mergeDatabases, getTestsTreeFromDatabase} from './db-utils/server';
+import {LocalImagesSaver} from './local-images-saver';
+import {version} from '../package.json';
+import {ImagesSaver, ReporterConfig, ReportsSaver} from './types';
+
+interface HtmlReporterValues {
+ extraItems: Record;
+ metaInfoExtenders: Record;
+ imagesSaver: ImagesSaver;
+ reportsSaver: ReportsSaver | null;
+}
+
+export class HtmlReporter extends EventsEmitter2 {
+ protected _config: ReporterConfig;
+ protected _values: HtmlReporterValues;
+ protected _version: string;
+
+ static create(this: new (config: ReporterConfig) => T, config: ReporterConfig): T {
+ return new this(config);
+ }
+
+ constructor(config: ReporterConfig) {
+ super();
+
+ this._config = config;
+ this._values = {
+ extraItems: {},
+ metaInfoExtenders: {},
+ imagesSaver: LocalImagesSaver,
+ reportsSaver: null
+ };
+ this._version = version;
+ }
+
+ get config(): ReporterConfig {
+ return this._config;
+ }
+
+ get version(): string {
+ return this._version;
+ }
+
+ get events(): typeof PluginEvents {
+ return PluginEvents;
+ }
+
+ addExtraItem(key: string, value: string): void {
+ this._values.extraItems[key] = value;
+ }
+
+ get extraItems(): Record {
+ return this._values.extraItems;
+ }
+
+ addMetaInfoExtender(key: string, value: string): void {
+ this._values.metaInfoExtenders[key] = value;
+ }
+
+ get metaInfoExtenders(): Record {
+ return this._values.metaInfoExtenders;
+ }
+
+ set imagesSaver(imagesSaver: ImagesSaver) {
+ this._values.imagesSaver = imagesSaver;
+ }
+
+ get imagesSaver(): ImagesSaver {
+ return this._values.imagesSaver;
+ }
+
+ set reportsSaver(reportsSaver: ReportsSaver) {
+ this._values.reportsSaver = reportsSaver;
+ }
+
+ get reportsSaver(): ReportsSaver | null {
+ return this._values.reportsSaver;
+ }
+
+ get values(): HtmlReporterValues {
+ return this._values;
+ }
+
+ downloadDatabases(...args: Parameters): ReturnType {
+ return downloadDatabases(...args);
+ }
+
+ mergeDatabases(...args: Parameters): ReturnType {
+ return mergeDatabases(...args);
+ }
+
+ getTestsTreeFromDatabase(...args: Parameters): ReturnType {
+ return getTestsTreeFromDatabase(...args);
+ }
+}
diff --git a/lib/report-builder/gui.js b/lib/report-builder/gui.js
index acc99e6d2..b5f393fd7 100644
--- a/lib/report-builder/gui.js
+++ b/lib/report-builder/gui.js
@@ -3,7 +3,7 @@
const _ = require('lodash');
const path = require('path');
-const StaticReportBuilder = require('./static');
+const {StaticReportBuilder} = require('./static');
const GuiTestsTreeBuilder = require('../tests-tree-builder/gui');
const {IDLE, RUNNING, SKIPPED, FAIL, SUCCESS, UPDATED} = require('../constants/test-statuses');
const {hasResultFails, hasNoRefImageErrors} = require('../static/modules/utils');
diff --git a/lib/report-builder/static.js b/lib/report-builder/static.js
deleted file mode 100644
index fa0f12e03..000000000
--- a/lib/report-builder/static.js
+++ /dev/null
@@ -1,163 +0,0 @@
-'use strict';
-
-const _ = require('lodash');
-const path = require('path');
-const fs = require('fs-extra');
-
-const {IDLE, RUNNING, SKIPPED, FAIL, ERROR, SUCCESS} = require('../constants/test-statuses');
-const {LOCAL_DATABASE_NAME} = require('../constants/database');
-const {SqliteAdapter} = require('../sqlite-adapter');
-const {TestAdapter} = require('../test-adapter');
-const {hasNoRefImageErrors} = require('../static/modules/utils');
-const {hasImage, saveStaticFilesToReportDir, writeDatabaseUrlsFile} = require('../server-utils');
-
-const ignoredStatuses = [RUNNING, IDLE];
-
-module.exports = class StaticReportBuilder {
- static create(...args) {
- return new this(...args);
- }
-
- constructor(hermione, pluginConfig, {reuse = false} = {}) {
- this._hermione = hermione;
- this._pluginConfig = pluginConfig;
-
- this._sqliteAdapter = SqliteAdapter.create({
- hermione: this._hermione,
- reportPath: this._pluginConfig.path,
- reuse
- });
- }
-
- async init() {
- await this._sqliteAdapter.init();
- }
-
- format(result, status) {
- result.timestamp = Date.now();
-
- return result instanceof TestAdapter
- ? result
- : TestAdapter.create(result, {
- hermione: this._hermione,
- sqliteAdapter: this._sqliteAdapter,
- status
- });
- }
-
- async saveStaticFiles() {
- const destPath = this._pluginConfig.path;
-
- await Promise.all(
- [
- saveStaticFilesToReportDir(this._hermione, this._pluginConfig, destPath),
- writeDatabaseUrlsFile(destPath, [LOCAL_DATABASE_NAME])
- ]
- );
- }
-
- addSkipped(result) {
- const formattedResult = this.format(result);
-
- return this._addTestResult(formattedResult, {
- status: SKIPPED,
- skipReason: formattedResult.suite.skipComment
- });
- }
-
- addSuccess(result) {
- return this._addTestResult(this.format(result), {status: SUCCESS});
- }
-
- addFail(result) {
- return this._addFailResult(this.format(result));
- }
-
- addError(result) {
- return this._addErrorResult(this.format(result));
- }
-
- addRetry(result) {
- const formattedResult = this.format(result);
-
- if (formattedResult.hasDiff()) {
- return this._addFailResult(formattedResult);
- } else {
- return this._addErrorResult(formattedResult);
- }
- }
-
- _addFailResult(formattedResult) {
- return this._addTestResult(formattedResult, {status: FAIL});
- }
-
- _addErrorResult(formattedResult) {
- return this._addTestResult(formattedResult, {status: ERROR, error: formattedResult.error});
- }
-
- _addTestResult(formattedResult, props) {
- formattedResult.image = hasImage(formattedResult);
-
- const testResult = this._createTestResult(formattedResult, _.extend(props, {
- timestamp: formattedResult.timestamp
- }));
-
- if (hasNoRefImageErrors(formattedResult)) {
- testResult.status = FAIL;
- }
-
- if (!ignoredStatuses.includes(testResult.status)) {
- this._writeTestResultToDb(testResult, formattedResult);
- }
-
- return formattedResult;
- }
-
- _createTestResult(result, props) {
- const {
- browserId, suite, sessionId, description, history,
- imagesInfo, screenshot, multipleTabs, errorDetails
- } = result;
-
- const {baseHost, saveErrorDetails} = this._pluginConfig;
- const suiteUrl = suite.getUrl({browserId, baseHost});
- const metaInfo = _.merge(_.cloneDeep(result.meta), {url: suite.fullUrl, file: suite.file, sessionId});
-
- const testResult = Object.assign({
- suiteUrl, name: browserId, metaInfo, description, history,
- imagesInfo, screenshot: Boolean(screenshot), multipleTabs
- }, props);
-
- if (saveErrorDetails && errorDetails) {
- testResult.errorDetails = _.pick(errorDetails, ['title', 'filePath']);
- }
-
- return testResult;
- }
-
- _writeTestResultToDb(testResult, formattedResult) {
- const {suite} = formattedResult;
- const suiteName = formattedResult.state.name;
- const suitePath = suite.path.concat(suiteName);
-
- this._sqliteAdapter.write({testResult, suitePath, suiteName});
- }
-
- _deleteTestResultFromDb({where, orderBy, orderDescending, limit}, ...args) {
- this._sqliteAdapter.delete({where, orderBy, orderDescending, limit}, ...args);
- }
-
- async finalize() {
- this._sqliteAdapter.close();
-
- const reportsSaver = this._hermione.htmlReporter.reportsSaver;
-
- if (reportsSaver) {
- const reportDir = this._pluginConfig.path;
- const src = path.join(reportDir, LOCAL_DATABASE_NAME);
- const dbPath = await reportsSaver.saveReportData(src, {destPath: LOCAL_DATABASE_NAME, reportDir: reportDir});
- await writeDatabaseUrlsFile(reportDir, [dbPath]);
- await fs.remove(src);
- }
- }
-};
diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts
new file mode 100644
index 000000000..388c4bbb9
--- /dev/null
+++ b/lib/report-builder/static.ts
@@ -0,0 +1,173 @@
+import _ from 'lodash';
+import path from 'path';
+import fs from 'fs-extra';
+import type {default as Hermione} from 'hermione';
+
+import {IDLE, RUNNING, SKIPPED, FAIL, ERROR, SUCCESS, TestStatus, LOCAL_DATABASE_NAME} from '../constants';
+import {PreparedTestResult, SqliteAdapter} from '../sqlite-adapter';
+import {TestAdapter} from '../test-adapter';
+import {hasNoRefImageErrors} from '../static/modules/utils';
+import {hasImage, saveStaticFilesToReportDir, writeDatabaseUrlsFile} from '../server-utils';
+import {HtmlReporterApi, ReporterConfig, TestResult} from '../types';
+
+const ignoredStatuses = [RUNNING, IDLE];
+
+interface StaticReportBuilderOptions {
+ reuse: boolean;
+}
+
+export class StaticReportBuilder {
+ protected _hermione: Hermione & HtmlReporterApi;
+ protected _pluginConfig: ReporterConfig;
+ protected _sqliteAdapter: SqliteAdapter;
+
+ static create(
+ this: new (hermione: Hermione & HtmlReporterApi, pluginConfig: ReporterConfig, options?: Partial) => T,
+ hermione: Hermione & HtmlReporterApi,
+ pluginConfig: ReporterConfig,
+ options?: Partial
+ ): T {
+ return new this(hermione, pluginConfig, options);
+ }
+
+ constructor(hermione: Hermione & HtmlReporterApi, pluginConfig: ReporterConfig, {reuse = false}: Partial = {}) {
+ this._hermione = hermione;
+ this._pluginConfig = pluginConfig;
+
+ this._sqliteAdapter = SqliteAdapter.create({
+ hermione: this._hermione,
+ reportPath: this._pluginConfig.path,
+ reuse
+ });
+ }
+
+ async init(): Promise {
+ await this._sqliteAdapter.init();
+ }
+
+ format(result: TestResult | TestAdapter, status: TestStatus): TestAdapter {
+ result.timestamp = Date.now();
+
+ return result instanceof TestAdapter
+ ? result
+ : TestAdapter.create(result, {
+ hermione: this._hermione,
+ sqliteAdapter: this._sqliteAdapter,
+ status
+ });
+ }
+
+ async saveStaticFiles(): Promise {
+ const destPath = this._pluginConfig.path;
+
+ await Promise.all([
+ saveStaticFilesToReportDir(this._hermione, this._pluginConfig, destPath),
+ writeDatabaseUrlsFile(destPath, [LOCAL_DATABASE_NAME])
+ ]);
+ }
+
+ addSkipped(result: TestResult | TestAdapter): TestAdapter {
+ const formattedResult = this.format(result, SKIPPED);
+
+ return this._addTestResult(formattedResult, {
+ status: SKIPPED,
+ skipReason: formattedResult.suite.skipComment
+ });
+ }
+
+ addSuccess(result: TestResult | TestAdapter): TestAdapter {
+ return this._addTestResult(this.format(result, SUCCESS), {status: SUCCESS});
+ }
+
+ addFail(result: TestResult | TestAdapter): TestAdapter {
+ return this._addFailResult(this.format(result, FAIL));
+ }
+
+ addError(result: TestResult | TestAdapter): TestAdapter {
+ return this._addErrorResult(this.format(result, ERROR));
+ }
+
+ addRetry(result: TestResult | TestAdapter): TestAdapter {
+ const formattedResult = this.format(result, FAIL);
+
+ if (formattedResult.hasDiff()) {
+ return this._addFailResult(formattedResult);
+ } else {
+ return this._addErrorResult(formattedResult);
+ }
+ }
+
+ protected _addFailResult(formattedResult: TestAdapter): TestAdapter {
+ return this._addTestResult(formattedResult, {status: FAIL});
+ }
+
+ protected _addErrorResult(formattedResult: TestAdapter): TestAdapter {
+ return this._addTestResult(formattedResult, {status: ERROR, error: formattedResult.error});
+ }
+
+ protected _addTestResult(formattedResult: TestAdapter, props: {status: TestStatus} & Partial): TestAdapter {
+ formattedResult.image = hasImage(formattedResult);
+
+ const testResult = this._createTestResult(formattedResult, _.extend(props, {
+ timestamp: formattedResult.timestamp
+ }));
+
+ if (hasNoRefImageErrors(formattedResult)) {
+ testResult.status = FAIL;
+ }
+
+ if (!ignoredStatuses.includes(testResult.status)) {
+ this._writeTestResultToDb(testResult, formattedResult);
+ }
+
+ return formattedResult;
+ }
+
+ _createTestResult(result: TestAdapter, props: {status: TestStatus} & Partial): PreparedTestResult {
+ const {
+ browserId, suite, sessionId, description, history,
+ imagesInfo = [], screenshot, multipleTabs, errorDetails
+ } = result;
+
+ const {baseHost, saveErrorDetails} = this._pluginConfig;
+ const suiteUrl: string = suite.getUrl({baseHost});
+ const metaInfo = _.merge(_.cloneDeep(result.meta), {url: suite.fullUrl, file: suite.file, sessionId});
+
+ const testResult: PreparedTestResult = Object.assign({
+ suiteUrl, name: browserId, metaInfo, description, history,
+ imagesInfo, screenshot: Boolean(screenshot), multipleTabs
+ }, props);
+
+ if (saveErrorDetails && errorDetails) {
+ testResult.errorDetails = _.pick(errorDetails, ['title', 'filePath']);
+ }
+
+ return testResult;
+ }
+
+ _writeTestResultToDb(testResult: PreparedTestResult, formattedResult: TestAdapter): void {
+ const {suite} = formattedResult;
+ const suiteName = formattedResult.state.name;
+ const suitePath = suite.path.concat(suiteName);
+
+ this._sqliteAdapter.write({testResult, suitePath, suiteName});
+ }
+
+ _deleteTestResultFromDb(...args: Parameters): void {
+ this._sqliteAdapter.delete(...args);
+ }
+
+ async finalize(): Promise {
+ this._sqliteAdapter.close();
+
+ const reportsSaver = this._hermione.htmlReporter.reportsSaver;
+
+ if (reportsSaver) {
+ const reportDir = this._pluginConfig.path;
+ const src = path.join(reportDir, LOCAL_DATABASE_NAME);
+ const dbPath = await reportsSaver.saveReportData(src, {destPath: LOCAL_DATABASE_NAME, reportDir: reportDir});
+ await writeDatabaseUrlsFile(reportDir, [dbPath]);
+ await fs.remove(src);
+ }
+ }
+}
diff --git a/lib/server-utils.ts b/lib/server-utils.ts
index c7fb02073..fccc4d9b3 100644
--- a/lib/server-utils.ts
+++ b/lib/server-utils.ts
@@ -5,7 +5,7 @@ import _ from 'lodash';
import fs from 'fs-extra';
import {logger} from './common-utils';
import {UPDATED, RUNNING, IDLE, SKIPPED, IMAGES_PATH, TestStatus} from './constants';
-import type HtmlReporter from './plugin-api';
+import type {HtmlReporter} from './plugin-api';
import type {TestAdapter} from './test-adapter';
import {CustomGuiItem, HtmlReporterApi, ReporterConfig} from './types';
import type Hermione from 'hermione';
diff --git a/lib/sqlite-adapter.ts b/lib/sqlite-adapter.ts
index 7ec859500..0d0502a2c 100644
--- a/lib/sqlite-adapter.ts
+++ b/lib/sqlite-adapter.ts
@@ -9,9 +9,9 @@ import NestedError from 'nested-error-stacks';
import {getShortMD5} from './common-utils';
import {TestStatus} from './constants';
import {DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, LOCAL_DATABASE_NAME, DATABASE_URLS_JSON_NAME} from './constants/database';
-import {createTablesQuery} from './db-utils/server';
+import {createTablesQuery} from './db-utils/common';
import {DbNotInitializedError} from './errors/db-not-initialized-error';
-import type {HtmlReporterApi, ImageInfoFull} from './types';
+import type {ErrorDetails, HtmlReporterApi, ImageInfoFull} from './types';
const debug = makeDebug('html-reporter:sqlite-adapter');
@@ -37,13 +37,14 @@ export interface PreparedTestResult {
metaInfo: Record;
history: string[];
description: unknown;
- error: Error;
+ error?: {message?: string; stack?: string; stateName?: string};
skipReason?: string;
imagesInfo: ImageInfoFull[];
screenshot: boolean;
multipleTabs: boolean;
status: TestStatus;
timestamp?: number;
+ errorDetails?: ErrorDetails;
}
interface ParseTestResultParams {
diff --git a/lib/static/modules/actions.js b/lib/static/modules/actions.js
index 73f1b2491..817afeaf4 100644
--- a/lib/static/modules/actions.js
+++ b/lib/static/modules/actions.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import {isEmpty, difference} from 'lodash';
import {notify, dismissNotification as dismissNotify, POSITIONS} from 'reapop';
-import StaticTestsTreeBuilder from '../../tests-tree-builder/static';
+import {StaticTestsTreeBuilder} from '../../tests-tree-builder/static';
import actionNames from './action-names';
import {types as modalTypes} from '../components/modals';
import {QUEUED} from '../../constants/test-statuses';
diff --git a/lib/static/modules/custom-queries.js b/lib/static/modules/custom-queries.js
index 1d996a50f..f31ee33bf 100644
--- a/lib/static/modules/custom-queries.js
+++ b/lib/static/modules/custom-queries.js
@@ -3,7 +3,7 @@
import {parseQuery, decodeBrowsers} from './query-params';
import {pick, has} from 'lodash';
import {ViewMode} from '../../constants/view-modes';
-import {config} from '../../constants/defaults';
+import {configDefaults} from '../../constants/defaults';
const allowedViewModes = new Set(Object.values(ViewMode));
@@ -13,7 +13,7 @@ export function getViewQuery(queryString) {
query.filteredBrowsers = decodeBrowsers(query.filteredBrowsers);
if (has(query, 'viewMode') && !allowedViewModes.has(query.viewMode)) {
- query.viewMode = config.defaultView;
+ query.viewMode = configDefaults.defaultView;
}
return pick(query, [
diff --git a/lib/static/modules/default-state.js b/lib/static/modules/default-state.js
index 2ac31c190..59d073bbc 100644
--- a/lib/static/modules/default-state.js
+++ b/lib/static/modules/default-state.js
@@ -1,12 +1,12 @@
'use strict';
-const defaults = require('../../constants/defaults');
+const {configDefaults} = require('../../constants/defaults');
const {ViewMode} = require('../../constants/view-modes');
const {DiffModes} = require('../../constants/diff-modes');
const {EXPAND_ERRORS} = require('../../constants/expand-modes');
const {RESULT_KEYS} = require('../../constants/group-tests');
-export default Object.assign(defaults, {
+export default Object.assign({config: configDefaults}, {
gui: true,
running: false,
processing: false,
diff --git a/lib/static/modules/reducers/browsers.js b/lib/static/modules/reducers/browsers.js
index 83f1ce14f..924c1ed1f 100644
--- a/lib/static/modules/reducers/browsers.js
+++ b/lib/static/modules/reducers/browsers.js
@@ -1,6 +1,6 @@
import _ from 'lodash';
import actionNames from '../action-names';
-import {versions as BrowserVersions} from '../../../constants/browser';
+import {BrowserVersions} from '../../../constants/browser';
export default (state, action) => {
switch (action.type) {
diff --git a/lib/suite-adapter.js b/lib/suite-adapter.js
deleted file mode 100644
index fb386b170..000000000
--- a/lib/suite-adapter.js
+++ /dev/null
@@ -1,71 +0,0 @@
-'use strict';
-
-const _ = require('lodash');
-const path = require('path');
-const url = require('url');
-const Uri = require('urijs');
-
-const {getSuitePath} = require('./plugin-utils');
-
-const wrapLinkByTag = (text) => {
- return text.replace(/https?:\/\/[^\s]*/g, (url) => {
- return `${url}`;
- });
-};
-
-function getSkipComment(suite) {
- return suite.skipReason || suite.parent && getSkipComment(suite.parent);
-}
-
-module.exports = class SuiteAdapter {
- static create(suite = {}, config = {}) {
- return new this(suite, config);
- }
-
- constructor(suite, config) {
- this._suite = suite;
- this._config = config;
- }
-
- _wrapSkipComment(skipComment) {
- return skipComment ? wrapLinkByTag(skipComment) : 'Unknown reason';
- }
-
- _configureUrl(url, baseHost) {
- return _.isEmpty(baseHost)
- ? url
- : Uri(baseHost).resource(url).href();
- }
-
- get skipComment() {
- const skipComment = getSkipComment(this._suite);
-
- return this._wrapSkipComment(skipComment);
- }
-
- get fullName() {
- return this._suite.fullTitle();
- }
-
- get path() {
- return getSuitePath(this._suite.parent);
- }
-
- get file() {
- return path.relative(process.cwd(), this._suite.file);
- }
-
- getUrl(opts = {}) {
- const url = _.get(this, '_suite.meta.url', '');
-
- return this._configureUrl(url, opts.baseHost);
- }
-
- get fullUrl() {
- const baseUrl = this.getUrl();
-
- return baseUrl
- ? url.parse(baseUrl).path
- : '';
- }
-};
diff --git a/lib/suite-adapter.ts b/lib/suite-adapter.ts
new file mode 100644
index 000000000..f7feb2e81
--- /dev/null
+++ b/lib/suite-adapter.ts
@@ -0,0 +1,70 @@
+import _ from 'lodash';
+import path from 'path';
+import url from 'url';
+import Uri from 'urijs';
+import {getSuitePath} from './plugin-utils';
+import {Suite, TestResult} from './types';
+
+const wrapLinkByTag = (text: string): string => {
+ return text.replace(/https?:\/\/[^\s]*/g, (url) => {
+ return `${url}`;
+ });
+};
+
+function getSkipComment(suite: TestResult | Suite): string | null | undefined {
+ return suite.skipReason || suite.parent && getSkipComment(suite.parent);
+}
+
+export class SuiteAdapter {
+ protected _suite: TestResult;
+
+ static create(suite: TestResult): SuiteAdapter {
+ return new this(suite);
+ }
+
+ constructor(suite: TestResult) {
+ this._suite = suite;
+ }
+
+ protected _wrapSkipComment(skipComment: string | null | undefined): string {
+ return skipComment ? wrapLinkByTag(skipComment) : 'Unknown reason';
+ }
+
+ protected _configureUrl(url: string, baseHost: string): string {
+ return _.isEmpty(baseHost)
+ ? url
+ : Uri(baseHost).resource(url).href();
+ }
+
+ get skipComment(): string {
+ const skipComment = getSkipComment(this._suite);
+
+ return this._wrapSkipComment(skipComment);
+ }
+
+ get fullName(): string {
+ return this._suite.fullTitle();
+ }
+
+ get path(): string[] {
+ return getSuitePath(this._suite.parent);
+ }
+
+ get file(): string {
+ return path.relative(process.cwd(), this._suite.file);
+ }
+
+ getUrl(opts: { baseHost?: string } = {}): string {
+ const url = _.get(this, '_suite.meta.url', '') as string;
+
+ return this._configureUrl(url, opts.baseHost || '');
+ }
+
+ get fullUrl(): string {
+ const baseUrl = this.getUrl();
+
+ return baseUrl
+ ? url.parse(baseUrl).path || ''
+ : '';
+ }
+}
diff --git a/lib/test-adapter.ts b/lib/test-adapter.ts
index f8e030610..ccda49982 100644
--- a/lib/test-adapter.ts
+++ b/lib/test-adapter.ts
@@ -5,7 +5,7 @@ import tmp from 'tmp';
import crypto from 'crypto';
import type {default as Hermione} from 'hermione';
-import SuiteAdapter from './suite-adapter';
+import {SuiteAdapter} from './suite-adapter';
import {DB_COLUMNS} from './constants/database';
import {getSuitePath} from './plugin-utils';
import {getCommandsHistory} from './history-utils';
@@ -18,7 +18,7 @@ import {
HtmlReporterApi,
ImageInfo,
ImagesSaver,
- SuitesRow,
+ LabeledSuitesRow,
TestResult,
ImageData,
ImageInfoFull, ImageDiffError, AssertViewResult, ImageInfoError,
@@ -26,7 +26,7 @@ import {
} from './types';
import type {SqliteAdapter} from './sqlite-adapter';
import EventEmitter2 from 'eventemitter2';
-import type HtmlReporter from './plugin-api';
+import type {HtmlReporter} from './plugin-api';
import type * as Workers from './workers/worker';
interface PrepareTestResultData {
@@ -91,6 +91,8 @@ export class TestAdapter {
this._attempt = testsAttempts.get(this._testId) || 0;
}
+ image?: boolean;
+
get suite(): SuiteAdapter {
return this._suite;
}
@@ -121,14 +123,14 @@ export class TestAdapter {
const suitePath = getSuitePath(this._testResult);
const suitePathString = JSON.stringify(suitePath);
- const imagesInfoResult = this._sqliteAdapter.query | undefined>({
+ const imagesInfoResult = this._sqliteAdapter.query | undefined>({
select: DB_COLUMNS.IMAGES_INFO,
where: `${DB_COLUMNS.SUITE_PATH} = ? AND ${DB_COLUMNS.NAME} = ?`,
orderBy: DB_COLUMNS.TIMESTAMP,
orderDescending: true
}, suitePathString, browserName);
- const imagesInfo: ImageInfoFull[] = imagesInfoResult && JSON.parse(imagesInfoResult[DB_COLUMNS.IMAGES_INFO as keyof SuitesRow]) || [];
+ const imagesInfo: ImageInfoFull[] = imagesInfoResult && JSON.parse(imagesInfoResult[DB_COLUMNS.IMAGES_INFO as keyof Pick]) || [];
return imagesInfo.find(info => info.stateName === stateName);
}
diff --git a/lib/tests-tree-builder/base.js b/lib/tests-tree-builder/base.ts
similarity index 50%
rename from lib/tests-tree-builder/base.js
rename to lib/tests-tree-builder/base.ts
index 621ff31f4..42fd78afe 100644
--- a/lib/tests-tree-builder/base.js
+++ b/lib/tests-tree-builder/base.ts
@@ -1,11 +1,82 @@
-'use strict';
-
-const _ = require('lodash');
-const {determineStatus} = require('../common-utils');
-const {versions: browserVersions} = require('../constants/browser');
-
-module.exports = class ResultsTreeBuilder {
- static create() {
+import _ from 'lodash';
+import {determineStatus} from '../common-utils';
+import {TestStatus, BrowserVersions} from '../constants';
+import {TestAdapter} from '../test-adapter';
+import {ImageInfoFull, ParsedSuitesRow} from '../types';
+
+type TreeResult = {
+ id: string;
+ parentId: string;
+ status: TestStatus;
+ imageIds: string[];
+} & Omit;
+
+interface TreeBrowser {
+ id: string;
+ name: string;
+ parentId: string;
+ resultIds: string[];
+ version: string;
+}
+
+interface TreeSuite {
+ status?: TestStatus;
+ id: string;
+ parentId: string | null;
+ name: string;
+ suitePath: string[];
+ root: boolean;
+ suiteIds?: string[];
+ browserIds?: string[];
+}
+
+type TreeImages = {
+ id: string;
+ parentId: string;
+} & ImageInfoFull;
+
+export interface Tree {
+ suites: {
+ byId: Record,
+ allIds: string[],
+ allRootIds: string[]
+ },
+ browsers: {
+ byId: Record,
+ allIds: string[]
+ },
+ results: {
+ byId: Record,
+ allIds: string[]
+ },
+ images: {
+ byId: Record,
+ allIds: string[]
+ }
+}
+
+interface ResultPayload {
+ id: string;
+ parentId: string;
+ result: ParsedSuitesRow;
+}
+
+interface BrowserPayload {
+ id: string;
+ name: string;
+ parentId: string;
+ version: string;
+}
+
+interface ImagesPayload {
+ imagesInfo: ImageInfoFull[];
+ parentId: string;
+}
+
+export class BaseTestsTreeBuilder {
+ protected _tree: Tree;
+
+ static create(this: new () => T): T {
return new this();
}
@@ -18,12 +89,12 @@ module.exports = class ResultsTreeBuilder {
};
}
- get tree() {
+ get tree(): Tree {
return this._tree;
}
- sortTree() {
- const sortChildSuites = (suiteId) => {
+ sortTree(): void {
+ const sortChildSuites = (suiteId: string): void => {
const childSuite = this._tree.suites.byId[suiteId];
if (childSuite.suiteIds) {
@@ -38,16 +109,16 @@ module.exports = class ResultsTreeBuilder {
this._tree.suites.allRootIds.sort().forEach(sortChildSuites);
}
- addTestResult(testResult, formattedResult) {
+ addTestResult(testResult: ParsedSuitesRow, formattedResult: Pick): void {
const {testPath, browserId: browserName, attempt} = formattedResult;
const {imagesInfo} = testResult;
- const {browserVersion = browserVersions.UNKNOWN} = testResult.metaInfo;
+ const {browserVersion = BrowserVersions.UNKNOWN} = testResult.metaInfo as {browserVersion: string};
const suiteId = this._buildId(testPath);
const browserId = this._buildId(suiteId, browserName);
- const testResultId = this._buildId(browserId, attempt);
+ const testResultId = this._buildId(browserId, attempt.toString());
const imageIds = imagesInfo
- .map((image, i) => this._buildId(testResultId, image.stateName || `${image.status}_${i}`));
+ .map((image: ImageInfoFull, i: number) => this._buildId(testResultId, image.stateName || `${image.status}_${i}`));
this._addSuites(testPath, browserId);
this._addBrowser({id: browserId, parentId: suiteId, name: browserName, version: browserVersion}, testResultId, attempt);
@@ -57,11 +128,11 @@ module.exports = class ResultsTreeBuilder {
this._setStatusForBranch(testPath);
}
- _buildId(parentId = [], name = []) {
- return [].concat(parentId, name).join(' ');
+ protected _buildId(parentId: string | string[] = [], name: string | string[] = []): string {
+ return ([] as string[]).concat(parentId, name).join(' ');
}
- _addSuites(testPath, browserId) {
+ protected _addSuites(testPath: string[], browserId: string): void {
testPath.reduce((suites, name, ind, arr) => {
const isRoot = ind === 0;
const suitePath = isRoot ? [name] : arr.slice(0, ind + 1);
@@ -69,7 +140,7 @@ module.exports = class ResultsTreeBuilder {
if (!suites.byId[id]) {
const parentId = isRoot ? null : this._buildId(suitePath.slice(0, -1));
- const suite = {id, parentId, name, suitePath, root: isRoot};
+ const suite: TreeSuite = {id, parentId, name, suitePath, root: isRoot};
this._addSuite(suite);
}
@@ -85,7 +156,7 @@ module.exports = class ResultsTreeBuilder {
}, this._tree.suites);
}
- _addSuite(suite) {
+ protected _addSuite(suite: TreeSuite): void {
const {suites} = this._tree;
suites.byId[suite.id] = suite;
@@ -96,7 +167,7 @@ module.exports = class ResultsTreeBuilder {
}
}
- _addNodeId(parentSuiteId, nodeId, {fieldName}) {
+ protected _addNodeId(parentSuiteId: string, nodeId: string, {fieldName}: {fieldName: 'browserIds' | 'suiteIds'}): void {
const {suites} = this._tree;
if (!suites.byId[parentSuiteId][fieldName]) {
@@ -105,15 +176,15 @@ module.exports = class ResultsTreeBuilder {
}
if (!this._isNodeIdExists(parentSuiteId, nodeId, {fieldName})) {
- suites.byId[parentSuiteId][fieldName].push(nodeId);
+ suites.byId[parentSuiteId][fieldName]?.push(nodeId);
}
}
- _isNodeIdExists(parentSuiteId, nodeId, {fieldName}) {
+ protected _isNodeIdExists(parentSuiteId: string, nodeId: string, {fieldName}: {fieldName: 'browserIds' | 'suiteIds'}): boolean {
return _.includes(this._tree.suites.byId[parentSuiteId][fieldName], nodeId);
}
- _addBrowser({id, parentId, name, version}, testResultId, attempt) {
+ protected _addBrowser({id, parentId, name, version}: BrowserPayload, testResultId: string, attempt: number): void {
const {browsers} = this._tree;
if (!browsers.byId[id]) {
@@ -124,11 +195,11 @@ module.exports = class ResultsTreeBuilder {
this._addResultIdToBrowser(id, testResultId, attempt);
}
- _addResultIdToBrowser(browserId, testResultId, attempt) {
+ protected _addResultIdToBrowser(browserId: string, testResultId: string, attempt: number): void {
this._tree.browsers.byId[browserId].resultIds[attempt] = testResultId;
}
- _addResult({id, parentId, result}, imageIds) {
+ protected _addResult({id, parentId, result}: ResultPayload, imageIds: string[]): void {
const resultWithoutImagesInfo = _.omit(result, 'imagesInfo');
if (!this._tree.results.byId[id]) {
@@ -138,14 +209,14 @@ module.exports = class ResultsTreeBuilder {
this._tree.results.byId[id] = {id, parentId, ...resultWithoutImagesInfo, imageIds};
}
- _addImages(imageIds, {imagesInfo, parentId}) {
+ protected _addImages(imageIds: string[], {imagesInfo, parentId}: ImagesPayload): void {
imageIds.forEach((id, ind) => {
this._tree.images.byId[id] = {...imagesInfo[ind], id, parentId};
this._tree.images.allIds.push(id);
});
}
- _setStatusForBranch(testPath = []) {
+ protected _setStatusForBranch(testPath: string[] = []): void {
const suiteId = this._buildId(testPath);
if (!suiteId) {
@@ -154,25 +225,25 @@ module.exports = class ResultsTreeBuilder {
const suite = this._tree.suites.byId[suiteId];
- const resultStatuses = _.compact([].concat(suite.browserIds))
- .map((browserId) => {
+ const resultStatuses = _.compact(([] as (string | undefined)[]).concat(suite.browserIds))
+ .map((browserId: string) => {
const browser = this._tree.browsers.byId[browserId];
- const lastResultId = _.last(browser.resultIds);
+ const lastResultId = _.last(browser.resultIds) as string;
return this._tree.results.byId[lastResultId].status;
});
- const childrenSuiteStatuses = _.compact([].concat(suite.suiteIds))
- .map((childSuiteId) => this._tree.suites.byId[childSuiteId].status);
+ const childrenSuiteStatuses = _.compact(([] as (string | undefined)[]).concat(suite.suiteIds))
+ .map((childSuiteId: string) => this._tree.suites.byId[childSuiteId].status);
- const status = determineStatus([...resultStatuses, ...childrenSuiteStatuses]);
+ const status = determineStatus(_.compact([...resultStatuses, ...childrenSuiteStatuses]));
// if newly determined status is the same as current status, do nothing
if (suite.status === status) {
return;
}
- suite.status = status;
+ suite.status = status || undefined;
this._setStatusForBranch(testPath.slice(0, -1));
}
-};
+}
diff --git a/lib/tests-tree-builder/gui.js b/lib/tests-tree-builder/gui.js
index 32423f361..41caace3b 100644
--- a/lib/tests-tree-builder/gui.js
+++ b/lib/tests-tree-builder/gui.js
@@ -1,7 +1,7 @@
'use strict';
const _ = require('lodash');
-const BaseTestsTreeBuilder = require('./base');
+const {BaseTestsTreeBuilder} = require('./base');
const {UPDATED} = require('../constants/test-statuses');
const {isUpdatedStatus} = require('../common-utils');
diff --git a/lib/tests-tree-builder/static.js b/lib/tests-tree-builder/static.ts
similarity index 61%
rename from lib/tests-tree-builder/static.js
rename to lib/tests-tree-builder/static.ts
index 55dddccc0..24bb64e94 100644
--- a/lib/tests-tree-builder/static.js
+++ b/lib/tests-tree-builder/static.ts
@@ -1,12 +1,41 @@
-'use strict';
+import _ from 'lodash';
+import {BaseTestsTreeBuilder, Tree} from './base';
+import {BrowserVersions, DB_COLUMN_INDEXES, TestStatus} from '../constants';
+import {Attempt, ParsedSuitesRow, RawSuitesRow} from '../types';
+
+interface Stats {
+ total: number;
+ passed: number;
+ failed: number;
+ skipped: number;
+ retries: number;
+}
+
+type FinalStats = Stats & {
+ perBrowser: {
+ [browserName: string]: {
+ [browserVersion: string]: Stats
+ }
+ }
+}
-const _ = require('lodash');
-const BaseTestsTreeBuilder = require('./base');
-const testStatus = require('../constants/test-statuses');
-const {versions: browserVersions} = require('../constants/browser');
-const {DB_COLUMN_INDEXES} = require('../constants/database');
+interface SkipItem {
+ browser: string;
+ suite: string;
+ comment: string;
+}
+
+interface BrowserItem {
+ id: string;
+ versions: string[];
+}
+
+export class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
+ protected _stats: FinalStats;
+ protected _skips: SkipItem[];
+ protected _failedBrowserIds: { [key: string]: boolean };
+ protected _passedBrowserIds: { [key: string]: boolean };
-module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
constructor() {
super();
@@ -19,20 +48,19 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
this._passedBrowserIds = {};
}
- build(rows = []) {
- // in order to sync attempts between gui tree and static tree
- const attemptsMap = new Map();
- const browsers = {};
+ build(rows: RawSuitesRow[] = []): { tree: Tree; stats: FinalStats; skips: SkipItem[]; browsers: BrowserItem[] } {
+ const attemptsMap = new Map();
+ const browsers: Record> = {};
for (const row of rows) {
- const testPath = JSON.parse(row[DB_COLUMN_INDEXES.suitePath]);
- const browserName = row[DB_COLUMN_INDEXES.name];
+ const testPath: string[] = JSON.parse(row[DB_COLUMN_INDEXES.suitePath] as string);
+ const browserName = row[DB_COLUMN_INDEXES.name] as string;
const testId = this._buildId(testPath);
const browserId = this._buildId(testId, browserName);
- attemptsMap.set(browserId, attemptsMap.has(browserId) ? attemptsMap.get(browserId) + 1 : 0);
- const attempt = attemptsMap.get(browserId);
+ attemptsMap.set(browserId, attemptsMap.has(browserId) ? attemptsMap.get(browserId) as number + 1 : 0);
+ const attempt = attemptsMap.get(browserId) as number;
const testResult = mkTestResult(row, {attempt});
const formattedResult = {browserId: browserName, testPath, attempt};
@@ -53,14 +81,14 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
};
}
- _addResultIdToBrowser(browserId, testResultId) {
+ protected _addResultIdToBrowser(browserId: string, testResultId: string): void {
this._tree.browsers.byId[browserId].resultIds.push(testResultId);
}
- _calcStats(testResult, {testId, browserId, browserName}) {
+ protected _calcStats(testResult: ParsedSuitesRow, {testId, browserId, browserName}: { testId: string; browserId: string; browserName: string }): void {
const {status} = testResult;
const {browserVersion} = testResult.metaInfo;
- const version = browserVersion || browserVersions.UNKNOWN;
+ const version = browserVersion || BrowserVersions.UNKNOWN;
if (!this._stats.perBrowser[browserName]) {
this._stats.perBrowser[browserName] = {};
@@ -71,8 +99,8 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
}
switch (status) {
- case testStatus.FAIL:
- case testStatus.ERROR: {
+ case TestStatus.FAIL:
+ case TestStatus.ERROR: {
if (this._failedBrowserIds[browserId]) {
this._stats.retries++;
this._stats.perBrowser[browserName][version].retries++;
@@ -87,7 +115,7 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
return;
}
- case testStatus.SUCCESS: {
+ case TestStatus.SUCCESS: {
if (this._passedBrowserIds[browserId]) {
this._stats.retries++;
this._stats.perBrowser[browserName][version].retries++;
@@ -115,7 +143,7 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
return;
}
- case testStatus.SKIPPED: {
+ case TestStatus.SKIPPED: {
this._skips.push({
browser: browserName,
suite: testId,
@@ -137,9 +165,9 @@ module.exports = class StaticTestsTreeBuilder extends BaseTestsTreeBuilder {
}
}
}
-};
+}
-function initStats() {
+function initStats(): Stats {
return {
total: 0,
passed: 0,
@@ -149,30 +177,30 @@ function initStats() {
};
}
-function mkTestResult(row, data = {}) {
+function mkTestResult(row: RawSuitesRow, data: {attempt: number}): ParsedSuitesRow & Attempt {
return {
- description: row[DB_COLUMN_INDEXES.description],
- imagesInfo: JSON.parse(row[DB_COLUMN_INDEXES.imagesInfo]),
- metaInfo: JSON.parse(row[DB_COLUMN_INDEXES.metaInfo]),
- history: JSON.parse(row[DB_COLUMN_INDEXES.history]),
+ description: row[DB_COLUMN_INDEXES.description] as string | null,
+ imagesInfo: JSON.parse(row[DB_COLUMN_INDEXES.imagesInfo] as string),
+ metaInfo: JSON.parse(row[DB_COLUMN_INDEXES.metaInfo] as string),
+ history: JSON.parse(row[DB_COLUMN_INDEXES.history] as string),
multipleTabs: Boolean(row[DB_COLUMN_INDEXES.multipleTabs]),
- name: row[DB_COLUMN_INDEXES.name],
+ name: row[DB_COLUMN_INDEXES.name] as string,
screenshot: Boolean(row[DB_COLUMN_INDEXES.screenshot]),
- status: row[DB_COLUMN_INDEXES.status],
- suiteUrl: row[DB_COLUMN_INDEXES.suiteUrl],
- skipReason: row[DB_COLUMN_INDEXES.skipReason],
- error: JSON.parse(row[DB_COLUMN_INDEXES.error]),
+ status: row[DB_COLUMN_INDEXES.status] as TestStatus,
+ suiteUrl: row[DB_COLUMN_INDEXES.suiteUrl] as string,
+ skipReason: row[DB_COLUMN_INDEXES.skipReason] as string,
+ error: JSON.parse(row[DB_COLUMN_INDEXES.error] as string),
...data
};
}
-function addBrowserVersion(browsers, testResult) {
+function addBrowserVersion(browsers: Record>, testResult: ParsedSuitesRow): void {
const browserId = testResult.name;
if (!browsers[browserId]) {
browsers[browserId] = new Set();
}
- const {browserVersion = browserVersions.UNKNOWN} = testResult.metaInfo;
+ const {browserVersion = BrowserVersions.UNKNOWN} = testResult.metaInfo;
browsers[browserId].add(browserVersion);
}
diff --git a/lib/types.ts b/lib/types.ts
index f2c33bcce..53f3ce38a 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -1,13 +1,21 @@
import type {LooksSameOptions, CoordBounds} from 'looks-same';
import type {default as Hermione} from 'hermione';
-import {DiffMode, TestStatus, ViewMode} from './constants';
-import type HtmlReporter from './plugin-api';
+import {DiffModeId, SaveFormat, TestStatus, ViewMode} from './constants';
+import type {HtmlReporter} from './plugin-api';
declare module 'tmp' {
export const tmpdir: string;
}
-export interface Suite {
+interface ConfigurableTestObject {
+ browserId: string;
+ browserVersion?: string;
+ id: string;
+ file: string;
+ skipReason: string;
+}
+
+export interface Suite extends ConfigurableTestObject {
readonly root: boolean;
readonly title: string;
parent: Suite | null;
@@ -17,9 +25,13 @@ export interface ImagesSaver {
saveImg: (localFilePath: string, options: {destPath: string; reportDir: string}) => string | Promise;
}
+export interface ReportsSaver {
+ saveReportData: (localDbPath: string, options: {destPath: string; reportDir: string}) => string | Promise;
+}
+
export interface ErrorDetails {
title: string;
- data: unknown;
+ data?: unknown;
filePath: string;
}
@@ -89,7 +101,7 @@ export interface ImageDiffError {
export type AssertViewResult = ImageDiffError;
-export interface TestResult {
+export interface TestResult extends ConfigurableTestObject {
assertViewResults: AssertViewResult[];
description?: string;
err?: {
@@ -99,11 +111,8 @@ export interface TestResult {
details: ErrorDetails
};
fullTitle(): string;
- id: string;
title: string;
meta: Record
- browserId: string;
- browserVersion?: string;
sessionId: string;
timestamp: number;
imagesInfo: ImageInfoFull[];
@@ -112,16 +121,52 @@ export interface TestResult {
parent: Suite;
}
-export interface SuitesRow {
+export interface LabeledSuitesRow {
imagesInfo: string;
+ timestamp: number;
+}
+
+export type RawSuitesRow = LabeledSuitesRow[keyof LabeledSuitesRow][];
+
+export interface ParsedSuitesRow {
+ description: string | null;
+ error: {
+ message: string;
+ stack: string;
+ };
+ history: unknown;
+ imagesInfo: ImageInfoFull[];
+ metaInfo: {
+ browserVersion?: string;
+ [key: string]: unknown;
+ };
+ multipleTabs: boolean;
+ name: string;
+ screenshot: boolean;
+ skipReason: string;
+ status: TestStatus;
+ suiteUrl: string;
+}
+
+export interface Attempt {
+ attempt: number;
}
export interface HtmlReporterApi {
htmlReporter: HtmlReporter;
}
+export interface ErrorPattern {
+ name: string;
+ pattern: string;
+}
+
export interface PluginDescription {
name: string;
+ component: string;
+ point?: string;
+ position?: 'after' | 'before' | 'wrap';
+ config?: Record;
}
export interface CustomGuiItem {
@@ -133,14 +178,26 @@ export interface CustomGuiItem {
export interface ReporterConfig {
baseHost: string;
- defaultView: ViewMode;
+ commandsWithShortHistory: string[];
customGui: Record;
- customScripts: object[];
- diffMode: DiffMode;
- errorPatterns: object[];
+ customScripts: (() => void)[];
+ defaultView: ViewMode;
+ diffMode: DiffModeId;
+ enabled: boolean;
+ errorPatterns: ErrorPattern[];
+ lazyLoadOffset: number | null;
metaInfoBaseUrls: Record;
path: string;
plugins: PluginDescription[];
pluginsEnabled: boolean;
+ saveErrorDetails: boolean;
+ saveFormat: SaveFormat;
yandexMetrika: { counterNumber: null | number };
}
+
+export type ReporterOptions = Omit & {errorPatterns: (string | ErrorPattern)[]};
+
+export interface DbUrlsJsonData {
+ dbUrls: string[];
+ jsonUrls: string[];
+}
diff --git a/lib/workers/create-workers.js b/lib/workers/create-workers.js
deleted file mode 100644
index dff0bc7b0..000000000
--- a/lib/workers/create-workers.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-module.exports = (runner) => {
- const workerFilepath = require.resolve('./worker');
-
- return runner.registerWorkers(workerFilepath, ['saveDiffTo']);
-};
diff --git a/lib/workers/create-workers.ts b/lib/workers/create-workers.ts
new file mode 100644
index 000000000..5407ee888
--- /dev/null
+++ b/lib/workers/create-workers.ts
@@ -0,0 +1,15 @@
+import type {EventEmitter} from 'events';
+
+type MapOfMethods> = {
+ [K in T[number]]: (...args: Array) => Promise | unknown;
+};
+
+type RegisterWorkers> = EventEmitter & MapOfMethods;
+
+export const createWorkers = (
+ runner: {registerWorkers: (workerFilePath: string, exportedMethods: string[]) => RegisterWorkers<['saveDiffTo']>}
+): RegisterWorkers<['saveDiffTo']> => {
+ const workerFilepath = require.resolve('./worker');
+
+ return runner.registerWorkers(workerFilepath, ['saveDiffTo']);
+};
diff --git a/package-lock.json b/package-lock.json
index 4a6e2c62c..0d3a60c24 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "html-reporter",
- "version": "9.10.3",
+ "version": "9.10.3-hello",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "html-reporter",
- "version": "9.10.3",
+ "version": "9.10.3-hello",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.22.5",
@@ -54,6 +54,7 @@
"@types/nested-error-stacks": "^2.1.0",
"@types/opener": "^1.4.0",
"@types/tmp": "^0.1.0",
+ "@types/urijs": "^1.19.19",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"app-module-path": "^2.2.0",
@@ -4623,6 +4624,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@types/urijs": {
+ "version": "1.19.19",
+ "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.19.tgz",
+ "integrity": "sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==",
+ "dev": true
+ },
"node_modules/@types/webpack": {
"version": "4.41.33",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz",
@@ -35371,6 +35378,12 @@
}
}
},
+ "@types/urijs": {
+ "version": "1.19.19",
+ "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.19.tgz",
+ "integrity": "sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==",
+ "dev": true
+ },
"@types/webpack": {
"version": "4.41.33",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz",
diff --git a/package.json b/package.json
index 94a6e52ff..eca5127bb 100644
--- a/package.json
+++ b/package.json
@@ -102,6 +102,7 @@
"@types/nested-error-stacks": "^2.1.0",
"@types/opener": "^1.4.0",
"@types/tmp": "^0.1.0",
+ "@types/urijs": "^1.19.19",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"app-module-path": "^2.2.0",
diff --git a/test/unit/hermione.js b/test/unit/hermione.js
index 156133a8a..409013fc8 100644
--- a/test/unit/hermione.js
+++ b/test/unit/hermione.js
@@ -35,15 +35,16 @@ describe('lib/hermione', () => {
'./server-utils': utils
});
- const StaticReportBuilder = proxyquire('lib/report-builder/static', {
+ const {StaticReportBuilder} = proxyquire('lib/report-builder/static', {
+ 'fs-extra': fs,
'../server-utils': utils,
'../sqlite-adapter': {SqliteAdapter},
'../test-adapter': {TestAdapter}
});
- const PluginAdapter = proxyquire('lib/plugin-adapter', {
+ const {PluginAdapter} = proxyquire('lib/plugin-adapter', {
'./server-utils': utils,
- './report-builder/static': StaticReportBuilder,
+ './report-builder/static': {StaticReportBuilder},
'./plugin-api': proxyquire('lib/plugin-api', {
'./local-images-saver': proxyquire('lib/local-images-saver', {
'./server-utils': utils
@@ -52,7 +53,7 @@ describe('lib/hermione', () => {
});
const HermioneReporter = proxyquire('../../hermione', {
- './lib/plugin-adapter': PluginAdapter
+ './lib/plugin-adapter': {PluginAdapter}
});
const events = {
diff --git a/test/unit/lib/config/index.js b/test/unit/lib/config/index.js
index 82e980b73..17370f3b9 100644
--- a/test/unit/lib/config/index.js
+++ b/test/unit/lib/config/index.js
@@ -1,11 +1,11 @@
'use strict';
const {isEmpty} = require('lodash');
-const parseConfig = require('lib/config');
-const {config: configDefaults} = require('lib/constants/defaults');
+const {parseConfig} = require('lib/config');
+const {configDefaults} = require('lib/constants/defaults');
const {ViewMode} = require('lib/constants/view-modes');
const {DiffModes} = require('lib/constants/diff-modes');
-const saveFormats = require('lib/constants/save-formats');
+const {SaveFormat} = require('lib/constants/save-formats');
const SUPPORTED_CONTROL_TYPES = Object.values(require('lib/gui/constants/custom-gui-control-types'));
const {logger} = require('lib/common-utils');
@@ -82,8 +82,8 @@ describe('config', () => {
});
describe('"saveFormat" option', () => {
- it(`should be ${saveFormats.SQLITE} by default`, () => {
- assert.equal(parseConfig({}).saveFormat, saveFormats.SQLITE);
+ it(`should be ${SaveFormat.SQLITE} by default`, () => {
+ assert.equal(parseConfig({}).saveFormat, SaveFormat.SQLITE);
});
});
diff --git a/test/unit/lib/local-images-saver.js b/test/unit/lib/local-images-saver.js
index 9988a949c..6d25e6dd6 100644
--- a/test/unit/lib/local-images-saver.js
+++ b/test/unit/lib/local-images-saver.js
@@ -14,7 +14,7 @@ describe('local-images-saver', () => {
imagesSaver = proxyquire('lib/local-images-saver', {
'./server-utils': utils
- });
+ }).LocalImagesSaver;
});
afterEach(() => sandbox.restore());
diff --git a/test/unit/lib/plugin-adapter.js b/test/unit/lib/plugin-adapter.js
index e09205752..a2dd57201 100644
--- a/test/unit/lib/plugin-adapter.js
+++ b/test/unit/lib/plugin-adapter.js
@@ -4,10 +4,10 @@ const {EventEmitter} = require('events');
const _ = require('lodash');
const proxyquire = require('proxyquire');
const {logger} = require('lib/common-utils');
-const StaticReportBuilder = require('lib/report-builder/static');
-const PluginApi = require('lib/plugin-api');
+const {StaticReportBuilder} = require('lib/report-builder/static');
+const {HtmlReporter} = require('lib/plugin-api');
const {stubTool, stubConfig} = require('../utils');
-const {GUI, MERGE_REPORTS, REMOVE_UNUSED_SCREENS} = require('lib/cli-commands');
+const {GUI, MERGE_REPORTS, REMOVE_UNUSED_SCREENS} = require('lib/cli-commands').cliCommands;
describe('lib/plugin-adapter', () => {
const sandbox = sinon.createSandbox();
@@ -76,11 +76,11 @@ describe('lib/plugin-adapter', () => {
cliCommands[REMOVE_UNUSED_SCREENS] = sandbox.stub();
toolReporter = proxyquire('lib/plugin-adapter', {
- './config': parseConfig,
+ './config': {parseConfig},
[`./cli-commands/${GUI}`]: cliCommands[GUI],
[`./cli-commands/${MERGE_REPORTS}`]: cliCommands[MERGE_REPORTS],
[`./cli-commands/${REMOVE_UNUSED_SCREENS}`]: cliCommands[REMOVE_UNUSED_SCREENS]
- });
+ }).PluginAdapter;
});
afterEach(() => sandbox.restore());
@@ -133,7 +133,7 @@ describe('lib/plugin-adapter', () => {
const plugin = toolReporter.create(tool, opts);
assert.deepEqual(plugin.addApi(), plugin);
- assert.instanceOf(tool.htmlReporter, PluginApi);
+ assert.instanceOf(tool.htmlReporter, HtmlReporter);
});
it(`should not register command if hermione called via API`, () => {
diff --git a/test/unit/lib/plugin-api.js b/test/unit/lib/plugin-api.js
index 114cca00f..6d303aa81 100644
--- a/test/unit/lib/plugin-api.js
+++ b/test/unit/lib/plugin-api.js
@@ -1,11 +1,11 @@
'use strict';
-const PluginApi = require('lib/plugin-api');
-const PluginEvents = require('lib/constants/plugin-events');
+const {HtmlReporter} = require('lib/plugin-api');
+const {PluginEvents} = require('lib/constants/plugin-events');
describe('plugin api', () => {
it('should store extra items', () => {
- const pluginApi = PluginApi.create();
+ const pluginApi = HtmlReporter.create();
pluginApi.addExtraItem('some', 'item');
@@ -13,7 +13,7 @@ describe('plugin api', () => {
});
it('should store meta info extenders', () => {
- const pluginApi = PluginApi.create();
+ const pluginApi = HtmlReporter.create();
pluginApi.addMetaInfoExtender('name', 'value');
@@ -21,7 +21,7 @@ describe('plugin api', () => {
});
it('should return all stored values', () => {
- const pluginApi = PluginApi.create();
+ const pluginApi = HtmlReporter.create();
pluginApi.addExtraItem('key1', 'value1');
pluginApi.addMetaInfoExtender('key2', 'value2');
@@ -38,14 +38,14 @@ describe('plugin api', () => {
describe('should provide access to', () => {
it('plugin events', () => {
- const pluginApi = PluginApi.create();
+ const pluginApi = HtmlReporter.create();
assert.deepEqual(pluginApi.events, PluginEvents);
});
it('plugin config', () => {
const pluginConfig = {path: 'some-path'};
- const pluginApi = PluginApi.create(pluginConfig);
+ const pluginApi = HtmlReporter.create(pluginConfig);
assert.deepEqual(pluginApi.config, pluginConfig);
});
diff --git a/test/unit/lib/report-builder/gui.js b/test/unit/lib/report-builder/gui.js
index bda41d216..58d6a4ad5 100644
--- a/test/unit/lib/report-builder/gui.js
+++ b/test/unit/lib/report-builder/gui.js
@@ -7,7 +7,7 @@ const serverUtils = require('lib/server-utils');
const {TestAdapter} = require('lib/test-adapter');
const {SqliteAdapter} = require('lib/sqlite-adapter');
const GuiTestsTreeBuilder = require('lib/tests-tree-builder/gui');
-const PluginApi = require('lib/plugin-api');
+const {HtmlReporter} = require('lib/plugin-api');
const {SUCCESS, FAIL, ERROR, SKIPPED, IDLE, RUNNING, UPDATED} = require('lib/constants/test-statuses');
const {LOCAL_DATABASE_NAME} = require('lib/constants/database');
const {mkFormattedTest} = require('../../utils');
@@ -26,7 +26,7 @@ describe('GuiReportBuilder', () => {
const browserConfigStub = {getAbsoluteUrl: toolConfig.getAbsoluteUrl};
const hermione = {
forBrowser: sandbox.stub().returns(browserConfigStub),
- htmlReporter: PluginApi.create()
+ htmlReporter: HtmlReporter.create()
};
TestAdapter.create = (obj) => obj;
diff --git a/test/unit/lib/report-builder/static.js b/test/unit/lib/report-builder/static.js
index 37856f936..04e98f13d 100644
--- a/test/unit/lib/report-builder/static.js
+++ b/test/unit/lib/report-builder/static.js
@@ -1,10 +1,10 @@
'use strict';
-const fs = require('fs-extra');
+const fsOriginal = require('fs-extra');
const _ = require('lodash');
const Database = require('better-sqlite3');
const proxyquire = require('proxyquire');
-const PluginApi = require('lib/plugin-api');
+const {HtmlReporter} = require('lib/plugin-api');
const {SUCCESS, FAIL, ERROR, SKIPPED} = require('lib/constants/test-statuses');
const {LOCAL_DATABASE_NAME} = require('lib/constants/database');
const {mkFormattedTest} = require('../../utils');
@@ -14,7 +14,14 @@ const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`;
describe('StaticReportBuilder', () => {
const sandbox = sinon.sandbox.create();
- let hasImage, StaticReportBuilder, hermione;
+ let StaticReportBuilder, hermione;
+
+ const fs = _.clone(fsOriginal);
+
+ const originalUtils = proxyquire('lib/server-utils', {
+ 'fs-extra': fs
+ });
+ const utils = _.clone(originalUtils);
const mkStaticReportBuilder_ = async ({toolConfig = {}, pluginConfig} = {}) => {
toolConfig = _.defaults(toolConfig, {getAbsoluteUrl: _.noop});
@@ -24,7 +31,7 @@ describe('StaticReportBuilder', () => {
hermione = {
forBrowser: sandbox.stub().returns(browserConfigStub),
on: sandbox.spy(),
- htmlReporter: _.extend(PluginApi.create(), {
+ htmlReporter: _.extend(HtmlReporter.create(), {
reportsSaver: {
saveReportData: sandbox.stub()
}
@@ -59,11 +66,12 @@ describe('StaticReportBuilder', () => {
};
beforeEach(() => {
- hasImage = sandbox.stub().returns(true);
+ sandbox.stub(utils, 'hasImage').returns(true);
StaticReportBuilder = proxyquire('lib/report-builder/static', {
- '../server-utils': {hasImage}
- });
+ 'fs-extra': fs,
+ '../server-utils': utils
+ }).StaticReportBuilder;
});
afterEach(() => {
diff --git a/test/unit/lib/sqlite-adapter.js b/test/unit/lib/sqlite-adapter.js
index 2df7e1c82..3bf15e344 100644
--- a/test/unit/lib/sqlite-adapter.js
+++ b/test/unit/lib/sqlite-adapter.js
@@ -5,7 +5,7 @@ const proxyquire = require('proxyquire');
const Database = require('better-sqlite3');
const {SqliteAdapter} = require('lib/sqlite-adapter');
-const PluginApi = require('lib/plugin-api');
+const {HtmlReporter} = require('lib/plugin-api');
describe('lib/sqlite-adapter', () => {
const sandbox = sinon.createSandbox();
@@ -18,7 +18,7 @@ describe('lib/sqlite-adapter', () => {
};
beforeEach(() => {
- hermione = {htmlReporter: PluginApi.create()};
+ hermione = {htmlReporter: HtmlReporter.create()};
});
afterEach(() => {
@@ -78,7 +78,7 @@ describe('lib/sqlite-adapter', () => {
getStub = sandbox.stub();
prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({get: getStub});
sqliteAdapter = proxyquire('lib/sqlite-adapter', {
- './db-utils/server': {createTablesQuery: () => []}
+ './db-utils/common': {createTablesQuery: () => []}
}).SqliteAdapter.create({hermione, reportPath: 'test'});
await sqliteAdapter.init();
@@ -146,7 +146,7 @@ describe('lib/sqlite-adapter', () => {
runStub = sandbox.stub();
prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({run: runStub});
sqliteAdapter = proxyquire('lib/sqlite-adapter', {
- './db-utils/server': {createTablesQuery: () => []}
+ './db-utils/common': {createTablesQuery: () => []}
}).SqliteAdapter.create({hermione, reportPath: 'test'});
await sqliteAdapter.init();
diff --git a/test/unit/lib/static/modules/actions.js b/test/unit/lib/static/modules/actions.js
index 3e7f76281..b9a6526d0 100644
--- a/test/unit/lib/static/modules/actions.js
+++ b/test/unit/lib/static/modules/actions.js
@@ -3,7 +3,7 @@ import proxyquire from 'proxyquire';
import {POSITIONS} from 'reapop';
import {acceptOpened, undoAcceptImages, retryTest, runFailedTests} from 'lib/static/modules/actions';
import actionNames from 'lib/static/modules/action-names';
-import StaticTestsTreeBuilder from 'lib/tests-tree-builder/static';
+import {StaticTestsTreeBuilder} from 'lib/tests-tree-builder/static';
import {LOCAL_DATABASE_NAME} from 'lib/constants/database';
import {DiffModes} from 'lib/constants/diff-modes';
diff --git a/test/unit/lib/suite-adapter.js b/test/unit/lib/suite-adapter.js
index a5843b6b8..0b1138dab 100644
--- a/test/unit/lib/suite-adapter.js
+++ b/test/unit/lib/suite-adapter.js
@@ -1,6 +1,6 @@
'use strict';
-const SuiteAdapter = require('lib/suite-adapter');
+const {SuiteAdapter} = require('lib/suite-adapter');
describe('suite adapter', () => {
it('should return suite skip reason', () => {
diff --git a/test/unit/lib/tests-tree-builder/base.js b/test/unit/lib/tests-tree-builder/base.js
index 5dd11291b..ac6fa30d3 100644
--- a/test/unit/lib/tests-tree-builder/base.js
+++ b/test/unit/lib/tests-tree-builder/base.js
@@ -3,7 +3,7 @@
const _ = require('lodash');
const proxyquire = require('proxyquire');
const {FAIL, ERROR, SUCCESS} = require('lib/constants/test-statuses');
-const {versions: browserVersions} = require('lib/constants/browser');
+const {BrowserVersions} = require('lib/constants/browser');
describe('ResultsTreeBuilder', () => {
const sandbox = sinon.sandbox.create();
@@ -25,7 +25,7 @@ describe('ResultsTreeBuilder', () => {
determineStatus = sandbox.stub().returns(SUCCESS);
ResultsTreeBuilder = proxyquire('lib/tests-tree-builder/base', {
'../common-utils': {determineStatus}
- });
+ }).BaseTestsTreeBuilder;
builder = ResultsTreeBuilder.create();
});
@@ -132,7 +132,7 @@ describe('ResultsTreeBuilder', () => {
name: 'b1',
parentId: 's1',
resultIds: ['s1 b1 0'],
- version: browserVersions.UNKNOWN
+ version: BrowserVersions.UNKNOWN
}
);
});
diff --git a/test/unit/lib/tests-tree-builder/static.js b/test/unit/lib/tests-tree-builder/static.js
index ccc6b84b7..d80978ad3 100644
--- a/test/unit/lib/tests-tree-builder/static.js
+++ b/test/unit/lib/tests-tree-builder/static.js
@@ -1,9 +1,9 @@
'use strict';
const _ = require('lodash');
-const StaticResultsTreeBuilder = require('lib/tests-tree-builder/static');
+const {StaticTestsTreeBuilder} = require('lib/tests-tree-builder/static');
const {SUCCESS} = require('lib/constants/test-statuses');
-const {versions: browserVersions} = require('lib/constants/browser');
+const {BrowserVersions} = require('lib/constants/browser');
describe('StaticResultsTreeBuilder', () => {
const sandbox = sinon.sandbox.create();
@@ -65,10 +65,10 @@ describe('StaticResultsTreeBuilder', () => {
};
beforeEach(() => {
- sandbox.stub(StaticResultsTreeBuilder.prototype, 'addTestResult');
- sandbox.stub(StaticResultsTreeBuilder.prototype, 'sortTree');
+ sandbox.stub(StaticTestsTreeBuilder.prototype, 'addTestResult');
+ sandbox.stub(StaticTestsTreeBuilder.prototype, 'sortTree');
- builder = StaticResultsTreeBuilder.create();
+ builder = StaticTestsTreeBuilder.create();
});
afterEach(() => sandbox.restore());
@@ -83,12 +83,12 @@ describe('StaticResultsTreeBuilder', () => {
builder.build(rows);
assert.calledWith(
- StaticResultsTreeBuilder.prototype.addTestResult.firstCall,
+ StaticTestsTreeBuilder.prototype.addTestResult.firstCall,
formatToTestResult(dataFromDb1, {attempt: 0}),
{browserId: 'yabro', testPath: ['s1'], attempt: 0}
);
assert.calledWith(
- StaticResultsTreeBuilder.prototype.addTestResult.secondCall,
+ StaticTestsTreeBuilder.prototype.addTestResult.secondCall,
formatToTestResult(dataFromDb2, {attempt: 0}),
{browserId: 'yabro', testPath: ['s2'], attempt: 0}
);
@@ -102,12 +102,12 @@ describe('StaticResultsTreeBuilder', () => {
builder.build(rows);
assert.calledWith(
- StaticResultsTreeBuilder.prototype.addTestResult.firstCall,
+ StaticTestsTreeBuilder.prototype.addTestResult.firstCall,
formatToTestResult(dataFromDb1, {attempt: 0}),
{browserId: 'yabro', testPath: ['s1'], attempt: 0}
);
assert.calledWith(
- StaticResultsTreeBuilder.prototype.addTestResult.secondCall,
+ StaticTestsTreeBuilder.prototype.addTestResult.secondCall,
formatToTestResult(dataFromDb1, {attempt: 1}),
{browserId: 'yabro', testPath: ['s1'], attempt: 1}
);
@@ -121,8 +121,8 @@ describe('StaticResultsTreeBuilder', () => {
builder.build(rows);
assert.callOrder(
- StaticResultsTreeBuilder.prototype.addTestResult,
- StaticResultsTreeBuilder.prototype.sortTree
+ StaticTestsTreeBuilder.prototype.addTestResult,
+ StaticTestsTreeBuilder.prototype.sortTree
);
});
@@ -131,12 +131,12 @@ describe('StaticResultsTreeBuilder', () => {
builder.build(rows);
- assert.calledOnce(StaticResultsTreeBuilder.prototype.sortTree);
+ assert.calledOnce(StaticTestsTreeBuilder.prototype.sortTree);
});
});
it('should return tests tree', () => {
- sandbox.stub(StaticResultsTreeBuilder.prototype, 'tree').get(() => 'tree');
+ sandbox.stub(StaticTestsTreeBuilder.prototype, 'tree').get(() => 'tree');
const {tree} = builder.build([]);
@@ -150,7 +150,7 @@ describe('StaticResultsTreeBuilder', () => {
const {browsers} = builder.build(rows);
- assert.deepEqual(browsers, [{id: 'yabro', versions: [browserVersions.UNKNOWN]}]);
+ assert.deepEqual(browsers, [{id: 'yabro', versions: [BrowserVersions.UNKNOWN]}]);
});
it('with a few versions for the same browser', () => {