diff --git a/.babelignore b/.babelignore deleted file mode 100644 index dc6e9aad3..000000000 --- a/.babelignore +++ /dev/null @@ -1 +0,0 @@ -./lib/gui diff --git a/.babelrc b/babel.config.json similarity index 100% rename from .babelrc rename to babel.config.json diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 9e8d7311a..adc2f6251 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -5,7 +5,7 @@ import axios, {AxiosRequestConfig} from 'axios'; import {SUCCESS, FAIL, ERROR, SKIPPED, UPDATED, IDLE, RUNNING, QUEUED, TestStatus} from './constants'; import {UNCHECKED, INDETERMINATE, CHECKED} from './constants/checked-statuses'; -import {AssertViewResult, ImageData, ImageBase64, ImageInfoFull, TestError, ImageInfoError} from './types'; +import {ImageData, ImageBase64, ImageInfoFull, TestError, ImageInfoError} from './types'; import {ErrorName, ImageDiffError, NoRefImageError} from './errors'; export const getShortMD5 = (str: string): string => { return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7); @@ -99,8 +99,8 @@ export const isNoRefImageError = (error?: unknown): error is NoRefImageError => return (error as {name?: string})?.name === ErrorName.NO_REF_IMAGE; }; -export const hasNoRefImageErrors = ({assertViewResults = []}: {assertViewResults?: AssertViewResult[]}): boolean => { - return assertViewResults.some((assertViewResult: AssertViewResult) => isNoRefImageError(assertViewResult)); +export const hasNoRefImageErrors = ({assertViewResults = []}: {assertViewResults?: {name?: string}[]}): boolean => { + return assertViewResults.some((assertViewResult) => isNoRefImageError(assertViewResult)); }; const hasFailedImages = (result: {imagesInfo?: ImageInfoFull[]}): boolean => { @@ -124,7 +124,7 @@ export const getError = (error?: TestError): undefined | Pick { +export const hasDiff = (assertViewResults: {name?: string}[]): boolean => { return assertViewResults.some((result) => isImageDiffError(result as {name?: string})); }; diff --git a/lib/config/custom-gui-asserts.ts b/lib/config/custom-gui-asserts.ts index 884ed9346..f049c360d 100644 --- a/lib/config/custom-gui-asserts.ts +++ b/lib/config/custom-gui-asserts.ts @@ -1,5 +1,5 @@ import {isUndefined, isArray, isEmpty, isFunction, isPlainObject, isString} from 'lodash'; -import CustomGuiControlTypes from '../gui/constants/custom-gui-control-types'; +import * as CustomGuiControlTypes from '../gui/constants/custom-gui-control-types'; const SUPPORTED_CONTROL_TYPES: string[] = Object.values(CustomGuiControlTypes); diff --git a/lib/constants/database.ts b/lib/constants/database.ts index 0a5b1a559..cc9cd381b 100644 --- a/lib/constants/database.ts +++ b/lib/constants/database.ts @@ -1,4 +1,4 @@ -import type {ValueOf} from 'type-fest'; +import {ValueOf} from 'type-fest'; // TODO: change to enums export const DB_TYPES = {int: 'INT', text: 'TEXT'} as const; @@ -19,12 +19,7 @@ export const DB_COLUMNS = { TIMESTAMP: 'timestamp' } as const; -type DbColumn = { - name: ValueOf; - type: ValueOf; -} - -export const SUITES_TABLE_COLUMNS: DbColumn[] = [ +export const SUITES_TABLE_COLUMNS = [ {name: DB_COLUMNS.SUITE_PATH, type: DB_TYPES.text}, {name: DB_COLUMNS.SUITE_NAME, type: DB_TYPES.text}, {name: DB_COLUMNS.NAME, type: DB_TYPES.text}, @@ -39,7 +34,7 @@ export const SUITES_TABLE_COLUMNS: DbColumn[] = [ {name: DB_COLUMNS.MULTIPLE_TABS, type: DB_TYPES.int}, //boolean - 0 or 1 {name: DB_COLUMNS.STATUS, type: DB_TYPES.text}, {name: DB_COLUMNS.TIMESTAMP, type: DB_TYPES.int} -]; +] as const; export const DB_MAX_AVAILABLE_PAGE_SIZE = 65536; // helps to speed up queries export const DB_SUITES_TABLE_NAME = 'suites'; @@ -48,4 +43,4 @@ export const DATABASE_URLS_JSON_NAME = 'databaseUrls.json'; export const DB_COLUMN_INDEXES = SUITES_TABLE_COLUMNS.reduce((acc: Record, {name}, index) => { acc[name] = index; return acc; -}, {}); +}, {}) as { [K in ValueOf]: number }; diff --git a/lib/db-utils/common.ts b/lib/db-utils/common.ts index 6183cc358..599d4a0e9 100644 --- a/lib/db-utils/common.ts +++ b/lib/db-utils/common.ts @@ -3,6 +3,7 @@ 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'; +import {ReadonlyDeep} from 'type-fest'; export const selectAllQuery = (tableName: string): string => `SELECT * FROM ${tableName}`; export const selectAllSuitesQuery = (): string => selectAllQuery(DB_SUITES_TABLE_NAME); @@ -76,7 +77,7 @@ export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => [] } }; -function createTableQuery(tableName: string, columns: { name: string, type: string }[]): string { +function createTableQuery(tableName: string, columns: ReadonlyDeep<{name: string, type: string }[]>): string { const formattedColumns = columns .map(({name, type}) => `${name} ${type}`) .join(', '); diff --git a/lib/gui/api/facade.js b/lib/gui/api/facade.js index fd3b0cc63..2856e22f3 100644 --- a/lib/gui/api/facade.js +++ b/lib/gui/api/facade.js @@ -1,7 +1,7 @@ 'use strict'; const EventEmitter2 = require('eventemitter2'); -const guiEvents = require('../constants/gui-events'); +const {GuiEvents} = require('../constants/gui-events'); module.exports = class ApiFacade extends EventEmitter2 { static create() { @@ -11,6 +11,6 @@ module.exports = class ApiFacade extends EventEmitter2 { constructor() { super(); - this.events = guiEvents; + this.events = GuiEvents; } }; diff --git a/lib/gui/constants/client-events.js b/lib/gui/constants/client-events.js deleted file mode 100644 index a502317ca..000000000 --- a/lib/gui/constants/client-events.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports = { - BEGIN_SUITE: 'beginSuite', - BEGIN_STATE: 'beginState', - - TEST_RESULT: 'testResult', - - RETRY: 'retry', - ERROR: 'err', - - END: 'end' -}; diff --git a/lib/gui/constants/client-events.ts b/lib/gui/constants/client-events.ts new file mode 100644 index 000000000..e1216cda1 --- /dev/null +++ b/lib/gui/constants/client-events.ts @@ -0,0 +1,17 @@ +import {ValueOf} from 'type-fest'; + +export const ClientEvents = { + BEGIN_SUITE: 'beginSuite', + BEGIN_STATE: 'beginState', + + TEST_RESULT: 'testResult', + + RETRY: 'retry', + ERROR: 'err', + + END: 'end' +} as const; + +export type ClientEvents = typeof ClientEvents; + +export type ClientEvent = ValueOf; diff --git a/lib/gui/constants/custom-gui-control-types.js b/lib/gui/constants/custom-gui-control-types.js deleted file mode 100644 index 70d2f8e87..000000000 --- a/lib/gui/constants/custom-gui-control-types.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = { - CONTROL_TYPE_BUTTON: 'button', - CONTROL_TYPE_RADIOBUTTON: 'radiobutton' -}; diff --git a/lib/gui/constants/custom-gui-control-types.ts b/lib/gui/constants/custom-gui-control-types.ts new file mode 100644 index 000000000..59634f992 --- /dev/null +++ b/lib/gui/constants/custom-gui-control-types.ts @@ -0,0 +1,3 @@ +export const CONTROL_TYPE_BUTTON = 'button'; + +export const CONTROL_TYPE_RADIOBUTTON = 'radiobutton'; diff --git a/lib/gui/constants/gui-events.js b/lib/gui/constants/gui-events.js deleted file mode 100644 index e88133511..000000000 --- a/lib/gui/constants/gui-events.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = { - SERVER_INIT: 'serverInit', - SERVER_READY: 'serverReady' -}; diff --git a/lib/gui/constants/gui-events.ts b/lib/gui/constants/gui-events.ts new file mode 100644 index 000000000..b31455b85 --- /dev/null +++ b/lib/gui/constants/gui-events.ts @@ -0,0 +1,10 @@ +import {ValueOf} from 'type-fest'; + +export const GuiEvents = { + SERVER_INIT: 'serverInit', + SERVER_READY: 'serverReady' +} as const; + +export type GuiEvents = typeof GuiEvents; + +export type GuiEvent = ValueOf; diff --git a/lib/gui/constants/index.ts b/lib/gui/constants/index.ts new file mode 100644 index 000000000..a6a5b519a --- /dev/null +++ b/lib/gui/constants/index.ts @@ -0,0 +1,4 @@ +export * from './client-events'; +export * from './custom-gui-control-types'; +export * from './gui-events'; +export * from './server'; diff --git a/lib/gui/constants/server.js b/lib/gui/constants/server.js deleted file mode 100644 index 54d45e942..000000000 --- a/lib/gui/constants/server.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = { - MAX_REQUEST_SIZE: '100mb', - KEEP_ALIVE_TIMEOUT: 120 * 1000, - HEADERS_TIMEOUT: 125 * 1000 -}; diff --git a/lib/gui/constants/server.ts b/lib/gui/constants/server.ts new file mode 100644 index 000000000..e0d7094f9 --- /dev/null +++ b/lib/gui/constants/server.ts @@ -0,0 +1,5 @@ +export const MAX_REQUEST_SIZE = '100mb'; + +export const KEEP_ALIVE_TIMEOUT = 120 * 1000; + +export const HEADERS_TIMEOUT = 125 * 1000; diff --git a/lib/gui/event-source.js b/lib/gui/event-source.ts similarity index 58% rename from lib/gui/event-source.js rename to lib/gui/event-source.ts index 6a1066e91..7d033fb4a 100644 --- a/lib/gui/event-source.js +++ b/lib/gui/event-source.ts @@ -1,21 +1,21 @@ -'use strict'; +import {Response} from 'express'; +import stringify from 'json-stringify-safe'; -const stringify = require('json-stringify-safe'); - -module.exports = class EventSource { +export class EventSource { + private _connections: Response[]; constructor() { this._connections = []; } - addConnection(connection) { + addConnection(connection: Response): void { this._connections.push(connection); } - emit(event, data) { + emit(event: string, data?: unknown): void { this._connections.forEach(function(connection) { connection.write('event: ' + event + '\n'); connection.write('data: ' + stringify(data) + '\n'); connection.write('\n\n'); }); } -}; +} diff --git a/lib/gui/server.js b/lib/gui/server.js index f3bee2c71..84e795216 100644 --- a/lib/gui/server.js +++ b/lib/gui/server.js @@ -8,7 +8,7 @@ const bodyParser = require('body-parser'); const {INTERNAL_SERVER_ERROR, OK} = require('http-codes'); const App = require('./app'); -const {MAX_REQUEST_SIZE, KEEP_ALIVE_TIMEOUT, HEADERS_TIMEOUT} = require('./constants/server'); +const {MAX_REQUEST_SIZE, KEEP_ALIVE_TIMEOUT, HEADERS_TIMEOUT} = require('./constants'); const {initializeCustomGui, runCustomGuiAction} = require('../server-utils'); const {logger} = require('../common-utils'); const initPluginsRoutes = require('./routes/plugins'); diff --git a/lib/gui/tool-runner/index.js b/lib/gui/tool-runner/index.js index 60865352a..9b7a6b98f 100644 --- a/lib/gui/tool-runner/index.js +++ b/lib/gui/tool-runner/index.js @@ -7,10 +7,10 @@ const chalk = require('chalk'); const Promise = require('bluebird'); const looksSame = require('looks-same'); -const Runner = require('./runner'); -const subscribeOnToolEvents = require('./report-subscriber'); -const GuiReportBuilder = require('../../report-builder/gui'); -const EventSource = require('../event-source'); +const {createTestRunner} = require('./runner'); +const {subscribeOnToolEvents} = require('./report-subscriber'); +const {GuiReportBuilder} = require('../../report-builder/gui'); +const {EventSource} = require('../event-source'); const {logger} = require('../../common-utils'); const reporterHelper = require('../../reporter-helpers'); const {UPDATED, SKIPPED, IDLE} = require('../../constants/test-statuses'); @@ -150,7 +150,7 @@ module.exports = class ToolRunner { } if (shouldRevertReference) { - await reporterHelper.revertReferenceImage(formattedResult, stateName); + await reporterHelper.revertReferenceImage(removedResult, formattedResult, stateName); } if (previousExpectedPath) { @@ -202,7 +202,7 @@ module.exports = class ToolRunner { run(tests = []) { const {grep, set: sets, browser: browsers} = this._globalOpts; - return Runner.create(this._collection, tests) + return createTestRunner(this._collection, tests) .run((collection) => this._hermione.run(collection, {grep, sets, browsers})); } @@ -250,7 +250,7 @@ module.exports = class ToolRunner { return _.extend(imageInfo, {expectedImg: refImg}); }); - const res = _.merge({}, rawTest, {assertViewResults, imagesInfo, sessionId, attempt, origAttempt: attempt, meta: {url}, updated: true}); + const res = _.merge({}, rawTest, {assertViewResults, imagesInfo, sessionId, attempt, meta: {url}, updated: true}); // _.merge can't fully clone test object since hermione@7+ // TODO: use separate object to represent test results. Do not extend test object with test results diff --git a/lib/gui/tool-runner/report-subscriber.js b/lib/gui/tool-runner/report-subscriber.ts similarity index 53% rename from lib/gui/tool-runner/report-subscriber.js rename to lib/gui/tool-runner/report-subscriber.ts index 03ad83148..504659967 100644 --- a/lib/gui/tool-runner/report-subscriber.js +++ b/lib/gui/tool-runner/report-subscriber.ts @@ -1,32 +1,38 @@ -'use strict'; - -const os = require('os'); -const PQueue = require('p-queue'); -const clientEvents = require('../constants/client-events'); -const {getSuitePath} = require('../../plugin-utils'); -const {createWorkers} = require('../../workers/create-workers'); -const {logError, formatTestResult} = require('../../server-utils'); -const {hasDiff} = require('../../common-utils'); -const {TestStatus, RUNNING, SUCCESS, SKIPPED} = require('../../constants'); - -let workers; - -module.exports = (hermione, reportBuilder, client, reportPath) => { +import os from 'os'; +import PQueue from 'p-queue'; +import Hermione from 'hermione'; +import {ClientEvents} from '../constants'; +import {getSuitePath} from '../../plugin-utils'; +import {createWorkers} from '../../workers/create-workers'; +import {logError, formatTestResult} from '../../server-utils'; +import {hasDiff} from '../../common-utils'; +import {TestStatus, RUNNING, SUCCESS, SKIPPED} from '../../constants'; +import {GuiReportBuilder} from '../../report-builder/gui'; +import {EventSource} from '../event-source'; +import {HermioneTestResult} from '../../types'; +import {HermioneTestAdapter, ReporterTestResult} from '../../test-adapter'; +import {ImageDiffError} from '../../errors'; + +let workers: ReturnType; + +type CreateWorkersRunner = Parameters[0]; + +export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiReportBuilder, client: EventSource, reportPath: string): void => { const queue = new PQueue({concurrency: os.cpus().length}); const {imageHandler} = reportBuilder; - function failHandler(testResult, formattedResult) { - const actions = [imageHandler.saveTestImages(formattedResult, workers)]; + async function failHandler(formattedResult: ReporterTestResult): Promise { + const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; if (formattedResult.errorDetails) { - actions.push(formattedResult.saveErrorDetails(reportPath)); + actions.push((formattedResult as HermioneTestAdapter).saveErrorDetails(reportPath)); } - return Promise.all(actions); + await Promise.all(actions); } hermione.on(hermione.events.RUNNER_START, (runner) => { - workers = createWorkers(runner); + workers = createWorkers(runner as unknown as CreateWorkersRunner); }); hermione.on(hermione.events.SUITE_BEGIN, (suite) => { @@ -34,20 +40,20 @@ module.exports = (hermione, reportBuilder, client, reportPath) => { return; } - client.emit(clientEvents.BEGIN_SUITE, { + client.emit(ClientEvents.BEGIN_SUITE, { suiteId: getSuitePath(suite).join(' '), status: TestStatus.RUNNING }); }); hermione.on(hermione.events.TEST_BEGIN, (data) => { - const formattedResult = formatTestResult(data, RUNNING, reportBuilder); + const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, reportBuilder); formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); reportBuilder.addRunning(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); - return client.emit(clientEvents.BEGIN_STATE, testBranch); + return client.emit(ClientEvents.BEGIN_STATE, testBranch); }); hermione.on(hermione.events.TEST_PASS, (testResult) => { @@ -59,59 +65,59 @@ module.exports = (hermione, reportBuilder, client, reportPath) => { reportBuilder.addSuccess(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); - client.emit(clientEvents.TEST_RESULT, testBranch); + client.emit(ClientEvents.TEST_RESULT, testBranch); }).catch(logError); }); hermione.on(hermione.events.RETRY, (testResult) => { queue.add(async () => { - const status = hasDiff(testResult.assertViewResults) ? TestStatus.FAIL : TestStatus.ERROR; + const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; const formattedResult = formatTestResult(testResult, status, reportBuilder); formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); - await failHandler(testResult, formattedResult); + await failHandler(formattedResult); reportBuilder.addRetry(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); - client.emit(clientEvents.TEST_RESULT, testBranch); + client.emit(ClientEvents.TEST_RESULT, testBranch); }).catch(logError); }); hermione.on(hermione.events.TEST_FAIL, (testResult) => { queue.add(async () => { - const status = hasDiff(testResult.assertViewResults) ? TestStatus.FAIL : TestStatus.ERROR; + const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; const formattedResult = formatTestResult(testResult, status, reportBuilder); formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); - await failHandler(testResult, formattedResult); + await failHandler(formattedResult); status === TestStatus.FAIL ? reportBuilder.addFail(formattedResult) : reportBuilder.addError(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); - client.emit(clientEvents.TEST_RESULT, testBranch); + client.emit(ClientEvents.TEST_RESULT, testBranch); }).catch(logError); }); hermione.on(hermione.events.TEST_PENDING, async (testResult) => { queue.add(async () => { - const formattedResult = formatTestResult(testResult, SKIPPED, reportBuilder); + const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, reportBuilder); formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); - await failHandler(testResult, formattedResult); + await failHandler(formattedResult); reportBuilder.addSkipped(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); - client.emit(clientEvents.TEST_RESULT, testBranch); + client.emit(ClientEvents.TEST_RESULT, testBranch); }).catch(logError); }); hermione.on(hermione.events.RUNNER_END, async () => { try { await queue.onIdle(); - client.emit(clientEvents.END); - } catch (err) { - logError(err); + client.emit(ClientEvents.END); + } catch (err: unknown) { + logError(err as Error); } }); }; diff --git a/lib/gui/tool-runner/runner/all-test-runner.js b/lib/gui/tool-runner/runner/all-test-runner.js deleted file mode 100644 index 08bb845f2..000000000 --- a/lib/gui/tool-runner/runner/all-test-runner.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const Runner = require('./runner'); - -module.exports = class AllRunner extends Runner { - run(runHandler) { - this._collection.enableAll(); - - return super.run(runHandler); - } -}; diff --git a/lib/gui/tool-runner/runner/all-test-runner.ts b/lib/gui/tool-runner/runner/all-test-runner.ts new file mode 100644 index 000000000..f181eb145 --- /dev/null +++ b/lib/gui/tool-runner/runner/all-test-runner.ts @@ -0,0 +1,9 @@ +import {BaseRunner, TestCollection} from './runner'; + +export class AllTestRunner extends BaseRunner { + override run(runHandler: (testCollection: TestCollection) => U): U { + this._collection.enableAll(); + + return super.run(runHandler); + } +} diff --git a/lib/gui/tool-runner/runner/index.js b/lib/gui/tool-runner/runner/index.js deleted file mode 100644 index 79e1ef857..000000000 --- a/lib/gui/tool-runner/runner/index.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AllTestRunner = require('./all-test-runner'); -const SpecificTestRunner = require('./specific-test-runner'); - -exports.create = (collection, tests) => { - return _.isEmpty(tests) - ? new AllTestRunner(collection) - : new SpecificTestRunner(collection, tests); -}; diff --git a/lib/gui/tool-runner/runner/index.ts b/lib/gui/tool-runner/runner/index.ts new file mode 100644 index 000000000..df29746d1 --- /dev/null +++ b/lib/gui/tool-runner/runner/index.ts @@ -0,0 +1,11 @@ +import _ from 'lodash'; + +import {TestCollection, TestRunner, TestSpec} from './runner'; +import {AllTestRunner} from './all-test-runner'; +import {SpecificTestRunner} from './specific-test-runner'; + +export const createTestRunner = (collection: TestCollection, tests: TestSpec[]): TestRunner => { + return _.isEmpty(tests) + ? new AllTestRunner(collection) + : new SpecificTestRunner(collection, tests); +}; diff --git a/lib/gui/tool-runner/runner/runner.js b/lib/gui/tool-runner/runner/runner.js deleted file mode 100644 index dfe61988d..000000000 --- a/lib/gui/tool-runner/runner/runner.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = class Runner { - constructor(collection) { - this._collection = collection; - } - - run(runHandler) { - return runHandler(this._collection); - } -}; diff --git a/lib/gui/tool-runner/runner/runner.ts b/lib/gui/tool-runner/runner/runner.ts new file mode 100644 index 000000000..a197f06fd --- /dev/null +++ b/lib/gui/tool-runner/runner/runner.ts @@ -0,0 +1,25 @@ +import Hermione from 'hermione'; +import {AsyncReturnType} from 'type-fest'; + +export type TestCollection = AsyncReturnType + +export interface TestRunner { + run(handler: (testCollection: TestCollection) => U): U; +} + +export interface TestSpec { + testName: string; + browserName: string; +} + +export class BaseRunner implements TestRunner { + protected _collection: TestCollection; + + constructor(collection: TestCollection) { + this._collection = collection; + } + + run(runHandler: (testCollection: TestCollection) => U): U { + return runHandler(this._collection); + } +} diff --git a/lib/gui/tool-runner/runner/specific-test-runner.js b/lib/gui/tool-runner/runner/specific-test-runner.js deleted file mode 100644 index 0658c8336..000000000 --- a/lib/gui/tool-runner/runner/specific-test-runner.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const Runner = require('./runner'); - -module.exports = class SpecificTestRunner extends Runner { - constructor(collection, tests) { - super(collection); - - this._tests = tests; - } - - run(runHandler) { - this._filter(); - - return super.run(runHandler); - } - - _filter() { - this._collection.disableAll(); - - this._tests.forEach(({testName, browserName}) => { - this._collection.enableTest(testName, browserName); - }); - } -}; diff --git a/lib/gui/tool-runner/runner/specific-test-runner.ts b/lib/gui/tool-runner/runner/specific-test-runner.ts new file mode 100644 index 000000000..9eba480d6 --- /dev/null +++ b/lib/gui/tool-runner/runner/specific-test-runner.ts @@ -0,0 +1,25 @@ +import {BaseRunner, TestCollection, TestSpec} from './runner'; + +export class SpecificTestRunner extends BaseRunner { + private _tests: TestSpec[]; + + constructor(collection: TestCollection, tests: TestSpec[]) { + super(collection); + + this._tests = tests; + } + + override run(runHandler: (testCollection: TestCollection) => U): U { + this._filter(); + + return super.run(runHandler); + } + + private _filter(): void { + this._collection.disableAll(); + + this._tests.forEach(({testName, browserName}) => { + this._collection.enableTest(testName, browserName); + }); + } +} diff --git a/lib/plugin-api.ts b/lib/plugin-api.ts index d8db5556d..e23f56544 100644 --- a/lib/plugin-api.ts +++ b/lib/plugin-api.ts @@ -5,7 +5,7 @@ import {LocalImagesSaver} from './local-images-saver'; import {version} from '../package.json'; import {ImagesSaver, ReporterConfig, ReportsSaver} from './types'; -interface HtmlReporterValues { +export interface HtmlReporterValues { toolName: ToolName; extraItems: Record; metaInfoExtenders: Record; diff --git a/lib/report-builder/gui.js b/lib/report-builder/gui.js deleted file mode 100644 index f4fa5d4cb..000000000 --- a/lib/report-builder/gui.js +++ /dev/null @@ -1,203 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const path = require('path'); - -const {StaticReportBuilder} = require('./static'); -const GuiTestsTreeBuilder = require('../tests-tree-builder/gui'); -const {IDLE, RUNNING, SKIPPED, FAIL, SUCCESS, UPDATED} = require('../constants/test-statuses'); -const {isSkippedStatus, isUpdatedStatus, hasNoRefImageErrors, hasResultFails} = require('../common-utils'); -const {getConfigForStaticFile, deleteFile} = require('../server-utils'); -const {DB_COLUMNS} = require('../constants/database'); -const {ToolName} = require('../constants'); - -module.exports = class GuiReportBuilder extends StaticReportBuilder { - static create(...args) { - return new this(...args); - } - - constructor(...args) { - super(...args); - - this._testsTree = GuiTestsTreeBuilder.create({toolName: ToolName.Hermione}); - this._skips = []; - this._apiValues = {}; - } - - addIdle(result) { - return this._addTestResult(result, {status: IDLE}); - } - - addRunning(result) { - return this._addTestResult(result, {status: RUNNING}); - } - - addSkipped(result) { - const formattedResult = super.addSkipped(result); - const { - fullName: suite, - skipReason: comment, - browserId: browser - } = formattedResult; - - this._skips.push({suite, browser, comment}); - } - - addUpdated(result, failResultId) { - return this._addTestResult(result, {status: UPDATED}, {failResultId}); - } - - setApiValues(values) { - this._apiValues = values; - - return this; - } - - reuseTestsTree(testsTree) { - this._testsTree.reuseTestsTree(testsTree); - } - - getResult() { - const {customGui} = this._pluginConfig; - const config = {...getConfigForStaticFile(this._pluginConfig), customGui}; - - this._testsTree.sortTree(); - - return { - tree: this._testsTree.tree, - skips: this._skips, - config, - date: new Date().toString(), - apiValues: this._apiValues - }; - } - - getTestBranch(id) { - return this._testsTree.getTestBranch(id); - } - - getTestsDataToUpdateRefs(imageIds) { - return this._testsTree.getTestsDataToUpdateRefs(imageIds); - } - - getImageDataToFindEqualDiffs(imageId) { - return this._testsTree.getImageDataToFindEqualDiffs(imageId); - } - - getCurrAttempt(formattedResult) { - const {status, attempt} = this._testsTree.getLastResult(formattedResult); - - return [IDLE, RUNNING, SKIPPED].includes(status) ? attempt : attempt + 1; - } - - getUpdatedAttempt(formattedResult) { - const {attempt} = this._testsTree.getLastResult(formattedResult); - const imagesInfo = this._testsTree.getImagesInfo(formattedResult.id); - const isUpdated = imagesInfo.some((image) => image.status === UPDATED); - - return isUpdated ? attempt : attempt + 1; - } - - async undoAcceptImage(formattedResult, stateName) { - const resultId = formattedResult.id; - const suitePath = formattedResult.testPath; - const browserName = formattedResult.browserId; - const { - imageId, - status, - timestamp, - previousImage, - shouldRemoveResult - } = this._testsTree.getResultDataToUnacceptImage(resultId, stateName); - - if (!isUpdatedStatus(status)) { - return {}; - } - - const previousExpectedPath = _.get(previousImage, 'expectedImg.path', null); - const previousImageRefImgSize = _.get(previousImage, 'refImg.size', null); - const shouldRemoveReference = _.isNull(previousImageRefImgSize); - const shouldRevertReference = !shouldRemoveReference; - - let updatedImage, removedResult; - - if (shouldRemoveResult) { - this._testsTree.removeTestResult(resultId); - formattedResult.decreaseAttemptNumber(); - - removedResult = resultId; - } else { - updatedImage = this._testsTree.updateImageInfo(imageId, previousImage); - } - - this._deleteTestResultFromDb({where: [ - `${DB_COLUMNS.SUITE_PATH} = ?`, - `${DB_COLUMNS.NAME} = ?`, - `${DB_COLUMNS.STATUS} = ?`, - `${DB_COLUMNS.TIMESTAMP} = ?`, - `json_extract(${DB_COLUMNS.IMAGES_INFO}, '$[0].stateName') = ?` - ].join(' AND ')}, JSON.stringify(suitePath), browserName, status, timestamp, stateName); - - return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference}; - } - - _addTestResult(formattedResult, props, opts = {}) { - super._addTestResult(formattedResult, props); - - const testResult = this._createTestResult(formattedResult, {...props, attempt: formattedResult.attempt}); - - this._extendTestWithImagePaths(testResult, formattedResult, opts); - - if (![IDLE, RUNNING].includes(testResult.status)) { - this._updateTestResultStatus(testResult, formattedResult); - } - - this._testsTree.addTestResult(testResult, formattedResult); - - return formattedResult; - } - - _updateTestResultStatus(testResult, formattedResult) { - if (!hasResultFails(testResult) && !isSkippedStatus(testResult.status)) { - testResult.status = SUCCESS; - return; - } - - if (hasNoRefImageErrors(formattedResult)) { - testResult.status = FAIL; - return; - } - - if (testResult.status === UPDATED) { - const {status: prevStatus} = this._testsTree.getResultByOrigAttempt(formattedResult); - testResult.status = prevStatus; - } - } - - _extendTestWithImagePaths(test, formattedResult, opts = {}) { - const newImagesInfo = formattedResult.imagesInfo; - - if (test.status !== UPDATED) { - return _.set(test, 'imagesInfo', newImagesInfo); - } - - const failImagesInfo = this._testsTree.getImagesInfo(opts.failResultId); - - if (failImagesInfo.length) { - test.imagesInfo = _.clone(failImagesInfo); - - newImagesInfo.forEach((imageInfo) => { - const {stateName} = imageInfo; - let index = _.findIndex(test.imagesInfo, {stateName}); - index = index >= 0 ? index : _.findLastIndex(test.imagesInfo); - test.imagesInfo[index] = imageInfo; - }); - } - } - - async _removeImageFromReport(reportPath, imgPath) { - const imageAbsolutePath = path.resolve(process.cwd(), reportPath, imgPath); - - await deleteFile(imageAbsolutePath); - } -}; diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts new file mode 100644 index 000000000..9f5a91a68 --- /dev/null +++ b/lib/report-builder/gui.ts @@ -0,0 +1,242 @@ +import * as _ from 'lodash'; +import {StaticReportBuilder} from './static'; +import {GuiTestsTreeBuilder, TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui'; +import { + IDLE, RUNNING, SKIPPED, FAIL, SUCCESS, UPDATED, TestStatus, DB_COLUMNS, ToolName, ERROR +} from '../constants'; +import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils'; +import {ReporterTestResult} from '../test-adapter'; +import {PreparedTestResult} from '../sqlite-adapter'; +import {Tree, TreeImage, TreeResult} from '../tests-tree-builder/base'; +import {ImageInfoWithState, ReporterConfig} from '../types'; +import {hasDiff, hasNoRefImageErrors, hasResultFails, isSkippedStatus, isUpdatedStatus} from '../common-utils'; +import {HtmlReporterValues} from '../plugin-api'; +import {SkipItem} from '../tests-tree-builder/static'; + +interface UndoAcceptImageResult { + updatedImage: TreeImage | undefined; + removedResult: string | undefined; + previousExpectedPath: string | null; + shouldRemoveReference: boolean; + shouldRevertReference: boolean; +} + +interface GuiReportBuilderResult { + tree: Tree; + skips: SkipItem[]; + config: ConfigForStaticFile & {customGui: ReporterConfig['customGui']}; + date: string; + apiValues?: HtmlReporterValues; +} + +export class GuiReportBuilder extends StaticReportBuilder { + private _testsTree: GuiTestsTreeBuilder; + private _skips: SkipItem[]; + private _apiValues?: HtmlReporterValues; + + constructor(...args: ConstructorParameters) { + super(...args); + + this._testsTree = GuiTestsTreeBuilder.create({toolName: ToolName.Hermione}); + this._skips = []; + } + + addIdle(result: ReporterTestResult): ReporterTestResult { + return this._addTestResult(result, {status: IDLE}); + } + + addRunning(result: ReporterTestResult): ReporterTestResult { + return this._addTestResult(result, {status: RUNNING}); + } + + addSkipped(result: ReporterTestResult): ReporterTestResult { + const formattedResult = super.addSkipped(result); + const { + fullName: suite, + skipReason: comment, + browserId: browser + } = formattedResult; + + this._skips.push({suite, browser, comment}); + + return formattedResult; + } + + addUpdated(result: ReporterTestResult, failResultId: string): ReporterTestResult { + return this._addTestResult(result, {status: UPDATED}, {failResultId}); + } + + setApiValues(values: HtmlReporterValues): this { + this._apiValues = values; + return this; + } + + reuseTestsTree(tree: Tree): void { + this._testsTree.reuseTestsTree(tree); + } + + getResult(): GuiReportBuilderResult { + const {customGui} = this._pluginConfig; + const config = {...getConfigForStaticFile(this._pluginConfig), customGui}; + + this._testsTree.sortTree(); + + return { + tree: this._testsTree.tree, + skips: this._skips, + config, + date: new Date().toString(), + apiValues: this._apiValues + }; + } + + getTestBranch(id: string): TestBranch { + return this._testsTree.getTestBranch(id); + } + + getTestsDataToUpdateRefs(imageIds: string[]): TestRefUpdateData[] { + return this._testsTree.getTestsDataToUpdateRefs(imageIds); + } + + getImageDataToFindEqualDiffs(imageIds: string[]): TestEqualDiffsData[] { + return this._testsTree.getImageDataToFindEqualDiffs(imageIds); + } + + getCurrAttempt(formattedResult: ReporterTestResult): number { + const lastResult = this._testsTree.getLastResult(formattedResult); + this._checkResult(lastResult, formattedResult); + + const {status, attempt} = lastResult; + + return [IDLE, RUNNING, SKIPPED].includes(status) ? attempt : attempt + 1; + } + + getUpdatedAttempt(formattedResult: ReporterTestResult): number { + const lastResult = this._testsTree.getLastResult(formattedResult); + this._checkResult(lastResult, formattedResult); + + const {attempt} = lastResult; + + const imagesInfo = this._testsTree.getImagesInfo(formattedResult.id); + const isUpdated = imagesInfo.some((image) => image.status === UPDATED); + + return isUpdated ? attempt : attempt + 1; + } + + undoAcceptImage(formattedResult: ReporterTestResult, stateName: string): UndoAcceptImageResult | null { + const resultId = formattedResult.id; + const suitePath = formattedResult.testPath; + const browserName = formattedResult.browserId; + const resultData = this._testsTree.getResultDataToUnacceptImage(resultId, stateName); + + if (!resultData || !isUpdatedStatus(resultData.status)) { + return null; + } + + const { + imageId, + status, + timestamp, + previousImage, + shouldRemoveResult + } = resultData; + + const previousExpectedPath = _.get(previousImage, 'expectedImg.path', null); + const previousImageRefImgSize = _.get(previousImage, 'refImg.size', null); + const shouldRemoveReference = _.isNull(previousImageRefImgSize); + const shouldRevertReference = !shouldRemoveReference; + + let updatedImage, removedResult; + + if (shouldRemoveResult) { + this._testsTree.removeTestResult(resultId); + formattedResult.attempt = formattedResult.attempt - 1; + + removedResult = resultId; + } else { + updatedImage = this._testsTree.updateImageInfo(imageId, previousImage); + } + + this._deleteTestResultFromDb({where: [ + `${DB_COLUMNS.SUITE_PATH} = ?`, + `${DB_COLUMNS.NAME} = ?`, + `${DB_COLUMNS.STATUS} = ?`, + `${DB_COLUMNS.TIMESTAMP} = ?`, + `json_extract(${DB_COLUMNS.IMAGES_INFO}, '$[0].stateName') = ?` + ].join(' AND ')}, JSON.stringify(suitePath), browserName, status, timestamp.toString(), stateName); + + return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference}; + } + + protected override _addTestResult(formattedResult: ReporterTestResult, props: {status: TestStatus} & Partial, opts: {failResultId?: string} = {}): ReporterTestResult { + super._addTestResult(formattedResult, props); + + const testResult = this._createTestResult(formattedResult, { + ...props, + timestamp: formattedResult.timestamp ?? 0, + attempt: formattedResult.attempt + }); + + this._extendTestWithImagePaths(testResult, formattedResult, opts); + + if (![IDLE, RUNNING].includes(testResult.status)) { + this._updateTestResultStatus(testResult, formattedResult); + } + + this._testsTree.addTestResult(testResult, formattedResult); + + return formattedResult; + } + + protected _checkResult(result: TreeResult | undefined, formattedResult: ReporterTestResult): asserts result is TreeResult { + if (!result) { + const filteredTestTreeResults = _.pickBy( + this._testsTree.tree.results.byId, + (_result, resultId) => resultId.startsWith(formattedResult.fullName)); + + throw new Error('Failed to get last result for test:\n' + + `fullName = ${formattedResult.fullName}; browserId = ${formattedResult.browserId}\n` + + `Related testsTree results: ${JSON.stringify(filteredTestTreeResults)}\n`); + } + } + + private _updateTestResultStatus(testResult: PreparedTestResult, formattedResult: ReporterTestResult): void { + if (!hasResultFails(testResult) && !isSkippedStatus(testResult.status)) { + testResult.status = SUCCESS; + return; + } + + const imageErrors = testResult.imagesInfo.map(imagesInfo => (imagesInfo as {error: {name?: string}}).error ?? {}); + if (hasDiff(imageErrors) || hasNoRefImageErrors({assertViewResults: imageErrors})) { + testResult.status = FAIL; + return; + } + + if (!_.isEmpty(formattedResult.error)) { + testResult.status = ERROR; + return; + } + } + + private _extendTestWithImagePaths(testResult: PreparedTestResult, formattedResult: ReporterTestResult, opts: {failResultId?: string} = {}): void { + const newImagesInfo = formattedResult.imagesInfo; + + if (testResult.status !== UPDATED) { + _.set(testResult, 'imagesInfo', newImagesInfo); + return; + } + + const failImagesInfo = opts.failResultId ? this._testsTree.getImagesInfo(opts.failResultId) : []; + + if (failImagesInfo.length) { + testResult.imagesInfo = _.clone(failImagesInfo); + + newImagesInfo?.forEach((imageInfo) => { + const {stateName} = imageInfo as ImageInfoWithState; + let index = _.findIndex(testResult.imagesInfo, {stateName}); + index = index >= 0 ? index : _.findLastIndex(testResult.imagesInfo); + testResult.imagesInfo[index] = imageInfo; + }); + } + } +} diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index dbb43d809..5ce2e56c6 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -23,6 +23,7 @@ import {ImageHandler} from '../image-handler'; import {SqliteImageStore} from '../image-store'; import {getUrlWithBase, getError, getRelativeUrl, hasDiff, hasNoRefImageErrors} from '../common-utils'; import {getTestFromDb} from '../db-utils/server'; +import {ImageDiffError} from '../errors'; const ignoredStatuses = [RUNNING, IDLE]; @@ -102,7 +103,7 @@ export class StaticReportBuilder { } addRetry(result: ReporterTestResult): ReporterTestResult { - if (hasDiff(result.assertViewResults)) { + if (hasDiff(result.assertViewResults as ImageDiffError[])) { return this._addFailResult(result); } else { return this._addErrorResult(result); @@ -121,10 +122,10 @@ export class StaticReportBuilder { formattedResult.image = hasImage(formattedResult); const testResult = this._createTestResult(formattedResult, _.extend(props, { - timestamp: formattedResult.timestamp + timestamp: formattedResult.timestamp ?? 0 })); - if (hasNoRefImageErrors(formattedResult)) { + if (hasNoRefImageErrors(formattedResult as {assertViewResults: ImageDiffError[]})) { testResult.status = FAIL; } @@ -138,7 +139,7 @@ export class StaticReportBuilder { return formattedResult; } - protected _createTestResult(result: ReporterTestResult, props: {status: TestStatus} & Partial): PreparedTestResult { + protected _createTestResult(result: ReporterTestResult, props: {attempt?: number, status: TestStatus, timestamp: number;} & Partial): PreparedTestResult { const { browserId, file, sessionId, description, history, imagesInfo = [], screenshot, multipleTabs, errorDetails diff --git a/lib/reporter-helpers.js b/lib/reporter-helpers.js index 49f37c353..3dc583b1f 100644 --- a/lib/reporter-helpers.js +++ b/lib/reporter-helpers.js @@ -7,7 +7,7 @@ const {getShortMD5} = require('./common-utils'); const utils = require('./server-utils'); const {ImageHandler} = require('./image-handler'); -const mkReferenceId = (testId, stateName) => getShortMD5(`${testId}#${stateName}`); +const mkReferenceHash = (testId, stateName) => getShortMD5(`${testId}#${stateName}`); exports.updateReferenceImage = async (testResult, reportPath, stateName) => { const currImg = ImageHandler.getCurrImg(testResult.assertViewResults, stateName) ?? {}; @@ -19,7 +19,7 @@ exports.updateReferenceImage = async (testResult, reportPath, stateName) => { const referencePath = ImageHandler.getRefImg(testResult.assertViewResults, stateName)?.path; if (utils.fileExists(referencePath)) { - const referenceId = mkReferenceId(testResult.id, stateName); + const referenceId = mkReferenceHash(testResult.id, stateName); const oldReferencePath = path.resolve(tmp.tmpdir, referenceId); await utils.copyFileAsync(referencePath, oldReferencePath); } @@ -30,9 +30,9 @@ exports.updateReferenceImage = async (testResult, reportPath, stateName) => { ]); }; -exports.revertReferenceImage = (testResult, stateName) => { - const referenceId = mkReferenceId(testResult.id, stateName); - const oldReferencePath = path.resolve(tmp.tmpdir, referenceId); +exports.revertReferenceImage = (referenceId, testResult, stateName) => { + const referenceHash = mkReferenceHash(referenceId, stateName); + const oldReferencePath = path.resolve(tmp.tmpdir, referenceHash); const referencePath = ImageHandler.getRefImg(testResult.assertViewResults, stateName)?.path; return utils.copyFileAsync(oldReferencePath, referencePath); diff --git a/lib/sqlite-adapter.ts b/lib/sqlite-adapter.ts index bb5c41857..3e866a695 100644 --- a/lib/sqlite-adapter.ts +++ b/lib/sqlite-adapter.ts @@ -35,14 +35,14 @@ export interface PreparedTestResult { suiteUrl: string; metaInfo: Record; history: string[]; - description: unknown; + description?: string | null; error?: {message?: string; stack?: string; stateName?: string}; skipReason?: string; imagesInfo: ImageInfoFull[]; screenshot: boolean; multipleTabs: boolean; status: TestStatus; - timestamp?: number; + timestamp: number; errorDetails?: ErrorDetails; } diff --git a/lib/static/components/gui.jsx b/lib/static/components/gui.jsx index 3ba3bf12c..81bcf2c72 100644 --- a/lib/static/components/gui.jsx +++ b/lib/static/components/gui.jsx @@ -11,7 +11,7 @@ import Loading from './loading'; import ModalContainer from '../containers/modal'; import MainTree from './main-tree'; import CustomScripts from './custom-scripts'; -import clientEvents from '../../gui/constants/client-events'; +import {ClientEvents} from '../../gui/constants/client-events'; import FaviconChanger from './favicon-changer'; import ExtensionPoint from './extension-point'; import BottomProgressBar from './bottom-progress-bar'; @@ -40,24 +40,24 @@ class Gui extends Component { const {actions} = this.props; const eventSource = new EventSource('/events'); - eventSource.addEventListener(clientEvents.BEGIN_SUITE, (e) => { + eventSource.addEventListener(ClientEvents.BEGIN_SUITE, (e) => { const data = JSON.parse(e.data); actions.suiteBegin(data); }); - eventSource.addEventListener(clientEvents.BEGIN_STATE, (e) => { + eventSource.addEventListener(ClientEvents.BEGIN_STATE, (e) => { const data = JSON.parse(e.data); actions.testBegin(data); }); - [clientEvents.TEST_RESULT, clientEvents.ERROR].forEach((eventName) => { + [ClientEvents.TEST_RESULT, ClientEvents.ERROR].forEach((eventName) => { eventSource.addEventListener(eventName, (e) => { const data = JSON.parse(e.data); actions.testResult(data); }); }); - eventSource.addEventListener(clientEvents.END, () => { + eventSource.addEventListener(ClientEvents.END, () => { actions.testsEnd(); }); } diff --git a/lib/test-adapter/hermione.ts b/lib/test-adapter/hermione.ts index 8656d7d4c..b7f93fa5a 100644 --- a/lib/test-adapter/hermione.ts +++ b/lib/test-adapter/hermione.ts @@ -49,7 +49,7 @@ export class HermioneTestAdapter implements ReporterTestResult { this._testResult = testResult; this._testId = mkTestId(testResult.fullTitle(), testResult.browserId); this._errorDetails = null; - this._timestamp = this._testResult.timestamp; + this._timestamp = this._testResult.timestamp ?? this._testResult.startTime ?? Date.now(); this._status = status; const browserVersion = _.get(this._testResult, 'meta.browserVersion', this._testResult.browserVersion); @@ -89,10 +89,6 @@ export class HermioneTestAdapter implements ReporterTestResult { return this._imagesInfoFormatter.getImagesInfo(this); } - get origAttempt(): number | undefined { - return this._testResult.origAttempt; - } - get attempt(): number { return this._attempt; } @@ -204,16 +200,4 @@ export class HermioneTestAdapter implements ReporterTestResult { await utils.makeDirFor(detailsFilePath); await fs.writeFile(detailsFilePath, detailsData); } - - decreaseAttemptNumber(): void { - const testId = mkTestId(this._testResult.fullTitle(), this.browserId); - const currentTestAttempt = testsAttempts.get(testId) as number; - const previousTestAttempt = currentTestAttempt - 1; - - if (previousTestAttempt) { - testsAttempts.set(testId, previousTestAttempt); - } else { - testsAttempts.delete(testId); - } - } } diff --git a/lib/tests-tree-builder/base.ts b/lib/tests-tree-builder/base.ts index 27ffb6335..69ea40d17 100644 --- a/lib/tests-tree-builder/base.ts +++ b/lib/tests-tree-builder/base.ts @@ -4,7 +4,8 @@ import {BrowserVersions, PWT_TITLE_DELIMITER, TestStatus, ToolName} from '../con import {ReporterTestResult} from '../test-adapter'; import {ImageInfoFull, ParsedSuitesRow} from '../types'; -type TreeResult = { +export type TreeResult = { + attempt: number; id: string; parentId: string; status: TestStatus; @@ -19,7 +20,7 @@ interface TreeBrowser { version: string; } -interface TreeSuite { +export interface TreeSuite { status?: TestStatus; id: string; parentId: string | null; @@ -30,7 +31,7 @@ interface TreeSuite { browserIds?: string[]; } -type TreeImages = { +export type TreeImage = { id: string; parentId: string; } & ImageInfoFull; @@ -50,7 +51,7 @@ export interface Tree { allIds: string[] }, images: { - byId: Record, + byId: Record, allIds: string[] } } @@ -222,7 +223,7 @@ export class BaseTestsTreeBuilder { this._tree.results.allIds.push(id); } - this._tree.results.byId[id] = {id, parentId, ...resultWithoutImagesInfo, imageIds}; + this._tree.results.byId[id] = {attempt: 0, id, parentId, ...resultWithoutImagesInfo, imageIds}; } protected _addImages(imageIds: string[], {imagesInfo, parentId}: ImagesPayload): void { diff --git a/lib/tests-tree-builder/gui.js b/lib/tests-tree-builder/gui.ts similarity index 55% rename from lib/tests-tree-builder/gui.js rename to lib/tests-tree-builder/gui.ts index 41caace3b..ffd88676f 100644 --- a/lib/tests-tree-builder/gui.js +++ b/lib/tests-tree-builder/gui.ts @@ -1,55 +1,106 @@ -'use strict'; - -const _ = require('lodash'); -const {BaseTestsTreeBuilder} = require('./base'); -const {UPDATED} = require('../constants/test-statuses'); -const {isUpdatedStatus} = require('../common-utils'); - -module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { - getLastResult(formattedResult) { +import _ from 'lodash'; +import {BaseTestsTreeBuilder, Tree, TreeImage, TreeResult, TreeSuite} from './base'; +import {TestStatus, UPDATED} from '../constants'; +import {isUpdatedStatus} from '../common-utils'; +import {ReporterTestResult} from '../test-adapter'; +import {ImageInfoFail, ImageInfoWithState} from '../types'; + +interface SuiteBranch { + id: string; + status?: TestStatus; +} + +export interface TestBranch { + result: TreeResult; + images: TreeImage[]; + suites: SuiteBranch[]; +} + +export interface TestRefUpdateData { + browserId: string; + error?: TreeResult['error']; + suite: {path: string[]}; + state: {name: string}; + metaInfo: TreeResult['metaInfo']; + imagesInfo: { + stateName: ImageInfoWithState['stateName']; + actualImg: ImageInfoWithState['actualImg']; + status: TestStatus; + }[]; + attempt: number; +} + +export type TestEqualDiffsData = TreeImage & { browserName: string }; + +interface TestUndoRefUpdateData { + imageId: string; + status: TestStatus; + timestamp: number; + previousImage: TreeImage | null; + previousImageId: string | null; + shouldRemoveResult: boolean; +} + +export class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { + getLastResult(formattedResult: ReporterTestResult): TreeResult | undefined { const browserId = this._buildBrowserId(formattedResult); const browser = this._tree.browsers.byId[browserId]; const testResultId = _.last(browser.resultIds); + if (!testResultId) { + return; + } + return this._tree.results.byId[testResultId]; } - getResultByOrigAttempt(formattedResult) { - const testResultId = this._buildTestResultId(formattedResult); + /* Returns "real" last test result - one that wasn't generated by clicking accept button */ + getLastActualResult(formattedResult: ReporterTestResult): TreeResult | undefined { + let attempt = formattedResult.attempt - 1; + while (attempt >= 0) { + const resultId = this._buildResultId(formattedResult, attempt); - return this._tree.results.byId[testResultId]; + if (![TestStatus.RUNNING, TestStatus.UPDATED].includes(this._tree.results.byId[resultId].status)) { + break; + } + + attempt -= 1; + } + + const resultId = this._buildResultId(formattedResult, attempt); + + return this._tree.results.byId[resultId]; } - getImagesInfo(testId) { + getImagesInfo(testId: string): TreeImage[] { return this._tree.results.byId[testId].imageIds.map((imageId) => { return this._tree.images.byId[imageId]; }); } - getTestBranch(id) { - const getSuites = (suite) => { + getTestBranch(id: string): TestBranch { + const getSuites = (suite: TreeSuite): SuiteBranch[] => { if (suite.root) { return [{id: suite.id, status: suite.status}]; } return _.flatten([ - getSuites(this._tree.suites.byId[suite.parentId]), + getSuites(this._tree.suites.byId[suite.parentId as string]), {id: suite.id, status: suite.status} ]); }; const result = this._tree.results.byId[id]; - const images = result.imageIds.map((imgId) => this._tree.images.byId[imgId]); + const images = result.imageIds.map((imgId): TreeImage => this._tree.images.byId[imgId]); const browser = this._tree.browsers.byId[result.parentId]; const suites = getSuites(this._tree.suites.byId[browser.parentId]); return {result, images, suites}; } - getTestsDataToUpdateRefs(imageIds) { - const imagesById = [].concat(imageIds).reduce((acc, imgId) => { + getTestsDataToUpdateRefs(imageIds: string[]): TestRefUpdateData[] { + const imagesById = ([] as string[]).concat(imageIds).reduce>((acc, imgId) => { acc[imgId] = this._tree.images.byId[imgId]; - return acc; }, {}); @@ -61,20 +112,26 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { const suite = this._tree.suites.byId[browser.parentId]; const imagesInfo = imagesByResultId[resultId] - .map(({stateName, actualImg}) => ({stateName, actualImg, status: UPDATED})); + .filter(treeImage => (treeImage as ImageInfoFail).stateName) + .map((treeImage) => ({ + stateName: (treeImage as ImageInfoWithState).stateName, + actualImg: treeImage.actualImg, + status: UPDATED + })); return { suite: {path: suite.suitePath.slice(0, -1)}, state: {name: suite.name}, browserId: browser.name, + error: result.error, metaInfo: result.metaInfo, imagesInfo, attempt: result.attempt - }; + } satisfies TestRefUpdateData; }); } - getImageDataToFindEqualDiffs(imageIds) { + getImageDataToFindEqualDiffs(imageIds: string[]): TestEqualDiffsData[] { return imageIds.map((imageId) => { const image = this._tree.images.byId[imageId]; const result = this._tree.results.byId[image.parentId]; @@ -84,10 +141,15 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { }); } - getResultDataToUnacceptImage(resultId, stateName) { + getResultDataToUnacceptImage(resultId: string, stateName: string): TestUndoRefUpdateData | null { const imageId = this._tree.results.byId[resultId].imageIds.find(imageId => { - return this._tree.images.byId[imageId].stateName === stateName; + return (this._tree.images.byId[imageId] as ImageInfoWithState).stateName === stateName; }); + + if (!imageId) { + return null; + } + const image = this._tree.images.byId[imageId]; const result = this._tree.results.byId[image.parentId]; const browser = this._tree.browsers.byId[result.parentId]; @@ -96,14 +158,16 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { const previousResult = previousResultId ? this._tree.results.byId[previousResultId] : null; const previousImageId = previousResult - ? previousResult.imageIds.find(imageId => this._tree.images.byId[imageId].stateName === image.stateName) + ? previousResult.imageIds.find(imageId => + (this._tree.images.byId[imageId] as ImageInfoWithState).stateName === + (image as ImageInfoWithState).stateName) as string : null; const previousImage = previousImageId ? this._tree.images.byId[previousImageId] : null; const countUpdated = result.imageIds.reduce((acc, currImageId) => { - return acc + isUpdatedStatus(this._tree.images.byId[currImageId].status); + return isUpdatedStatus(this._tree.images.byId[currImageId].status) ? acc + 1 : acc; }, 0); const shouldRemoveResult = isUpdatedStatus(image.status) && countUpdated === 1; @@ -117,24 +181,25 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { }; } - reuseTestsTree(testsTree) { + reuseTestsTree(testsTree: Tree): void { this._tree.browsers.allIds.forEach((browserId) => this._reuseBrowser(testsTree, browserId)); } - updateImageInfo(imageId, imageInfo) { + updateImageInfo(imageId: string, imageInfo?: TreeImage | null): TreeImage { const currentImage = this._tree.images.byId[imageId]; - const updatedImage = { + // TODO: check TreeImage type. Is it correct to let it consist of id and parentId? + const updatedImage: TreeImage = { ...imageInfo, id: currentImage.id, parentId: currentImage.parentId - }; + } as TreeImage; this._tree.images.byId[imageId] = updatedImage; return updatedImage; } - removeTestResult(resultId) { + removeTestResult(resultId: string): void { const result = this._tree.results.byId[resultId]; this._removeImagesById(result.imageIds); @@ -147,7 +212,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { delete this._tree.results.byId[resultId]; } - _removeImagesById(imageIds) { + private _removeImagesById(imageIds: string[]): void { this._tree.images.allIds = this._tree.images.allIds.filter(id => !imageIds.includes(id)); imageIds.forEach(imageId => { @@ -155,7 +220,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { }); } - _reuseBrowser(testsTree, browserId) { + private _reuseBrowser(testsTree: Tree, browserId: string): void { const reuseBrowser = testsTree.browsers.byId[browserId]; if (!reuseBrowser) { @@ -168,7 +233,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { this._reuseSuiteStatus(testsTree, this._tree.browsers.byId[browserId].parentId); } - _reuseResults(testsTree, resultId) { + private _reuseResults(testsTree: Tree, resultId: string): void { const reuseResult = testsTree.results.byId[resultId]; if (!this._tree.results.byId[resultId]) { @@ -180,7 +245,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { reuseResult.imageIds.forEach((imageId) => this._reuseImages(testsTree, imageId)); } - _reuseImages(testsTree, imageId) { + private _reuseImages(testsTree: Tree, imageId: string): void { const reuseImage = testsTree.images.byId[imageId]; if (!this._tree.images.byId[imageId]) { @@ -190,7 +255,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { this._tree.images.byId[imageId] = reuseImage; } - _reuseSuiteStatus(testsTree, suiteId) { + _reuseSuiteStatus(testsTree: Tree, suiteId?: string | null): void { if (!suiteId) { return; } @@ -201,7 +266,7 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { this._reuseSuiteStatus(testsTree, suite.parentId); } - _buildBrowserId(formattedResult) { + protected _buildBrowserId(formattedResult: Pick): string { const {testPath, browserId: browserName} = formattedResult; const suiteId = this._buildId(testPath); const browserId = this._buildId(suiteId, browserName); @@ -209,9 +274,9 @@ module.exports = class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { return browserId; } - _buildTestResultId(formattedResult) { + protected _buildResultId(formattedResult: ReporterTestResult, attempt: number): string { const browserId = this._buildBrowserId(formattedResult); - return `${browserId} ${formattedResult.origAttempt}`; + return `${browserId} ${attempt}`; } -}; +} diff --git a/lib/tests-tree-builder/static.ts b/lib/tests-tree-builder/static.ts index 9987834b3..b7a44aa39 100644 --- a/lib/tests-tree-builder/static.ts +++ b/lib/tests-tree-builder/static.ts @@ -19,10 +19,10 @@ type FinalStats = Stats & { } } -interface SkipItem { +export interface SkipItem { browser: string; suite: string; - comment: string; + comment?: string; } interface BrowserItem { @@ -204,6 +204,7 @@ function mkTestResult(row: RawSuitesRow, data: {attempt: number}): ParsedSuitesR 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), + timestamp: Number(row[DB_COLUMN_INDEXES.timestamp]), ...data }; } diff --git a/lib/types.ts b/lib/types.ts index 706a9fefd..88107fd06 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -13,7 +13,6 @@ export {Suite as HermioneSuite} from 'hermione'; export interface HermioneTestResult extends HermioneTestResultOriginal { timestamp?: number; updated?: boolean; - origAttempt?: number; } export interface ImagesSaver { @@ -89,6 +88,8 @@ export interface ImageInfoError { actualImg: ImageData; } +export type ImageInfoWithState = ImageInfoFail | ImageInfoSuccess | ImageInfoError; + export type ImageInfoFull = ImageInfoFail | ImageInfoSuccess | ImageInfoError | ImageInfoPageSuccess; export type ImageInfo = @@ -116,10 +117,10 @@ export interface LabeledSuitesRow { export type RawSuitesRow = LabeledSuitesRow[keyof LabeledSuitesRow][]; export interface ParsedSuitesRow { - description: string | null; - error: { - message: string; - stack: string; + description?: string | null; + error?: { + message?: string; + stack?: string; }; history: unknown; imagesInfo: ImageInfoFull[]; @@ -130,9 +131,10 @@ export interface ParsedSuitesRow { multipleTabs: boolean; name: string; screenshot: boolean; - skipReason: string; + skipReason?: string; status: TestStatus; suiteUrl: string; + timestamp: number; } export interface Attempt { diff --git a/package-lock.json b/package-lock.json index 4e37be78a..4500ba7f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,10 +54,13 @@ "@playwright/test": "^1.37.1", "@swc/core": "^1.3.64", "@types/better-sqlite3": "^7.6.4", + "@types/bluebird": "^3.5.3", "@types/chai": "^4.3.5", "@types/debug": "^4.1.8", "@types/enzyme": "^3.10.13", + "@types/express": "4.16", "@types/fs-extra": "^7.0.0", + "@types/json-stringify-safe": "^5.0.2", "@types/lodash": "^4.14.195", "@types/nested-error-stacks": "^2.1.0", "@types/opener": "^1.4.0", @@ -4178,6 +4181,22 @@ "@types/node": "*" } }, + "node_modules/@types/bluebird": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.41.tgz", + "integrity": "sha512-/OT2UoYPu2fqGNS85UYUx0Ke8Zd/vM0/Au0JqLInTprkRO0NexYe7qAUkDsjhsO3BKHI14wX/UhN5SUaoFVDUQ==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -4205,6 +4224,15 @@ "@types/node": "*" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", @@ -4261,6 +4289,29 @@ "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", + "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/fs-extra": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-7.0.0.tgz", @@ -4302,6 +4353,12 @@ "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -4332,6 +4389,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/json-stringify-safe": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/json-stringify-safe/-/json-stringify-safe-5.0.3.tgz", + "integrity": "sha512-oNOjRxLfPeYbBSQ60maucaFNqbslVOPU4WWs5t/sHvAh6tyo/CThXSG+E24tEzkgh/fzvxyDrYdOJufgeNy1sQ==", + "dev": true + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -4347,6 +4410,12 @@ "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -4410,6 +4479,18 @@ "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", "dev": true }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, "node_modules/@types/react": { "version": "17.0.36", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", @@ -4454,6 +4535,27 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/sinon": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz", @@ -34592,6 +34694,22 @@ "@types/node": "*" } }, + "@types/bluebird": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.41.tgz", + "integrity": "sha512-/OT2UoYPu2fqGNS85UYUx0Ke8Zd/vM0/Au0JqLInTprkRO0NexYe7qAUkDsjhsO3BKHI14wX/UhN5SUaoFVDUQ==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -34619,6 +34737,15 @@ "@types/node": "*" } }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", @@ -34677,6 +34804,29 @@ "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", "dev": true }, + "@types/express": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", + "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "@types/fs-extra": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-7.0.0.tgz", @@ -34718,6 +34868,12 @@ "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -34748,6 +34904,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/json-stringify-safe": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/json-stringify-safe/-/json-stringify-safe-5.0.3.tgz", + "integrity": "sha512-oNOjRxLfPeYbBSQ60maucaFNqbslVOPU4WWs5t/sHvAh6tyo/CThXSG+E24tEzkgh/fzvxyDrYdOJufgeNy1sQ==", + "dev": true + }, "@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -34763,6 +34925,12 @@ "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", "dev": true }, + "@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -34826,6 +34994,18 @@ "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", "dev": true }, + "@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, "@types/react": { "version": "17.0.36", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", @@ -34870,6 +35050,27 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "@types/sinon": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz", diff --git a/package.json b/package.json index 8b7463fbe..776ea1038 100644 --- a/package.json +++ b/package.json @@ -101,10 +101,13 @@ "@playwright/test": "^1.37.1", "@swc/core": "^1.3.64", "@types/better-sqlite3": "^7.6.4", + "@types/bluebird": "^3.5.3", "@types/chai": "^4.3.5", "@types/debug": "^4.1.8", "@types/enzyme": "^3.10.13", + "@types/express": "4.16", "@types/fs-extra": "^7.0.0", + "@types/json-stringify-safe": "^5.0.2", "@types/lodash": "^4.14.195", "@types/nested-error-stacks": "^2.1.0", "@types/opener": "^1.4.0", diff --git a/test/func/fixtures/playwright/tests/failed-describe.spec.ts b/test/func/fixtures/playwright/tests/failed-describe.spec.ts index 214fbcc22..9704ed5a0 100644 --- a/test/func/fixtures/playwright/tests/failed-describe.spec.ts +++ b/test/func/fixtures/playwright/tests/failed-describe.spec.ts @@ -34,6 +34,7 @@ test.describe('failed describe', () => { test.skip(true, 'foo-bar'); }); + // Warning: this test may fail unless launched inside docker container test('test with successful assertView and error', async ({page, baseURL}) => { await page.goto(baseURL as string); diff --git a/test/func/packages/webpack.common.js b/test/func/packages/webpack.common.js index 7751452db..8c0d00022 100644 --- a/test/func/packages/webpack.common.js +++ b/test/func/packages/webpack.common.js @@ -17,7 +17,7 @@ module.exports = { loader: 'babel-loader', exclude: /node_modules/, options: { - configFile: path.resolve(__dirname, '../../../.babelrc') + configFile: path.resolve(__dirname, '../../../babel.config.json') } } ] diff --git a/test/unit/lib/gui/api/index.js b/test/unit/lib/gui/api/index.js index cd2e2c853..8ba58af75 100644 --- a/test/unit/lib/gui/api/index.js +++ b/test/unit/lib/gui/api/index.js @@ -1,7 +1,7 @@ 'use strict'; const EventEmitter2 = require('eventemitter2'); -const guiEvents = require('lib/gui/constants/gui-events'); +const {GuiEvents} = require('lib/gui/constants/gui-events'); const Api = require('lib/gui/api'); const {stubTool} = require('../../../utils'); @@ -20,7 +20,7 @@ describe('lig/gui/api', () => { Api.create(tool); - assert.deepEqual(tool.gui.events, guiEvents); + assert.deepEqual(tool.gui.events, GuiEvents); }); }); @@ -29,7 +29,7 @@ describe('lig/gui/api', () => { const tool = stubTool(); const api = Api.create(tool); const onServerInit = sinon.spy().named('onServerInit'); - tool.gui.on(guiEvents.SERVER_INIT, onServerInit); + tool.gui.on(GuiEvents.SERVER_INIT, onServerInit); api.initServer({foo: 'bar'}); @@ -42,7 +42,7 @@ describe('lig/gui/api', () => { const tool = stubTool(); const api = Api.create(tool); const onServerReady = sinon.spy().named('onServerReady'); - tool.gui.on(guiEvents.SERVER_READY, onServerReady); + tool.gui.on(GuiEvents.SERVER_READY, onServerReady); api.serverReady({url: 'http://my.server'}); diff --git a/test/unit/lib/gui/tool-runner/index.js b/test/unit/lib/gui/tool-runner/index.js index 9ff31fdba..624eb54c8 100644 --- a/test/unit/lib/gui/tool-runner/index.js +++ b/test/unit/lib/gui/tool-runner/index.js @@ -4,23 +4,23 @@ const path = require('path'); const fs = require('fs-extra'); const _ = require('lodash'); const proxyquire = require('proxyquire'); -const GuiReportBuilder = require('lib/report-builder/gui'); +const {GuiReportBuilder} = require('lib/report-builder/gui'); const {LOCAL_DATABASE_NAME} = require('lib/constants/database'); const {logger} = require('lib/common-utils'); -const Runner = require('lib/gui/tool-runner/runner'); const {stubTool, stubConfig, mkImagesInfo, mkState, mkSuite} = require('test/unit/utils'); describe('lib/gui/tool-runner/index', () => { const sandbox = sinon.createSandbox(); let reportBuilder; let ToolGuiReporter; - let reportSubscriber; + let subscribeOnToolEvents; let hermione; let getTestsTreeFromDatabase; let looksSame; let removeReferenceImage; let revertReferenceImage; let toolRunnerUtils; + let createTestRunner; const mkTestCollection_ = (testsTree = {}) => { return { @@ -67,6 +67,8 @@ describe('lib/gui/tool-runner/index', () => { beforeEach(() => { hermione = stubTool(); + createTestRunner = sinon.stub(); + toolRunnerUtils = { findTestResult: sandbox.stub(), formatId: sandbox.stub().returns('some-id') @@ -75,7 +77,7 @@ describe('lib/gui/tool-runner/index', () => { reportBuilder = sinon.createStubInstance(GuiReportBuilder); reportBuilder.getUpdatedAttempt.returns(0); - reportSubscriber = sandbox.stub().named('reportSubscriber').resolves(); + subscribeOnToolEvents = sandbox.stub().named('reportSubscriber').resolves(); looksSame = sandbox.stub().named('looksSame').resolves({equal: true}); removeReferenceImage = sandbox.stub().resolves(); revertReferenceImage = sandbox.stub().resolves(); @@ -87,7 +89,8 @@ describe('lib/gui/tool-runner/index', () => { ToolGuiReporter = proxyquire(`lib/gui/tool-runner`, { 'looks-same': looksSame, - './report-subscriber': reportSubscriber, + './runner': {createTestRunner}, + './report-subscriber': {subscribeOnToolEvents}, './utils': toolRunnerUtils, '../../db-utils/server': {getTestsTreeFromDatabase}, '../../reporter-helpers': { @@ -206,7 +209,7 @@ describe('lib/gui/tool-runner/index', () => { const gui = initGuiReporter(hermione, {paths: ['foo']}); return gui.initialize() - .then(() => assert.callOrder(reportSubscriber, hermione.readTests)); + .then(() => assert.callOrder(subscribeOnToolEvents, hermione.readTests)); }); it('should initialize report builder after read tests for the correct order of events', async () => { @@ -303,32 +306,6 @@ describe('lib/gui/tool-runner/index', () => { }); }); }); - - it('should pass "origAttempt" to the ReportBuilder.addUpdate method to be able to calculate status properly', async () => { - const tests = [{ - id: 'some-id', - fullTitle: () => 'some-title', - browserId: 'yabro', - attempt: 10, - suite: {path: ['suite1']}, - state: {}, - metaInfo: {}, - imagesInfo: [] - }]; - - const getScreenshotPath = sandbox.stub().returns('/ref/path1'); - const config = stubConfig({ - browsers: {yabro: {getScreenshotPath}} - }); - - const hermione = mkHermione_(config, {'some-title.yabro': tests[0]}); - const gui = initGuiReporter(hermione); - await gui.initialize(); - - await gui.updateReferenceImage(tests); - - assert.calledOnceWith(reportBuilder.addUpdated, sinon.match({origAttempt: 10})); - }); }); describe('undoAcceptImages', () => { @@ -375,11 +352,13 @@ describe('lib/gui/tool-runner/index', () => { it('should revert reference, if ReportBuilder.undoAcceptImages resolved "shouldRevertReference"', async () => { const stateName = 'plain'; - const {gui, tests} = await mkUndoTestData_({shouldRevertReference: true}, {stateName}); + const {gui, tests} = await mkUndoTestData_({ + shouldRevertReference: true, removedResult: 'some-result' + }, {stateName}); await gui.undoAcceptImages(tests); - assert.calledOnceWith(revertReferenceImage, sinon.match({fullName: 'some-title'}), 'plain'); + assert.calledOnceWith(revertReferenceImage, 'some-result', sinon.match({fullName: 'some-title'}), 'plain'); }); it('should update expected path', async () => { @@ -502,7 +481,7 @@ describe('lib/gui/tool-runner/index', () => { beforeEach(() => { runner = {run: sandbox.stub().resolves()}; collection = mkTestCollection_(); - sandbox.stub(Runner, 'create').withArgs(collection).returns(runner); + createTestRunner.withArgs(collection).returns(runner); hermione.readTests.resolves(collection); }); diff --git a/test/unit/lib/gui/tool-runner/report-subsciber.js b/test/unit/lib/gui/tool-runner/report-subsciber.js index 89f6b174a..ea19cb2ca 100644 --- a/test/unit/lib/gui/tool-runner/report-subsciber.js +++ b/test/unit/lib/gui/tool-runner/report-subsciber.js @@ -3,9 +3,9 @@ const {EventEmitter} = require('events'); const Promise = require('bluebird'); const _ = require('lodash'); -const reportSubscriber = require('lib/gui/tool-runner/report-subscriber'); -const GuiReportBuilder = require('lib/report-builder/gui'); -const clientEvents = require('lib/gui/constants/client-events'); +const {subscribeOnToolEvents} = require('lib/gui/tool-runner/report-subscriber'); +const {GuiReportBuilder} = require('lib/report-builder/gui'); +const {ClientEvents} = require('lib/gui/constants'); const {stubTool, stubConfig} = require('test/unit/utils'); const {HermioneTestAdapter} = require('lib/test-adapter'); const {ErrorName} = require('lib/errors'); @@ -50,10 +50,10 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { it('should emit "END" event for client', () => { const hermione = mkHermione_(); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); return hermione.emitAsync(hermione.events.RUNNER_END) - .then(() => assert.calledOnceWith(client.emit, clientEvents.END)); + .then(() => assert.calledOnceWith(client.emit, ClientEvents.END)); }); it('should emit "END" event after all promises are resolved', async () => { @@ -63,11 +63,11 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { reportBuilder.imageHandler.saveTestImages.callsFake(() => Promise.delay(100).then(mediator)); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); - assert.callOrder(mediator, client.emit.withArgs(clientEvents.END)); + assert.callOrder(mediator, client.emit.withArgs(ClientEvents.END)); }); }); @@ -78,10 +78,10 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { reportBuilder.getTestBranch.withArgs('some-id').returns('test-tree-branch'); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_BEGIN, testResult); - assert.calledOnceWith(client.emit, clientEvents.BEGIN_STATE, 'test-tree-branch'); + assert.calledOnceWith(client.emit, ClientEvents.BEGIN_STATE, 'test-tree-branch'); }); }); @@ -90,7 +90,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const hermione = mkHermione_(); const testResult = mkHermioneTestResult(); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); await hermione.emitAsync(hermione.events.TEST_PENDING, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); @@ -107,11 +107,11 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { reportBuilder.getTestBranch.withArgs('some-id').returns('test-tree-branch'); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); await hermione.emitAsync(hermione.events.TEST_PENDING, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); - assert.calledWith(client.emit, clientEvents.TEST_RESULT, 'test-tree-branch'); + assert.calledWith(client.emit, ClientEvents.TEST_RESULT, 'test-tree-branch'); }); }); @@ -122,7 +122,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { reportBuilder.getCurrAttempt.returns(1); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); @@ -133,7 +133,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const hermione = mkHermione_(); const testResult = mkHermioneTestResult({assertViewResults: [{name: ErrorName.IMAGE_DIFF}]}); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); @@ -146,11 +146,11 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { reportBuilder.getTestBranch.withArgs('some-id').returns('test-tree-branch'); - reportSubscriber(hermione, reportBuilder, client); + subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); await hermione.emitAsync(hermione.events.RUNNER_END); - assert.calledWith(client.emit, clientEvents.TEST_RESULT, 'test-tree-branch'); + assert.calledWith(client.emit, ClientEvents.TEST_RESULT, 'test-tree-branch'); }); }); }); diff --git a/test/unit/lib/report-builder/gui.js b/test/unit/lib/report-builder/gui.js index e0d2bdd30..c6acec0aa 100644 --- a/test/unit/lib/report-builder/gui.js +++ b/test/unit/lib/report-builder/gui.js @@ -6,7 +6,7 @@ const proxyquire = require('proxyquire'); const serverUtils = require('lib/server-utils'); const {HermioneTestAdapter} = require('lib/test-adapter'); const {SqliteAdapter} = require('lib/sqlite-adapter'); -const GuiTestsTreeBuilder = require('lib/tests-tree-builder/gui'); +const {GuiTestsTreeBuilder} = require('lib/tests-tree-builder/gui'); 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'); @@ -77,8 +77,13 @@ describe('GuiReportBuilder', () => { hasImage = sandbox.stub().returns(true); deleteFile = sandbox.stub().resolves(); GuiReportBuilder = proxyquire('lib/report-builder/gui', { + './static': { + StaticReportBuilder: proxyquire('lib/report-builder/static', { + '../sqlite-adapter': {SqliteAdapter} + }).StaticReportBuilder + }, '../server-utils': {hasImage, deleteFile} - }); + }).GuiReportBuilder; sandbox.stub(GuiTestsTreeBuilder, 'create').returns(Object.create(GuiTestsTreeBuilder.prototype)); sandbox.stub(GuiTestsTreeBuilder.prototype, 'sortTree').returns({}); @@ -88,7 +93,6 @@ describe('GuiReportBuilder', () => { sandbox.stub(GuiTestsTreeBuilder.prototype, 'getImageDataToFindEqualDiffs').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getImagesInfo').returns([]); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getLastResult').returns({}); - sandbox.stub(GuiTestsTreeBuilder.prototype, 'getResultByOrigAttempt').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'addTestResult').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getResultDataToUnacceptImage').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'updateImageInfo').returns({}); @@ -210,21 +214,16 @@ describe('GuiReportBuilder', () => { assert.equal(getTestResult_().status, SUCCESS); }); - it(`should corectly determine the status based on a previous result`, async () => { + it(`should correctly determine the status based on current result`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - GuiTestsTreeBuilder.prototype.getResultByOrigAttempt.returns({status: FAIL}); - reportBuilder.addUpdated(stubTest_({ + error: {name: 'some-error', message: 'some-message'}, imagesInfo: [{status: FAIL}], - attempt: 4, - origAttempt: 2 + attempt: 4 })); - assert.equal(getTestResult_().status, FAIL); - assert.calledOnceWith(GuiTestsTreeBuilder.prototype.getResultByOrigAttempt, sinon.match({ - origAttempt: 2 - })); + assert.equal(getTestResult_().status, ERROR); }); it('should update test image for current state name', async () => { @@ -239,7 +238,8 @@ describe('GuiReportBuilder', () => { const updatedTest = stubTest_({ imagesInfo: [ - {stateName: 'plain1', status: UPDATED} + {stateName: 'plain1', status: UPDATED}, + {stateName: 'plain2', status: FAIL} ] }); @@ -271,7 +271,7 @@ describe('GuiReportBuilder', () => { reportBuilder.addFail(failedTest); GuiTestsTreeBuilder.prototype.getImagesInfo.returns(failedTest.imagesInfo); - reportBuilder.addUpdated(updatedTest); + reportBuilder.addUpdated(updatedTest, 'result-2'); const {imagesInfo} = GuiTestsTreeBuilder.prototype.addTestResult.secondCall.args[0]; @@ -434,17 +434,6 @@ describe('GuiReportBuilder', () => { assert.calledOnceWith(GuiTestsTreeBuilder.prototype.removeTestResult, 'result-id'); }); - it('should decrease test attempt number after deleting result', async () => { - const resultId = 'id'; - const stateName = 's-name'; - const formattedResult = mkFormattedResultStub_({id: resultId, stateName}); - stubResultData_({shouldRemoveResult: true}, {resultId, stateName}); - - await reportBuilder.undoAcceptImage(formattedResult, stateName); - - assert.calledOnceWith(formattedResult.decreaseAttemptNumber); - }); - it('should resolve removed result id', async () => { const resultId = 'result-id'; const stateName = 's-name'; @@ -507,7 +496,7 @@ describe('GuiReportBuilder', () => { '["s","p"]', 'bro-name', UPDATED, - 100500, + '100500', 'plain' ); }); diff --git a/test/unit/lib/tests-tree-builder/base.js b/test/unit/lib/tests-tree-builder/base.js index 35e6163cd..de57131ea 100644 --- a/test/unit/lib/tests-tree-builder/base.js +++ b/test/unit/lib/tests-tree-builder/base.js @@ -184,6 +184,7 @@ describe('ResultsTreeBuilder', () => { assert.deepEqual( builder.tree.results.byId['s1 b1 0'], { + attempt: 0, id: 's1 b1 0', parentId: 's1 b1', imageIds: [], diff --git a/test/unit/lib/tests-tree-builder/gui.js b/test/unit/lib/tests-tree-builder/gui.js index e4770239b..dff7b4061 100644 --- a/test/unit/lib/tests-tree-builder/gui.js +++ b/test/unit/lib/tests-tree-builder/gui.js @@ -1,7 +1,7 @@ 'use strict'; const _ = require('lodash'); -const GuiResultsTreeBuilder = require('lib/tests-tree-builder/gui'); +const {GuiTestsTreeBuilder} = require('lib/tests-tree-builder/gui'); const {FAIL, SUCCESS, IDLE, UPDATED} = require('lib/constants/test-statuses'); const {ToolName} = require('lib/constants'); @@ -20,7 +20,7 @@ describe('GuiResultsTreeBuilder', () => { }); }; - const mkGuiTreeBuilder = () => GuiResultsTreeBuilder.create({toolName: ToolName.Hermione}); + const mkGuiTreeBuilder = () => GuiTestsTreeBuilder.create({toolName: ToolName.Hermione}); beforeEach(() => { builder = mkGuiTreeBuilder(); @@ -39,7 +39,7 @@ describe('GuiResultsTreeBuilder', () => { }); }); - describe('"getResultByOrigAttempt" method', () => { + describe('"getLastActualResult" method', () => { it('should return previous result from tree', () => { const formattedRes1 = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 0}); const formattedRes2 = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 1}); @@ -48,9 +48,9 @@ describe('GuiResultsTreeBuilder', () => { builder.addTestResult(mkTestResult_(), formattedRes2); builder.addTestResult(mkTestResult_(), formattedRes3); - const lastResult = builder.getResultByOrigAttempt({testPath: ['s'], browserId: 'b', origAttempt: 1}); + const lastResult = builder.getLastActualResult({testPath: ['s'], browserId: 'b', attempt: 1}); - assert.deepEqual(lastResult, builder.tree.results.byId['s b 1']); + assert.deepEqual(lastResult, builder.tree.results.byId['s b 0']); }); }); diff --git a/test/unit/lib/tests-tree-builder/static.js b/test/unit/lib/tests-tree-builder/static.js index 2404e359e..7a2521c21 100644 --- a/test/unit/lib/tests-tree-builder/static.js +++ b/test/unit/lib/tests-tree-builder/static.js @@ -61,6 +61,7 @@ describe('StaticResultsTreeBuilder', () => { suiteUrl: result.suiteUrl, skipReason: result.skipReason, error: result.error, + timestamp: result.timestamp, ...data }; }; @@ -109,7 +110,7 @@ describe('StaticResultsTreeBuilder', () => { ); assert.calledWith( StaticTestsTreeBuilder.prototype.addTestResult.secondCall, - formatToTestResult(dataFromDb1, {attempt: 1}), + formatToTestResult(dataFromDb2, {attempt: 1}), {browserId: 'yabro', testPath: ['s1'], attempt: 1} ); });