From 3266eae61ef745696b348d0143a1bbc6f05ddb0e Mon Sep 17 00:00:00 2001 From: shadowusr Date: Wed, 22 Nov 2023 17:13:09 +0300 Subject: [PATCH 01/17] refactor: rename sqlite-adapter to sqlite-client --- lib/db-utils/server.ts | 6 +- lib/image-store.ts | 10 +-- lib/report-builder/gui.ts | 2 +- lib/report-builder/static.ts | 20 +++--- lib/{sqlite-adapter.ts => sqlite-client.ts} | 13 ++-- test/unit/hermione.js | 8 +-- test/unit/lib/report-builder/gui.js | 10 +-- .../{sqlite-adapter.js => sqlite-client.js} | 64 +++++++++---------- 8 files changed, 66 insertions(+), 67 deletions(-) rename lib/{sqlite-adapter.ts => sqlite-client.ts} (94%) rename test/unit/lib/{sqlite-adapter.js => sqlite-client.js} (70%) diff --git a/lib/db-utils/server.ts b/lib/db-utils/server.ts index fbdb54560..a93b65382 100644 --- a/lib/db-utils/server.ts +++ b/lib/db-utils/server.ts @@ -14,7 +14,7 @@ import {DbLoadResult, HandleDatabasesOptions} from './common'; import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types'; import {Tree} from '../tests-tree-builder/base'; import {ReporterTestResult} from '../test-adapter'; -import {SqliteAdapter} from '../sqlite-adapter'; +import {SqliteClient} from '../sqlite-client'; export * from './common'; @@ -121,8 +121,8 @@ async function rewriteDatabaseUrls(dbPaths: string[], mainDatabaseUrls: string, }); } -export const getTestFromDb = (sqliteAdapter: SqliteAdapter, testResult: ReporterTestResult): T | undefined => { - return sqliteAdapter.query({ +export const getTestFromDb = (dbClient: SqliteClient, testResult: ReporterTestResult): T | undefined => { + return dbClient.query({ select: '*', where: `${DB_COLUMNS.SUITE_PATH} = ? AND ${DB_COLUMNS.NAME} = ? AND ${DB_COLUMNS.STATUS} = ?`, orderBy: DB_COLUMNS.TIMESTAMP, diff --git a/lib/image-store.ts b/lib/image-store.ts index bdbb6db79..5970f0ea3 100644 --- a/lib/image-store.ts +++ b/lib/image-store.ts @@ -1,5 +1,5 @@ import {DB_COLUMNS} from './constants'; -import {SqliteAdapter} from './sqlite-adapter'; +import {SqliteClient} from './sqlite-client'; import {ImageInfo, ImageInfoFull, LabeledSuitesRow} from './types'; import {ReporterTestResultPlain} from './image-handler'; @@ -8,10 +8,10 @@ export interface ImageStore { } export class SqliteImageStore implements ImageStore { - private _sqliteAdapter: SqliteAdapter; + private _dbClient: SqliteClient; - constructor(sqliteAdapter: SqliteAdapter) { - this._sqliteAdapter = sqliteAdapter; + constructor(dbClient: SqliteClient) { + this._dbClient = dbClient; } getLastImageInfoFromDb(testResult: ReporterTestResultPlain, stateName?: string): ImageInfo | undefined { @@ -19,7 +19,7 @@ export class SqliteImageStore implements ImageStore { const suitePath = testResult.testPath; const suitePathString = JSON.stringify(suitePath); - const imagesInfoResult = this._sqliteAdapter.query | undefined>({ + const imagesInfoResult = this._dbClient.query | undefined>({ select: DB_COLUMNS.IMAGES_INFO, where: `${DB_COLUMNS.SUITE_PATH} = ? AND ${DB_COLUMNS.NAME} = ?`, orderBy: DB_COLUMNS.TIMESTAMP, diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts index e7b42ef22..5de0e4894 100644 --- a/lib/report-builder/gui.ts +++ b/lib/report-builder/gui.ts @@ -6,7 +6,7 @@ import { } from '../constants'; import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils'; import {ReporterTestResult} from '../test-adapter'; -import {PreparedTestResult} from '../sqlite-adapter'; +import {PreparedTestResult} from '../sqlite-client'; import {Tree, TreeImage, TreeResult} from '../tests-tree-builder/base'; import {ImageInfoWithState, ReporterConfig} from '../types'; import {hasDiff, hasNoRefImageErrors, hasResultFails, isSkippedStatus, isUpdatedStatus} from '../common-utils'; diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index 5ce2e56c6..b41ff7c60 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -14,7 +14,7 @@ import { LOCAL_DATABASE_NAME, PluginEvents } from '../constants'; -import {PreparedTestResult, SqliteAdapter} from '../sqlite-adapter'; +import {PreparedTestResult, SqliteClient} from '../sqlite-client'; import {ReporterTestResult} from '../test-adapter'; import {hasImage, saveStaticFilesToReportDir, writeDatabaseUrlsFile} from '../server-utils'; import {ReporterConfig} from '../types'; @@ -34,7 +34,7 @@ interface StaticReportBuilderOptions { export class StaticReportBuilder { protected _htmlReporter: HtmlReporter; protected _pluginConfig: ReporterConfig; - protected _sqliteAdapter: SqliteAdapter; + protected _dbClient: SqliteClient; protected _imageHandler: ImageHandler; static create( @@ -50,13 +50,13 @@ export class StaticReportBuilder { this._htmlReporter = htmlReporter; this._pluginConfig = pluginConfig; - this._sqliteAdapter = SqliteAdapter.create({ + this._dbClient = SqliteClient.create({ htmlReporter: this._htmlReporter, reportPath: this._pluginConfig.path, reuse }); - const imageStore = new SqliteImageStore(this._sqliteAdapter); + const imageStore = new SqliteImageStore(this._dbClient); this._imageHandler = new ImageHandler(imageStore, htmlReporter.imagesSaver, {reportPath: pluginConfig.path}); this._htmlReporter.on(PluginEvents.IMAGES_SAVER_UPDATED, (newImagesSaver) => { @@ -71,7 +71,7 @@ export class StaticReportBuilder { } async init(): Promise { - await this._sqliteAdapter.init(); + await this._dbClient.init(); } async saveStaticFiles(): Promise { @@ -130,7 +130,7 @@ export class StaticReportBuilder { } // To prevent skips duplication on reporter startup - const isPreviouslySkippedTest = testResult.status === SKIPPED && getTestFromDb(this._sqliteAdapter, formattedResult); + const isPreviouslySkippedTest = testResult.status === SKIPPED && getTestFromDb(this._dbClient, formattedResult); if (!ignoredStatuses.includes(testResult.status) && !isPreviouslySkippedTest) { this._writeTestResultToDb(testResult, formattedResult); @@ -171,15 +171,15 @@ export class StaticReportBuilder { const suiteName = formattedResult.state.name; const suitePath = formattedResult.testPath; - this._sqliteAdapter.write({testResult, suitePath, suiteName}); + this._dbClient.write({testResult, suitePath, suiteName}); } - protected _deleteTestResultFromDb(...args: Parameters): void { - this._sqliteAdapter.delete(...args); + protected _deleteTestResultFromDb(...args: Parameters): void { + this._dbClient.delete(...args); } async finalize(): Promise { - this._sqliteAdapter.close(); + this._dbClient.close(); const reportsSaver = this._htmlReporter.reportsSaver; diff --git a/lib/sqlite-adapter.ts b/lib/sqlite-client.ts similarity index 94% rename from lib/sqlite-adapter.ts rename to lib/sqlite-client.ts index 3e866a695..eb5f7ab8c 100644 --- a/lib/sqlite-adapter.ts +++ b/lib/sqlite-client.ts @@ -5,14 +5,13 @@ import fs from 'fs-extra'; 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 {TestStatus, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, LOCAL_DATABASE_NAME, DATABASE_URLS_JSON_NAME} from './constants'; import {createTablesQuery} from './db-utils/common'; import {DbNotInitializedError} from './errors/db-not-initialized-error'; import type {ErrorDetails, ImageInfoFull} from './types'; import {HtmlReporter} from './plugin-api'; -const debug = makeDebug('html-reporter:sqlite-adapter'); +const debug = makeDebug('html-reporter:sqlite-client'); interface QueryParams { select?: string; @@ -57,24 +56,24 @@ interface ParsedTestResult extends PreparedTestResult { suitePath: ParseTestResultParams['suitePath']; } -interface SqliteAdapterOptions { +interface SqliteClientOptions { htmlReporter: HtmlReporter; reportPath: string; reuse?: boolean; } -export class SqliteAdapter { +export class SqliteClient { private _htmlReporter: HtmlReporter; private _reportPath: string; private _reuse: boolean; private _db: null | Database.Database; private _queryCache: Map; - static create(this: new (options: SqliteAdapterOptions) => T, options: SqliteAdapterOptions): T { + static create(this: new (options: SqliteClientOptions) => T, options: SqliteClientOptions): T { return new this(options); } - constructor({htmlReporter, reportPath, reuse = false}: SqliteAdapterOptions) { + constructor({htmlReporter, reportPath, reuse = false}: SqliteClientOptions) { this._htmlReporter = htmlReporter; this._reportPath = reportPath; this._reuse = reuse; diff --git a/test/unit/hermione.js b/test/unit/hermione.js index 9c9883fa5..18320cf95 100644 --- a/test/unit/hermione.js +++ b/test/unit/hermione.js @@ -26,7 +26,7 @@ describe('lib/hermione', () => { }); const utils = _.clone(originalUtils); - const {SqliteAdapter} = proxyquire('lib/sqlite-adapter', { + const {SqliteClient} = proxyquire('lib/sqlite-client', { 'fs-extra': fs, 'better-sqlite3': sinon.stub().returns(mkSqliteDb()) }); @@ -45,7 +45,7 @@ describe('lib/hermione', () => { const {StaticReportBuilder} = proxyquire('lib/report-builder/static', { 'fs-extra': fs, '../server-utils': utils, - '../sqlite-adapter': {SqliteAdapter}, + '../sqlite-client': {SqliteClient}, '../test-adapter': {TestAdapter}, '../image-handler': {ImageHandler} }); @@ -165,8 +165,8 @@ describe('lib/hermione', () => { sandbox.stub(StaticReportBuilder.prototype, 'saveStaticFiles'); sandbox.stub(StaticReportBuilder.prototype, 'finalize'); - sandbox.stub(SqliteAdapter.prototype, 'init').resolves({}); - sandbox.stub(SqliteAdapter.prototype, 'query'); + sandbox.stub(SqliteClient.prototype, 'init').resolves({}); + sandbox.stub(SqliteClient.prototype, 'query'); sandbox.stub(fs, 'readFile').resolves(Buffer.from('')); }); diff --git a/test/unit/lib/report-builder/gui.js b/test/unit/lib/report-builder/gui.js index c6acec0aa..eb046112f 100644 --- a/test/unit/lib/report-builder/gui.js +++ b/test/unit/lib/report-builder/gui.js @@ -5,7 +5,7 @@ const _ = require('lodash'); const proxyquire = require('proxyquire'); const serverUtils = require('lib/server-utils'); const {HermioneTestAdapter} = require('lib/test-adapter'); -const {SqliteAdapter} = require('lib/sqlite-adapter'); +const {SqliteClient} = require('lib/sqlite-client'); 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'); @@ -79,7 +79,7 @@ describe('GuiReportBuilder', () => { GuiReportBuilder = proxyquire('lib/report-builder/gui', { './static': { StaticReportBuilder: proxyquire('lib/report-builder/static', { - '../sqlite-adapter': {SqliteAdapter} + '../sqlite-client': {SqliteClient} }).StaticReportBuilder }, '../server-utils': {hasImage, deleteFile} @@ -391,7 +391,7 @@ describe('GuiReportBuilder', () => { beforeEach(async () => { reportBuilder = await mkGuiReportBuilder_(); - sandbox.stub(SqliteAdapter.prototype, 'delete'); + sandbox.stub(SqliteClient.prototype, 'delete'); }); describe('if image status is not "updated"', () => { @@ -419,7 +419,7 @@ describe('GuiReportBuilder', () => { it('should not delete test from db', async () => { await tryUndoFailedImage_(); - assert.notCalled(SqliteAdapter.prototype.delete); + assert.notCalled(SqliteClient.prototype.delete); }); }); @@ -491,7 +491,7 @@ describe('GuiReportBuilder', () => { await reportBuilder.undoAcceptImage(formattedResult, stateName); assert.calledOnceWith( - SqliteAdapter.prototype.delete, + SqliteClient.prototype.delete, sinon.match.any, '["s","p"]', 'bro-name', diff --git a/test/unit/lib/sqlite-adapter.js b/test/unit/lib/sqlite-client.js similarity index 70% rename from test/unit/lib/sqlite-adapter.js rename to test/unit/lib/sqlite-client.js index 90a2866ce..a51e13785 100644 --- a/test/unit/lib/sqlite-adapter.js +++ b/test/unit/lib/sqlite-client.js @@ -4,17 +4,17 @@ const fs = require('fs-extra'); const proxyquire = require('proxyquire'); const Database = require('better-sqlite3'); -const {SqliteAdapter} = require('lib/sqlite-adapter'); +const {SqliteClient} = require('lib/sqlite-client'); const {HtmlReporter} = require('lib/plugin-api'); -describe('lib/sqlite-adapter', () => { +describe('lib/sqlite-client', () => { const sandbox = sinon.createSandbox(); let htmlReporter; - const makeSqliteAdapter_ = async () => { - const sqliteAdapter = SqliteAdapter.create({htmlReporter, reportPath: 'test'}); - await sqliteAdapter.init(); - return sqliteAdapter; + const makeSqliteClient_ = async () => { + const sqliteClient = SqliteClient.create({htmlReporter, reportPath: 'test'}); + await sqliteClient.init(); + return sqliteClient; }; beforeEach(() => { @@ -27,13 +27,13 @@ describe('lib/sqlite-adapter', () => { }); it('should create database', async () => { - await makeSqliteAdapter_(); + await makeSqliteClient_(); assert.equal(fs.existsSync('test/sqlite.db'), true); }); it('should create database with correct structure', async () => { - await makeSqliteAdapter_(); + await makeSqliteClient_(); const db = new Database('test/sqlite.db'); const tableStructure = [ {cid: 0, name: 'suitePath', type: 'TEXT'}, @@ -66,108 +66,108 @@ describe('lib/sqlite-adapter', () => { const onDatabaseCreated = sinon.spy(); htmlReporter.on(htmlReporter.events.DATABASE_CREATED, onDatabaseCreated); - await makeSqliteAdapter_(); + await makeSqliteClient_(); assert.calledOnceWith(onDatabaseCreated, sinon.match.instanceOf(Database)); }); describe('query', () => { - let getStub, prepareStub, sqliteAdapter; + let getStub, prepareStub, sqliteClient; beforeEach(async () => { getStub = sandbox.stub(); prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({get: getStub}); - sqliteAdapter = proxyquire('lib/sqlite-adapter', { + sqliteClient = proxyquire('lib/sqlite-client', { './db-utils/common': {createTablesQuery: () => []} - }).SqliteAdapter.create({htmlReporter, reportPath: 'test'}); + }).SqliteClient.create({htmlReporter, reportPath: 'test'}); - await sqliteAdapter.init(); + await sqliteClient.init(); }); describe('should create valid query string', () => { it('if called with no query params', () => { - sqliteAdapter.query(); + sqliteClient.query(); assert.calledOnceWith(prepareStub, 'SELECT * FROM suites'); }); it('if called with "select", "where", "order" and "orderDescending"', () => { - sqliteAdapter.query({select: 'foo', where: 'bar', orderBy: 'baz', orderDescending: true, limit: 42}); + sqliteClient.query({select: 'foo', where: 'bar', orderBy: 'baz', orderDescending: true, limit: 42}); assert.calledOnceWith(prepareStub, 'SELECT foo FROM suites WHERE bar ORDER BY baz DESC LIMIT 42'); }); it('if "orderDescending" is not specified', () => { - sqliteAdapter.query({select: 'foo', orderBy: 'baz'}); + sqliteClient.query({select: 'foo', orderBy: 'baz'}); assert.calledOnceWith(prepareStub, 'SELECT foo FROM suites ORDER BY baz ASC'); }); }); it('should apply query arguments', () => { - sqliteAdapter.query({}, 'foo', 'bar'); + sqliteClient.query({}, 'foo', 'bar'); assert.calledOnceWith(getStub, 'foo', 'bar'); }); it('should cache equal queries by default', () => { - sqliteAdapter.query({select: 'foo', where: 'bar'}); - sqliteAdapter.query({select: 'foo', where: 'bar'}); + sqliteClient.query({select: 'foo', where: 'bar'}); + sqliteClient.query({select: 'foo', where: 'bar'}); assert.calledOnce(getStub); }); it('should not cache queries if "noCache" is set', () => { - sqliteAdapter.query({select: 'foo', noCache: true}); - sqliteAdapter.query({select: 'foo', noCache: true}); + sqliteClient.query({select: 'foo', noCache: true}); + sqliteClient.query({select: 'foo', noCache: true}); assert.calledTwice(getStub); }); it('should not use cache for different queries', () => { - sqliteAdapter.query({select: 'foo', where: 'bar'}); - sqliteAdapter.query({select: 'foo', where: 'baz'}); + sqliteClient.query({select: 'foo', where: 'bar'}); + sqliteClient.query({select: 'foo', where: 'baz'}); assert.calledTwice(getStub); }); it('should not use cache for queries with different args', () => { - sqliteAdapter.query({select: 'foo', where: 'bar = ?'}, 'baz'); - sqliteAdapter.query({select: 'foo', where: 'bar = ?'}, 'qux'); + sqliteClient.query({select: 'foo', where: 'bar = ?'}, 'baz'); + sqliteClient.query({select: 'foo', where: 'bar = ?'}, 'qux'); assert.calledTwice(getStub); }); }); describe('delete', () => { - let runStub, prepareStub, sqliteAdapter; + let runStub, prepareStub, sqliteClient; beforeEach(async () => { runStub = sandbox.stub(); prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({run: runStub}); - sqliteAdapter = proxyquire('lib/sqlite-adapter', { + sqliteClient = proxyquire('lib/sqlite-client', { './db-utils/common': {createTablesQuery: () => []} - }).SqliteAdapter.create({htmlReporter, reportPath: 'test'}); + }).SqliteClient.create({htmlReporter, reportPath: 'test'}); - await sqliteAdapter.init(); + await sqliteClient.init(); }); describe('should create valid sentence string', () => { it('if called with no query params', () => { - sqliteAdapter.delete(); + sqliteClient.delete(); assert.calledOnceWith(prepareStub, 'DELETE FROM suites'); }); it('if called with "select", "where", "order" and "orderDescending"', () => { - sqliteAdapter.delete({where: 'bar', orderBy: 'baz', orderDescending: true, limit: 42}); + sqliteClient.delete({where: 'bar', orderBy: 'baz', orderDescending: true, limit: 42}); assert.calledOnceWith(prepareStub, 'DELETE FROM suites WHERE bar ORDER BY baz DESC LIMIT 42'); }); }); it('should apply delete arguments', () => { - sqliteAdapter.delete({}, 'foo', 'bar'); + sqliteClient.delete({}, 'foo', 'bar'); assert.calledOnceWith(runStub, 'foo', 'bar'); }); From 7f74b440cc4aa318bf0166c75cefacd5247baefb Mon Sep 17 00:00:00 2001 From: shadowusr Date: Thu, 23 Nov 2023 00:30:05 +0300 Subject: [PATCH 02/17] chore: re-write hermione.js to typescript --- hermione.js => hermione.ts | 51 +++++++++++++----------- lib/gui/tool-runner/report-subscriber.ts | 4 +- lib/plugin-adapter.ts | 16 +++++--- lib/workers/create-workers.ts | 2 + tsconfig.json | 2 +- tsconfig.spec.json | 2 +- 6 files changed, 42 insertions(+), 35 deletions(-) rename hermione.js => hermione.ts (51%) diff --git a/hermione.js b/hermione.ts similarity index 51% rename from hermione.js rename to hermione.ts index f01cfd5d8..8b58a3248 100644 --- a/hermione.js +++ b/hermione.ts @@ -1,16 +1,19 @@ -'use strict'; - -const os = require('os'); -const PQueue = require('p-queue'); -const {PluginAdapter} = require('./lib/plugin-adapter'); -const {createWorkers} = require('./lib/workers/create-workers'); -const {FAIL, SUCCESS} = require('./lib/constants'); -const {hasDiff} = require('./lib/common-utils'); -const {formatTestResult} = require('./lib/server-utils'); - -let workers; - -module.exports = (hermione, opts) => { +import os from 'os'; +import PQueue from 'p-queue'; +import Hermione, {TestResult as HermioneTestResult} from 'hermione'; + +import {PluginAdapter} from './lib/plugin-adapter'; +import {createWorkers, CreateWorkersRunner} from './lib/workers/create-workers'; +import {FAIL, SUCCESS} from './lib/constants'; +import {hasDiff} from './lib/common-utils'; +import {formatTestResult} from './lib/server-utils'; +import {ReporterConfig, ReporterOptions} from './lib/types'; +import {StaticReportBuilder} from './lib/report-builder/static'; +import {HermioneTestAdapter, ReporterTestResult} from './lib/test-adapter'; + +let workers: ReturnType; + +export = (hermione: Hermione, opts: Partial): void => { if (hermione.isWorker()) { return; } @@ -26,20 +29,20 @@ module.exports = (hermione, opts) => { .init(prepare); hermione.on(hermione.events.RUNNER_START, (runner) => { - workers = createWorkers(runner); + workers = createWorkers(runner as unknown as CreateWorkersRunner); }); }; -async function prepare(hermione, reportBuilder, pluginConfig) { +async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, pluginConfig: ReporterConfig): Promise { const {path: reportPath} = pluginConfig; const {imageHandler} = reportBuilder; - const failHandler = async (testResult) => { + const failHandler = async (testResult: HermioneTestResult): Promise => { const formattedResult = formatTestResult(testResult, FAIL, reportBuilder); - const actions = [imageHandler.saveTestImages(formattedResult, workers)]; + const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; - if (formattedResult.errorDetails) { - actions.push(formattedResult.saveErrorDetails(reportPath)); + if (pluginConfig.saveErrorDetails && formattedResult.errorDetails) { + actions.push((formattedResult as HermioneTestAdapter).saveErrorDetails(reportPath)); } await Promise.all(actions); @@ -47,15 +50,15 @@ async function prepare(hermione, reportBuilder, pluginConfig) { return formattedResult; }; - const addFail = (formattedResult) => { - return hasDiff(formattedResult.assertViewResults) + const addFail = (formattedResult: ReporterTestResult): ReporterTestResult => { + return hasDiff(formattedResult.assertViewResults as {name?: string}[]) ? reportBuilder.addFail(formattedResult) : reportBuilder.addError(formattedResult); }; return new Promise((resolve, reject) => { const queue = new PQueue({concurrency: os.cpus().length}); - const promises = []; + const promises: Promise[] = []; hermione.on(hermione.events.TEST_PASS, testResult => { promises.push(queue.add(async () => { @@ -75,11 +78,11 @@ async function prepare(hermione, reportBuilder, pluginConfig) { }); hermione.on(hermione.events.TEST_PENDING, testResult => { - promises.push(queue.add(() => failHandler(testResult).then((testResult) => reportBuilder.addSkipped(testResult)).catch(reject))); + promises.push(queue.add(() => failHandler(testResult as HermioneTestResult).then((testResult) => reportBuilder.addSkipped(testResult)).catch(reject))); }); hermione.on(hermione.events.RUNNER_END, () => { - return Promise.all(promises).then(resolve, reject); + return Promise.all(promises).then(() => resolve(), reject); }); }); } diff --git a/lib/gui/tool-runner/report-subscriber.ts b/lib/gui/tool-runner/report-subscriber.ts index 504659967..e672fadd5 100644 --- a/lib/gui/tool-runner/report-subscriber.ts +++ b/lib/gui/tool-runner/report-subscriber.ts @@ -3,7 +3,7 @@ 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 {createWorkers, CreateWorkersRunner} from '../../workers/create-workers'; import {logError, formatTestResult} from '../../server-utils'; import {hasDiff} from '../../common-utils'; import {TestStatus, RUNNING, SUCCESS, SKIPPED} from '../../constants'; @@ -15,8 +15,6 @@ 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; diff --git a/lib/plugin-adapter.ts b/lib/plugin-adapter.ts index 4c08c3e08..3f8c151d7 100644 --- a/lib/plugin-adapter.ts +++ b/lib/plugin-adapter.ts @@ -13,18 +13,18 @@ import {ToolName} from './constants'; type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise; export class PluginAdapter { - protected _hermione: Hermione & HtmlReporterApi; + protected _hermione: Hermione & Partial; protected _config: ReporterConfig; static create( - this: new (hermione: Hermione & HtmlReporterApi, opts: Partial) => T, - hermione: Hermione & HtmlReporterApi, + this: new (hermione: Hermione, opts: Partial) => T, + hermione: Hermione, opts: Partial ): T { return new this(hermione, opts); } - constructor(hermione: Hermione & HtmlReporterApi, opts: Partial) { + constructor(hermione: Hermione, opts: Partial) { this._hermione = hermione; this._config = parseConfig(opts); } @@ -56,6 +56,10 @@ export class PluginAdapter { } protected async _createStaticReportBuilder(prepareData: PrepareFn): Promise { + if (!this._hermione.htmlReporter) { + throw new Error('Html-reporter API has to be added to hermione before usage'); + } + const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config); await staticReportBuilder.init(); @@ -63,11 +67,11 @@ export class PluginAdapter { return Promise .all([ staticReportBuilder.saveStaticFiles(), - prepareData(this._hermione, staticReportBuilder, this._config) + prepareData(this._hermione as Hermione & HtmlReporterApi, staticReportBuilder, this._config) ]) .then(() => staticReportBuilder.finalize()) .then(async () => { - const htmlReporter = this._hermione.htmlReporter; + const htmlReporter = this._hermione.htmlReporter as HtmlReporter; await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: this._config.path}); }); diff --git a/lib/workers/create-workers.ts b/lib/workers/create-workers.ts index 3bf7ed593..48247c747 100644 --- a/lib/workers/create-workers.ts +++ b/lib/workers/create-workers.ts @@ -13,3 +13,5 @@ export const createWorkers = ( return runner.registerWorkers(workerFilepath, ['saveDiffTo']); }; + +export type CreateWorkersRunner = Parameters[0]; diff --git a/tsconfig.json b/tsconfig.json index 68083ee03..aded57834 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.common.json", - "include": ["lib", "hermione.js", "gemini.js", "playwright.ts"], + "include": ["lib", "hermione.ts", "gemini.js", "playwright.ts"], "exclude": ["lib/static",], "compilerOptions": { "outDir": "build", diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 375ce25db..fc10e273c 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.common.json", - "include": ["lib", "test", "hermione.js", "gemini.js", "playwright.ts"], + "include": ["lib", "test", "hermione.ts", "gemini.js", "playwright.ts"], "exclude": ["test/func"], "compilerOptions": { "baseUrl": ".", From 48130c12fbbc87f8f7b282bd0b61eba728699d44 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Fri, 24 Nov 2023 10:12:44 +0300 Subject: [PATCH 03/17] refactor: get rid of init method in sqlite-client --- lib/errors/db-not-initialized-error.ts | 5 --- lib/gui/tool-runner/index.ts | 6 ++- lib/plugin-adapter.ts | 5 ++- lib/report-builder/static.ts | 18 +++----- lib/sqlite-client.ts | 58 ++++++++------------------ playwright.ts | 53 ++++++++++++++--------- 6 files changed, 63 insertions(+), 82 deletions(-) delete mode 100644 lib/errors/db-not-initialized-error.ts diff --git a/lib/errors/db-not-initialized-error.ts b/lib/errors/db-not-initialized-error.ts deleted file mode 100644 index a27a42c1a..000000000 --- a/lib/errors/db-not-initialized-error.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class DbNotInitializedError extends Error { - constructor() { - super('Database must be initialized before use'); - } -} diff --git a/lib/gui/tool-runner/index.ts b/lib/gui/tool-runner/index.ts index 6769e3e65..3c5de011f 100644 --- a/lib/gui/tool-runner/index.ts +++ b/lib/gui/tool-runner/index.ts @@ -33,6 +33,7 @@ import {Response} from 'express'; import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../../tests-tree-builder/gui'; import {ReporterTestResult} from '../../test-adapter'; import {ImagesInfoFormatter} from '../../image-handler'; +import {SqliteClient} from '../../sqlite-client'; type ToolRunnerArgs = [paths: string[], hermione: Hermione & HtmlReporterApi, configs: GuiConfigs]; @@ -101,12 +102,13 @@ export class ToolRunner { async initialize(): Promise { await mergeDatabasesForReuse(this._reportPath); - this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {reuse: true}); + const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._reportPath, reuse: true}); + + this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient}); this._subscribeOnEvents(); this._collection = await this._readTests(); - await this._reportBuilder.init(); await this._reportBuilder.saveStaticFiles(); this._reportBuilder.setApiValues(this._hermione.htmlReporter.values); diff --git a/lib/plugin-adapter.ts b/lib/plugin-adapter.ts index 3f8c151d7..3e13c6006 100644 --- a/lib/plugin-adapter.ts +++ b/lib/plugin-adapter.ts @@ -9,6 +9,7 @@ import {cliCommands} from './cli-commands'; import {HtmlReporter} from './plugin-api'; import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './types'; import {ToolName} from './constants'; +import {SqliteClient} from './sqlite-client'; type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise; @@ -60,9 +61,9 @@ export class PluginAdapter { throw new Error('Html-reporter API has to be added to hermione before usage'); } - const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config); + const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._config.path}); - await staticReportBuilder.init(); + const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient}); return Promise .all([ diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index b41ff7c60..172e774ca 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -28,7 +28,7 @@ import {ImageDiffError} from '../errors'; const ignoredStatuses = [RUNNING, IDLE]; interface StaticReportBuilderOptions { - reuse: boolean; + dbClient: SqliteClient; } export class StaticReportBuilder { @@ -38,23 +38,19 @@ export class StaticReportBuilder { protected _imageHandler: ImageHandler; static create( - this: new (htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, options?: Partial) => T, + this: new (htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, options: StaticReportBuilderOptions) => T, htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, - options?: Partial + options: StaticReportBuilderOptions ): T { return new this(htmlReporter, pluginConfig, options); } - constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {reuse = false}: Partial = {}) { + constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient}: StaticReportBuilderOptions) { this._htmlReporter = htmlReporter; this._pluginConfig = pluginConfig; - this._dbClient = SqliteClient.create({ - htmlReporter: this._htmlReporter, - reportPath: this._pluginConfig.path, - reuse - }); + this._dbClient = dbClient; const imageStore = new SqliteImageStore(this._dbClient); this._imageHandler = new ImageHandler(imageStore, htmlReporter.imagesSaver, {reportPath: pluginConfig.path}); @@ -70,10 +66,6 @@ export class StaticReportBuilder { return this._imageHandler; } - async init(): Promise { - await this._dbClient.init(); - } - async saveStaticFiles(): Promise { const destPath = this._pluginConfig.path; diff --git a/lib/sqlite-client.ts b/lib/sqlite-client.ts index eb5f7ab8c..a0dfe2eb9 100644 --- a/lib/sqlite-client.ts +++ b/lib/sqlite-client.ts @@ -7,7 +7,6 @@ import NestedError from 'nested-error-stacks'; import {getShortMD5} from './common-utils'; import {TestStatus, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, LOCAL_DATABASE_NAME, DATABASE_URLS_JSON_NAME} from './constants'; import {createTablesQuery} from './db-utils/common'; -import {DbNotInitializedError} from './errors/db-not-initialized-error'; import type {ErrorDetails, ImageInfoFull} from './types'; import {HtmlReporter} from './plugin-api'; @@ -63,54 +62,45 @@ interface SqliteClientOptions { } export class SqliteClient { - private _htmlReporter: HtmlReporter; - private _reportPath: string; - private _reuse: boolean; - private _db: null | Database.Database; + private readonly _db: Database.Database; private _queryCache: Map; - static create(this: new (options: SqliteClientOptions) => T, options: SqliteClientOptions): T { - return new this(options); - } - - constructor({htmlReporter, reportPath, reuse = false}: SqliteClientOptions) { - this._htmlReporter = htmlReporter; - this._reportPath = reportPath; - this._reuse = reuse; - this._db = null; - this._queryCache = new Map(); - } + static async create(this: { new(db: Database.Database): T }, options: SqliteClientOptions): Promise { + const {htmlReporter, reportPath} = options; + const dbPath = path.resolve(reportPath, LOCAL_DATABASE_NAME); + const dbUrlsJsonPath = path.resolve(reportPath, DATABASE_URLS_JSON_NAME); - async init(): Promise { - const dbPath = path.resolve(this._reportPath, LOCAL_DATABASE_NAME); - const dbUrlsJsonPath = path.resolve(this._reportPath, DATABASE_URLS_JSON_NAME); + let db: Database.Database; try { - if (!this._reuse) { + if (!options.reuse) { await Promise.all([ fs.remove(dbPath), fs.remove(dbUrlsJsonPath) ]); } - await fs.ensureDir(this._reportPath); + await fs.ensureDir(reportPath); - this._db = new Database(dbPath); + db = new Database(dbPath); debug('db connection opened'); - createTablesQuery().forEach((query) => this._db?.prepare(query).run()); + createTablesQuery().forEach((query) => db?.prepare(query).run()); - this._htmlReporter.emit(this._htmlReporter.events.DATABASE_CREATED, this._db); + htmlReporter.emit(htmlReporter.events.DATABASE_CREATED, db); } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any throw new NestedError(`Error creating database at "${dbPath}"`, err); } + + return new this(db); } - close(): void { - if (!this._db) { - throw new DbNotInitializedError(); - } + constructor(db: Database.Database) { + this._db = db; + this._queryCache = new Map(); + } + close(): void { this._db.prepare('VACUUM').run(); debug('db connection closed'); @@ -118,10 +108,6 @@ export class SqliteClient { } query(queryParams: QueryParams = {}, ...queryArgs: string[]): T { - if (!this._db) { - throw new DbNotInitializedError(); - } - const {select, where, orderBy, orderDescending, limit, noCache = false} = queryParams; const cacheKey = (!noCache && getShortMD5(`${select}#${where}#${orderBy}${orderDescending}${queryArgs.join('#')}`)) as string; @@ -142,10 +128,6 @@ export class SqliteClient { } write(testResult: ParseTestResultParams): void { - if (!this._db) { - throw new DbNotInitializedError(); - } - const testResultObj = this._parseTestResult(testResult); const values = this._createValuesArray(testResultObj); @@ -154,10 +136,6 @@ export class SqliteClient { } delete(deleteParams: DeleteParams = {}, ...deleteArgs: string[]): void { - if (!this._db) { - throw new DbNotInitializedError(); - } - const sentence = `DELETE FROM ${DB_SUITES_TABLE_NAME}` + this._createSentence(deleteParams); diff --git a/playwright.ts b/playwright.ts index bc222336d..4946b2fdc 100644 --- a/playwright.ts +++ b/playwright.ts @@ -12,21 +12,23 @@ import {RegisterWorkers} from './lib/workers/create-workers'; import {EventEmitter} from 'events'; import {PlaywrightTestAdapter, getStatus} from './lib/test-adapter/playwright'; import PQueue from 'p-queue'; +import {SqliteClient} from './lib/sqlite-client'; export {ReporterConfig} from './lib/types'; class MyReporter implements Reporter { protected _promiseQueue: PQueue = new PQueue(); - protected _staticReportBuilder: StaticReportBuilder; + protected _staticReportBuilder: StaticReportBuilder | null; protected _htmlReporter: HtmlReporter; protected _config: ReporterConfig; protected _workerFarm: Workers; protected _workers: RegisterWorkers<['saveDiffTo']>; + protected _initPromise: Promise; constructor(opts: Partial) { this._config = parseConfig(_.omit(opts, ['configDir'])); this._htmlReporter = HtmlReporter.create(this._config, {toolName: ToolName.Playwright}); - this._staticReportBuilder = StaticReportBuilder.create(this._htmlReporter, this._config); + this._staticReportBuilder = null; this._workerFarm = workerFarm(require.resolve('./lib/workers/worker'), ['saveDiffTo']); const workers: RegisterWorkers<['saveDiffTo']> = new EventEmitter() as RegisterWorkers<['saveDiffTo']>; @@ -34,33 +36,44 @@ class MyReporter implements Reporter { promisify(this._workerFarm.saveDiffTo)(imageDiffError, diffPath); this._workers = workers; - this._promiseQueue.add(() => this._staticReportBuilder.init() - .then(() => this._staticReportBuilder.saveStaticFiles()) - ); + this._initPromise = (async (htmlReporter: HtmlReporter, config: ReporterConfig): Promise => { + const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); + + this._staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient}); + await this._staticReportBuilder.saveStaticFiles(); + })(this._htmlReporter, this._config); + + this._promiseQueue.add(async () => this._initPromise); } onTestEnd(test: TestCase, result: PwtTestResult): void { - const status = getStatus(result); - const formattedResult = new PlaywrightTestAdapter(test, result, {imagesInfoFormatter: this._staticReportBuilder.imageHandler}); - - if (status === TestStatus.FAIL) { - if (formattedResult.status === TestStatus.FAIL) { - this._staticReportBuilder.addFail(formattedResult); - } else { - this._staticReportBuilder.addError(formattedResult); + this._promiseQueue.add(async () => { + await this._initPromise; + + const staticReportBuilder = this._staticReportBuilder as StaticReportBuilder; + + const status = getStatus(result); + const formattedResult = new PlaywrightTestAdapter(test, result, {imagesInfoFormatter: staticReportBuilder.imageHandler}); + + if (status === TestStatus.FAIL) { + if (formattedResult.status === TestStatus.FAIL) { + staticReportBuilder.addFail(formattedResult); + } else { + staticReportBuilder.addError(formattedResult); + } + } else if (status === TestStatus.SUCCESS) { + staticReportBuilder.addSuccess(formattedResult); + } else if (status === TestStatus.SKIPPED) { + staticReportBuilder.addSkipped(formattedResult); } - } else if (status === TestStatus.SUCCESS) { - this._staticReportBuilder.addSuccess(formattedResult); - } else if (status === TestStatus.SKIPPED) { - this._staticReportBuilder.addSkipped(formattedResult); - } - this._promiseQueue.add(() => this._staticReportBuilder.imageHandler.saveTestImages(formattedResult, this._workers)); + await staticReportBuilder.imageHandler.saveTestImages(formattedResult, this._workers); + }); } async onEnd(): Promise { await this._promiseQueue.onIdle(); - await this._staticReportBuilder.finalize(); + await this._staticReportBuilder?.finalize(); await this._htmlReporter.emitAsync(PluginEvents.REPORT_SAVED); } From 0e019c7dc67cdf516a763ca978979763e135dd5d Mon Sep 17 00:00:00 2001 From: shadowusr Date: Fri, 24 Nov 2023 12:25:02 +0300 Subject: [PATCH 04/17] refactor: streamline test result types in sqlite-client --- lib/report-builder/static.ts | 9 +-- lib/sqlite-client.ts | 86 ++++++++++++----------------- lib/test-adapter/transformers/db.ts | 44 +++++++++++++++ 3 files changed, 81 insertions(+), 58 deletions(-) create mode 100644 lib/test-adapter/transformers/db.ts diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index 172e774ca..7d0f00145 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -125,7 +125,7 @@ export class StaticReportBuilder { const isPreviouslySkippedTest = testResult.status === SKIPPED && getTestFromDb(this._dbClient, formattedResult); if (!ignoredStatuses.includes(testResult.status) && !isPreviouslySkippedTest) { - this._writeTestResultToDb(testResult, formattedResult); + this._dbClient.write(formattedResult); } return formattedResult; @@ -159,13 +159,6 @@ export class StaticReportBuilder { return testResult; } - protected _writeTestResultToDb(testResult: PreparedTestResult, formattedResult: ReporterTestResult): void { - const suiteName = formattedResult.state.name; - const suitePath = formattedResult.testPath; - - this._dbClient.write({testResult, suitePath, suiteName}); - } - protected _deleteTestResultFromDb(...args: Parameters): void { this._dbClient.delete(...args); } diff --git a/lib/sqlite-client.ts b/lib/sqlite-client.ts index a0dfe2eb9..e11e18176 100644 --- a/lib/sqlite-client.ts +++ b/lib/sqlite-client.ts @@ -7,8 +7,10 @@ import NestedError from 'nested-error-stacks'; import {getShortMD5} from './common-utils'; import {TestStatus, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, LOCAL_DATABASE_NAME, DATABASE_URLS_JSON_NAME} from './constants'; import {createTablesQuery} from './db-utils/common'; -import type {ErrorDetails, ImageInfoFull} from './types'; +import type {ErrorDetails, ImageInfoFull, TestError} from './types'; import {HtmlReporter} from './plugin-api'; +import {ReporterTestResult} from './test-adapter'; +import {DbTestResultTransformer} from './test-adapter/transformers/db'; const debug = makeDebug('html-reporter:sqlite-client'); @@ -44,18 +46,29 @@ export interface PreparedTestResult { errorDetails?: ErrorDetails; } -interface ParseTestResultParams { +export interface DbTestResult { + description?: string | null; + error?: TestError; + history: string[]; + imagesInfo: ImageInfoFull[]; + metaInfo: Record; + multipleTabs: boolean; + /* Browser name. Example: `"chrome"` */ + name: string; + screenshot: boolean; + skipReason?: string; + status: TestStatus; + /* Last part of `suitePath`. Example: `"Test 1"` */ suiteName: string; + /* Segments of full test name. Example: `["Title", "Test 1"]` */ suitePath: string[]; - testResult: PreparedTestResult; -} - -interface ParsedTestResult extends PreparedTestResult { - suiteName: ParseTestResultParams['suiteName']; - suitePath: ParseTestResultParams['suitePath']; + suiteUrl: string; + /* Unix time in ms. Example: `1700563430266` */ + timestamp: number; } interface SqliteClientOptions { + // TODO: get rid of htmlReporter in the future htmlReporter: HtmlReporter; reportPath: string; reuse?: boolean; @@ -64,8 +77,12 @@ interface SqliteClientOptions { export class SqliteClient { private readonly _db: Database.Database; private _queryCache: Map; + private _transformer: DbTestResultTransformer; - static async create(this: { new(db: Database.Database): T }, options: SqliteClientOptions): Promise { + static async create( + this: { new(db: Database.Database, transformer: DbTestResultTransformer): T }, + options: SqliteClientOptions + ): Promise { const {htmlReporter, reportPath} = options; const dbPath = path.resolve(reportPath, LOCAL_DATABASE_NAME); const dbUrlsJsonPath = path.resolve(reportPath, DATABASE_URLS_JSON_NAME); @@ -92,12 +109,15 @@ export class SqliteClient { throw new NestedError(`Error creating database at "${dbPath}"`, err); } - return new this(db); + const transformer = new DbTestResultTransformer({baseHost: htmlReporter.config.baseHost}); + + return new this(db, transformer); } - constructor(db: Database.Database) { + constructor(db: Database.Database, transformer: DbTestResultTransformer) { this._db = db; this._queryCache = new Map(); + this._transformer = transformer; } close(): void { @@ -127,9 +147,9 @@ export class SqliteClient { return result as T; } - write(testResult: ParseTestResultParams): void { - const testResultObj = this._parseTestResult(testResult); - const values = this._createValuesArray(testResultObj); + write(testResult: ReporterTestResult): void { + const dbTestResult = this._transformer.transform(testResult); + const values = this._createValuesArray(dbTestResult); const placeholders = values.map(() => '?').join(', '); this._db.prepare(`INSERT INTO ${DB_SUITES_TABLE_NAME} VALUES (${placeholders})`).run(...values); @@ -160,43 +180,9 @@ export class SqliteClient { return sentence; } - _parseTestResult({suitePath, suiteName, testResult}: ParseTestResultParams): ParsedTestResult { - const { - name, - suiteUrl, - metaInfo, - history, - description, - error, - skipReason, - imagesInfo, - screenshot, - multipleTabs, - status, - timestamp = Date.now() - } = testResult; - - return { - suitePath, - suiteName, - name, - suiteUrl, - metaInfo, - history, - description, - error, - skipReason, - imagesInfo, - screenshot, - multipleTabs, - status, - timestamp - }; - } - - private _createValuesArray(testResult: PreparedTestResult): (string | number | null)[] { + private _createValuesArray(testResult: DbTestResult): (string | number | null)[] { return SUITES_TABLE_COLUMNS.reduce<(string | number | null)[]>((acc, {name}) => { - const value = testResult[name as keyof PreparedTestResult]; + const value = testResult[name as keyof DbTestResult]; if (value === undefined || value === null) { acc.push(null); return acc; diff --git a/lib/test-adapter/transformers/db.ts b/lib/test-adapter/transformers/db.ts new file mode 100644 index 000000000..8ae83ca1c --- /dev/null +++ b/lib/test-adapter/transformers/db.ts @@ -0,0 +1,44 @@ +import {ReporterTestResult} from '../index'; +import {DbTestResult} from '../../sqlite-client'; +import {getError, getRelativeUrl, getUrlWithBase} from '../../common-utils'; +import _ from 'lodash'; + +interface Options { + baseHost?: string; +} + +export class DbTestResultTransformer { + private _options: Options; + + constructor(options: Options) { + this._options = options; + } + + transform(testResult: ReporterTestResult): DbTestResult { + const suiteUrl = getUrlWithBase(testResult.url, this._options.baseHost); + + const metaInfoFull = _.merge(_.cloneDeep(testResult.meta), { + url: getRelativeUrl(suiteUrl) ?? '', + file: testResult.file, + sessionId: testResult.sessionId + }); + const metaInfo = _.omitBy(metaInfoFull, _.isEmpty); + + return { + suitePath: testResult.testPath, + suiteName: _.last(testResult.testPath) as string, + name: testResult.browserId, + suiteUrl, + metaInfo, + history: testResult.history, + description: testResult.description, + error: getError(testResult.error), + skipReason: testResult.skipReason, + imagesInfo: testResult.imagesInfo ?? [], + screenshot: Boolean(testResult.screenshot), + multipleTabs: testResult.multipleTabs, + status: testResult.status, + timestamp: testResult.timestamp ?? Date.now() + }; + } +} From 3f4fbed861d8b83022b80be7541f8e5eb8f465cd Mon Sep 17 00:00:00 2001 From: shadowusr Date: Mon, 27 Nov 2023 02:28:37 +0300 Subject: [PATCH 05/17] refactor: handle attempt number explicitly --- hermione.ts | 7 ++- lib/gui/tool-runner/index.ts | 38 ++++++++----- lib/gui/tool-runner/report-subscriber.ts | 24 ++++---- lib/plugin-adapter.ts | 4 +- lib/report-builder/gui.ts | 43 +++++--------- lib/report-builder/static.ts | 13 ++++- lib/server-utils.ts | 9 ++- lib/test-adapter/hermione.ts | 20 ++----- lib/test-adapter/index.ts | 2 +- lib/test-adapter/utils/index.ts | 5 ++ lib/test-attempt-manager.ts | 71 ++++++++++++++++++++++++ lib/tests-tree-builder/gui.ts | 30 ---------- playwright.ts | 4 +- 13 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 lib/test-adapter/utils/index.ts create mode 100644 lib/test-attempt-manager.ts diff --git a/hermione.ts b/hermione.ts index 8b58a3248..e6515c85e 100644 --- a/hermione.ts +++ b/hermione.ts @@ -38,7 +38,9 @@ async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, p const {imageHandler} = reportBuilder; const failHandler = async (testResult: HermioneTestResult): Promise => { - const formattedResult = formatTestResult(testResult, FAIL, reportBuilder); + const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); + const formattedResult = formatTestResult(testResult, FAIL, attempt, reportBuilder); + const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; if (pluginConfig.saveErrorDetails && formattedResult.errorDetails) { @@ -62,7 +64,8 @@ async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, p hermione.on(hermione.events.TEST_PASS, testResult => { promises.push(queue.add(async () => { - const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder); + const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); + const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder); await imageHandler.saveTestImages(formattedResult, workers); return reportBuilder.addSuccess(formattedResult); diff --git a/lib/gui/tool-runner/index.ts b/lib/gui/tool-runner/index.ts index 3c5de011f..fe7cd7b58 100644 --- a/lib/gui/tool-runner/index.ts +++ b/lib/gui/tool-runner/index.ts @@ -34,6 +34,7 @@ import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../../tests-tre import {ReporterTestResult} from '../../test-adapter'; import {ImagesInfoFormatter} from '../../image-handler'; import {SqliteClient} from '../../sqlite-client'; +import {TestAttemptManager} from '../../test-attempt-manager'; type ToolRunnerArgs = [paths: string[], hermione: Hermione & HtmlReporterApi, configs: GuiConfigs]; @@ -53,8 +54,13 @@ export interface UndoAcceptImagesResult { } // TODO: get rid of this function. It allows to format raw test, but is type-unsafe. -const formatTestResultUnsafe = (test: HermioneTest | HermioneTestExtended | HermioneTestPlain, status: TestStatus, {imageHandler}: {imageHandler: ImagesInfoFormatter}): ReporterTestResult => { - return formatTestResult(test as HermioneTestResult, status, {imageHandler}); +const formatTestResultUnsafe = ( + test: HermioneTest | HermioneTestExtended | HermioneTestPlain, + status: TestStatus, + attempt: number, + {imageHandler}: {imageHandler: ImagesInfoFormatter} +): ReporterTestResult => { + return formatTestResult(test as HermioneTestResult, status, attempt, {imageHandler}); }; export class ToolRunner { @@ -103,8 +109,9 @@ export class ToolRunner { await mergeDatabasesForReuse(this._reportPath); const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._reportPath, reuse: true}); + const testAttemptManager = new TestAttemptManager(); - this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient}); + this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient, testAttemptManager}); this._subscribeOnEvents(); this._collection = await this._readTests(); @@ -167,11 +174,12 @@ export class ToolRunner { return Promise.all(tests.map(async (test): Promise => { const updateResult = this._prepareTestResult(test); - const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, reportBuilder); + + const fullName = test.suite.path.join(' '); + const updateAttempt = reportBuilder.testAttemptManager.registerAttempt({fullName, browserId: test.browserId}, UPDATED); + const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt, reportBuilder); const failResultId = formattedResult.id; - const updateAttempt = reportBuilder.getUpdatedAttempt(formattedResult); - formattedResult.attempt = updateAttempt; updateResult.attempt = updateAttempt; await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => { @@ -183,7 +191,7 @@ export class ToolRunner { this._emitUpdateReference(result, stateName); })); - reportBuilder.addUpdated(formatTestResultUnsafe(updateResult, UPDATED, reportBuilder), failResultId); + reportBuilder.addUpdated(formattedResult, failResultId); return reportBuilder.getTestBranch(formattedResult.id); })); @@ -195,9 +203,9 @@ export class ToolRunner { await Promise.all(tests.map(async (test) => { const updateResult = this._prepareTestResult(test); - const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, reportBuilder); - - formattedResult.attempt = updateResult.attempt; + const fullName = test.suite.path.join(' '); + const attempt = reportBuilder.testAttemptManager.removeAttempt({fullName, browserId: test.browserId}); + const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, attempt, reportBuilder); await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => { const {stateName} = imageInfo; @@ -300,9 +308,13 @@ export class ToolRunner { const testId = formatId(test.id.toString(), browserId); this._tests[testId] = _.extend(test, {browserId}); - test.pending - ? reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, reportBuilder)) - : reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, reportBuilder)); + if (test.pending) { + const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, SKIPPED); + reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, attempt, reportBuilder)); + } else { + const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, IDLE); + reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, attempt, reportBuilder)); + } }); await this._fillTestsTree(); diff --git a/lib/gui/tool-runner/report-subscriber.ts b/lib/gui/tool-runner/report-subscriber.ts index e672fadd5..344bb6982 100644 --- a/lib/gui/tool-runner/report-subscriber.ts +++ b/lib/gui/tool-runner/report-subscriber.ts @@ -17,7 +17,7 @@ let workers: ReturnType; export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiReportBuilder, client: EventSource, reportPath: string): void => { const queue = new PQueue({concurrency: os.cpus().length}); - const {imageHandler} = reportBuilder; + const {imageHandler, testAttemptManager} = reportBuilder; async function failHandler(formattedResult: ReporterTestResult): Promise { const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; @@ -45,8 +45,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo }); hermione.on(hermione.events.TEST_BEGIN, (data) => { - const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, reportBuilder); - formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); + const attempt = testAttemptManager.registerAttempt({fullName: data.fullTitle(), browserId: data.browserId}, RUNNING); + const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, attempt, reportBuilder); reportBuilder.addRunning(formattedResult); const testBranch = reportBuilder.getTestBranch(formattedResult.id); @@ -56,8 +56,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.TEST_PASS, (testResult) => { queue.add(async () => { - const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder); - formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); + const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SUCCESS); + const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder); await imageHandler.saveTestImages(formattedResult, workers); reportBuilder.addSuccess(formattedResult); @@ -70,8 +70,9 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.RETRY, (testResult) => { queue.add(async () => { const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; - const formattedResult = formatTestResult(testResult, status, reportBuilder); - formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); + const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); + + const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); await failHandler(formattedResult); reportBuilder.addRetry(formattedResult); @@ -84,8 +85,9 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.TEST_FAIL, (testResult) => { queue.add(async () => { const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; - const formattedResult = formatTestResult(testResult, status, reportBuilder); - formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); + const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); + + const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); await failHandler(formattedResult); status === TestStatus.FAIL @@ -99,8 +101,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.TEST_PENDING, async (testResult) => { queue.add(async () => { - const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, reportBuilder); - formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult); + const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SKIPPED); + const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, attempt, reportBuilder); await failHandler(formattedResult); reportBuilder.addSkipped(formattedResult); diff --git a/lib/plugin-adapter.ts b/lib/plugin-adapter.ts index 3e13c6006..dbd29b3ab 100644 --- a/lib/plugin-adapter.ts +++ b/lib/plugin-adapter.ts @@ -10,6 +10,7 @@ import {HtmlReporter} from './plugin-api'; import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './types'; import {ToolName} from './constants'; import {SqliteClient} from './sqlite-client'; +import {TestAttemptManager} from './test-attempt-manager'; type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise; @@ -62,8 +63,9 @@ export class PluginAdapter { } const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._config.path}); + const testAttemptManager = new TestAttemptManager(); - const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient}); + const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient, testAttemptManager}); return Promise .all([ diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts index 5de0e4894..e6b527441 100644 --- a/lib/report-builder/gui.ts +++ b/lib/report-builder/gui.ts @@ -2,7 +2,7 @@ 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 + IDLE, RUNNING, FAIL, SUCCESS, UPDATED, TestStatus, DB_COLUMNS, ToolName, ERROR } from '../constants'; import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils'; import {ReporterTestResult} from '../test-adapter'; @@ -12,6 +12,7 @@ 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'; +import {copyAndUpdate} from '../test-adapter/utils'; interface UndoAcceptImageResult { updatedImage: TreeImage | undefined; @@ -19,6 +20,7 @@ interface UndoAcceptImageResult { previousExpectedPath: string | null; shouldRemoveReference: boolean; shouldRevertReference: boolean; + newTestResult: ReporterTestResult; } export interface GuiReportBuilderResult { @@ -102,31 +104,10 @@ export class GuiReportBuilder extends StaticReportBuilder { 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; + undoAcceptImage(testResult: ReporterTestResult, stateName: string): UndoAcceptImageResult | null { + const resultId = testResult.id; + const suitePath = testResult.testPath; + const browserName = testResult.browserId; const resultData = this._testsTree.getResultDataToUnacceptImage(resultId, stateName); if (!resultData || !isUpdatedStatus(resultData.status)) { @@ -146,15 +127,19 @@ export class GuiReportBuilder extends StaticReportBuilder { const shouldRemoveReference = _.isNull(previousImageRefImgSize); const shouldRevertReference = !shouldRemoveReference; - let updatedImage, removedResult; + let updatedImage: TreeImage | undefined, removedResult: string | undefined, newTestResult: ReporterTestResult; if (shouldRemoveResult) { this._testsTree.removeTestResult(resultId); - formattedResult.attempt = formattedResult.attempt - 1; + this._testAttemptManager.removeAttempt(testResult); + + newTestResult = copyAndUpdate(testResult, {attempt: this._testAttemptManager.getCurrentAttempt(testResult)}); removedResult = resultId; } else { updatedImage = this._testsTree.updateImageInfo(imageId, previousImage); + + newTestResult = testResult; } this._deleteTestResultFromDb({where: [ @@ -165,7 +150,7 @@ export class GuiReportBuilder extends StaticReportBuilder { `json_extract(${DB_COLUMNS.IMAGES_INFO}, '$[0].stateName') = ?` ].join(' AND ')}, JSON.stringify(suitePath), browserName, status, timestamp.toString(), stateName); - return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference}; + return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newTestResult}; } protected override _addTestResult(formattedResult: ReporterTestResult, props: {status: TestStatus} & Partial, opts: {failResultId?: string} = {}): ReporterTestResult { diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index 7d0f00145..dd2c92a7a 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -24,11 +24,13 @@ import {SqliteImageStore} from '../image-store'; import {getUrlWithBase, getError, getRelativeUrl, hasDiff, hasNoRefImageErrors} from '../common-utils'; import {getTestFromDb} from '../db-utils/server'; import {ImageDiffError} from '../errors'; +import {TestAttemptManager} from '../test-attempt-manager'; const ignoredStatuses = [RUNNING, IDLE]; interface StaticReportBuilderOptions { dbClient: SqliteClient; + testAttemptManager: TestAttemptManager; } export class StaticReportBuilder { @@ -36,6 +38,7 @@ export class StaticReportBuilder { protected _pluginConfig: ReporterConfig; protected _dbClient: SqliteClient; protected _imageHandler: ImageHandler; + protected _testAttemptManager: TestAttemptManager; static create( this: new (htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, options: StaticReportBuilderOptions) => T, @@ -46,12 +49,14 @@ export class StaticReportBuilder { return new this(htmlReporter, pluginConfig, options); } - constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient}: StaticReportBuilderOptions) { + constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient, testAttemptManager}: StaticReportBuilderOptions) { this._htmlReporter = htmlReporter; this._pluginConfig = pluginConfig; this._dbClient = dbClient; + this._testAttemptManager = testAttemptManager; + const imageStore = new SqliteImageStore(this._dbClient); this._imageHandler = new ImageHandler(imageStore, htmlReporter.imagesSaver, {reportPath: pluginConfig.path}); @@ -66,6 +71,10 @@ export class StaticReportBuilder { return this._imageHandler; } + get testAttemptManager(): TestAttemptManager { + return this._testAttemptManager; + } + async saveStaticFiles(): Promise { const destPath = this._pluginConfig.path; @@ -131,7 +140,7 @@ export class StaticReportBuilder { return formattedResult; } - protected _createTestResult(result: ReporterTestResult, props: {attempt?: number, status: TestStatus, timestamp: number;} & Partial): PreparedTestResult { + protected _createTestResult(result: ReporterTestResult, props: {attempt?: number | null, status: TestStatus, timestamp: number;} & Partial): PreparedTestResult { const { browserId, file, sessionId, description, history, imagesInfo = [], screenshot, multipleTabs, errorDetails diff --git a/lib/server-utils.ts b/lib/server-utils.ts index cf60419a2..ccd28fab3 100644 --- a/lib/server-utils.ts +++ b/lib/server-utils.ts @@ -311,6 +311,11 @@ export function mapPlugins(plugins: ReporterConfig['plugins'], callback: (nam return result; } -export const formatTestResult = (rawResult: HermioneTestResult, status: TestStatus, {imageHandler}: {imageHandler: ImagesInfoFormatter}): ReporterTestResult => { - return new HermioneTestAdapter(rawResult, {status, imagesInfoFormatter: imageHandler}); +export const formatTestResult = ( + rawResult: HermioneTestResult, + status: TestStatus, + attempt: number, + {imageHandler}: {imageHandler: ImagesInfoFormatter} +): ReporterTestResult => { + return new HermioneTestAdapter(rawResult, {attempt, status, imagesInfoFormatter: imageHandler}); }; diff --git a/lib/test-adapter/hermione.ts b/lib/test-adapter/hermione.ts index b7f93fa5a..829fea3d4 100644 --- a/lib/test-adapter/hermione.ts +++ b/lib/test-adapter/hermione.ts @@ -4,7 +4,7 @@ import path from 'path'; import {getCommandsHistory} from '../history-utils'; import {ERROR_DETAILS_PATH, TestStatus} from '../constants'; -import {mkTestId, wrapLinkByTag} from '../common-utils'; +import {wrapLinkByTag} from '../common-utils'; import * as utils from '../server-utils'; import { AssertViewResult, @@ -16,7 +16,7 @@ import { import {ImagesInfoFormatter} from '../image-handler'; import {ReporterTestResult} from './index'; import {getSuitePath} from '../plugin-utils'; -import {testsAttempts} from './cache/hermione'; +// import {testsAttempts} from './cache/hermione'; const getSkipComment = (suite: HermioneTestResult | HermioneSuite): string | null | undefined => { return suite.skipReason || suite.parent && getSkipComment(suite.parent); @@ -27,6 +27,7 @@ const wrapSkipComment = (skipComment: string | null | undefined): string => { }; export interface HermioneTestAdapterOptions { + attempt: number; status: TestStatus; imagesInfoFormatter: ImagesInfoFormatter; } @@ -34,7 +35,6 @@ export interface HermioneTestAdapterOptions { export class HermioneTestAdapter implements ReporterTestResult { private _imagesInfoFormatter: ImagesInfoFormatter; private _testResult: HermioneTestResult; - private _testId: string; private _errorDetails: ErrorDetails | null; private _timestamp: number | undefined; private _attempt: number; @@ -44,10 +44,9 @@ export class HermioneTestAdapter implements ReporterTestResult { return new this(testResult, options); } - constructor(testResult: HermioneTestResult, {status, imagesInfoFormatter}: HermioneTestAdapterOptions) { + constructor(testResult: HermioneTestResult, {attempt, status, imagesInfoFormatter}: HermioneTestAdapterOptions) { this._imagesInfoFormatter = imagesInfoFormatter; this._testResult = testResult; - this._testId = mkTestId(testResult.fullTitle(), testResult.browserId); this._errorDetails = null; this._timestamp = this._testResult.timestamp ?? this._testResult.startTime ?? Date.now(); this._status = status; @@ -56,11 +55,7 @@ export class HermioneTestAdapter implements ReporterTestResult { _.set(this._testResult, 'meta.browserVersion', browserVersion); - if (utils.shouldUpdateAttempt(status)) { - testsAttempts.set(this._testId, _.isUndefined(testsAttempts.get(this._testId)) ? 0 : testsAttempts.get(this._testId) as number + 1); - } - - this._attempt = testsAttempts.get(this._testId) || 0; + this._attempt = attempt; } image?: boolean; @@ -93,11 +88,6 @@ export class HermioneTestAdapter implements ReporterTestResult { return this._attempt; } - set attempt(attemptNum: number) { - testsAttempts.set(this._testId, attemptNum); - this._attempt = attemptNum; - } - get assertViewResults(): AssertViewResult[] { return this._testResult.assertViewResults || []; } diff --git a/lib/test-adapter/index.ts b/lib/test-adapter/index.ts index 9ea46ce21..71378e66a 100644 --- a/lib/test-adapter/index.ts +++ b/lib/test-adapter/index.ts @@ -5,7 +5,7 @@ export * from './hermione'; export interface ReporterTestResult { readonly assertViewResults: AssertViewResult[]; - attempt: number; + readonly attempt: number; readonly browserId: string; readonly description: string | undefined; error: undefined | TestError; diff --git a/lib/test-adapter/utils/index.ts b/lib/test-adapter/utils/index.ts new file mode 100644 index 000000000..58e91efa7 --- /dev/null +++ b/lib/test-adapter/utils/index.ts @@ -0,0 +1,5 @@ +import _ from 'lodash'; +import {ReporterTestResult} from '../index'; + +export const copyAndUpdate = (original: ReporterTestResult, updates: Partial): ReporterTestResult => + _.assign({}, original, updates); diff --git a/lib/test-attempt-manager.ts b/lib/test-attempt-manager.ts new file mode 100644 index 000000000..a6e3305a1 --- /dev/null +++ b/lib/test-attempt-manager.ts @@ -0,0 +1,71 @@ +import _ from 'lodash'; +import {ReporterTestResult} from './test-adapter'; +import {IDLE, RUNNING, SKIPPED, TestStatus, UPDATED} from './constants'; + +type TestSpec = Pick + +const INVALID_ATTEMPT = -1; + +export class TestAttemptManager { + private _attemptMap: Map; + private _statusMap: Map; + + constructor() { + this._attemptMap = new Map(); + this._statusMap = new Map(); + } + + removeAttempt(testResult: TestSpec): number { + const hash = this._getHash(testResult); + let value = this._attemptMap.get(hash); + + if (_.isNil(value) || value === 0) { + value = INVALID_ATTEMPT; + } else { + value -= 1; + + const statuses = this._statusMap.get(hash) as TestStatus[]; + statuses.pop(); + } + + this._attemptMap.set(hash, value); + + return value; + } + + getCurrentAttempt(testResult: TestSpec): number { + const hash = this._getHash(testResult); + + return this._attemptMap.get(hash) ?? INVALID_ATTEMPT; + } + + registerAttempt(testResult: TestSpec, status: TestStatus): number { + const hash = this._getHash(testResult); + + let attempt = this._attemptMap.get(hash); + const statuses = this._statusMap.get(hash) ?? []; + + if (_.isNil(attempt)) { + this._attemptMap.set(hash, 0); + statuses.push(status); + this._statusMap.set(hash, statuses); + + return 0; + } + + if ([IDLE, RUNNING, SKIPPED, UPDATED].includes(_.last(statuses) as TestStatus)) { + return attempt; + } + + attempt += 1; + this._attemptMap.set(hash, attempt); + statuses.push(status); + this._statusMap.set(hash, statuses); + + return attempt; + } + + private _getHash(testResult: TestSpec): string { + return `${testResult.fullName}.${testResult.browserId}`; + } +} diff --git a/lib/tests-tree-builder/gui.ts b/lib/tests-tree-builder/gui.ts index ffd88676f..0a660ece4 100644 --- a/lib/tests-tree-builder/gui.ts +++ b/lib/tests-tree-builder/gui.ts @@ -42,36 +42,6 @@ interface TestUndoRefUpdateData { } 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]; - } - - /* 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); - - 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: string): TreeImage[] { return this._tree.results.byId[testId].imageIds.map((imageId) => { return this._tree.images.byId[imageId]; diff --git a/playwright.ts b/playwright.ts index 4946b2fdc..285628a1b 100644 --- a/playwright.ts +++ b/playwright.ts @@ -13,6 +13,7 @@ import {EventEmitter} from 'events'; import {PlaywrightTestAdapter, getStatus} from './lib/test-adapter/playwright'; import PQueue from 'p-queue'; import {SqliteClient} from './lib/sqlite-client'; +import {TestAttemptManager} from './lib/test-attempt-manager'; export {ReporterConfig} from './lib/types'; @@ -38,8 +39,9 @@ class MyReporter implements Reporter { this._initPromise = (async (htmlReporter: HtmlReporter, config: ReporterConfig): Promise => { const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); + const testAttemptManager = new TestAttemptManager(); - this._staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient}); + this._staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, testAttemptManager}); await this._staticReportBuilder.saveStaticFiles(); })(this._htmlReporter, this._config); From 4d99efdaba3da74df3403c66faeb5b09d8f9c641 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 5 Dec 2023 12:09:00 +0300 Subject: [PATCH 06/17] fix: unit test and minor bug fixes --- lib/gui/tool-runner/index.ts | 12 +++++- lib/report-builder/static.ts | 2 +- lib/sqlite-client.ts | 6 ++- test/unit/hermione.js | 3 +- test/unit/lib/gui/tool-runner/index.js | 14 +++++-- .../lib/gui/tool-runner/report-subsciber.js | 10 +++-- test/unit/lib/plugin-adapter.js | 3 +- test/unit/lib/report-builder/gui.js | 40 +++++-------------- test/unit/lib/report-builder/static.js | 18 ++++----- test/unit/lib/sqlite-client.js | 23 ++--------- test/unit/lib/test-adapter/hermione.ts | 19 --------- test/unit/lib/tests-tree-builder/gui.js | 28 ------------- test/unit/utils.js | 3 +- 13 files changed, 61 insertions(+), 120 deletions(-) diff --git a/lib/gui/tool-runner/index.ts b/lib/gui/tool-runner/index.ts index fe7cd7b58..8d50d32e2 100644 --- a/lib/gui/tool-runner/index.ts +++ b/lib/gui/tool-runner/index.ts @@ -14,7 +14,16 @@ import {GuiReportBuilder, GuiReportBuilderResult} from '../../report-builder/gui import {EventSource} from '../event-source'; import {logger, getShortMD5} from '../../common-utils'; import * as reporterHelper from '../../reporter-helpers'; -import {UPDATED, SKIPPED, IDLE, TestStatus, ToolName, DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} from '../../constants'; +import { + UPDATED, + SKIPPED, + IDLE, + TestStatus, + ToolName, + DATABASE_URLS_JSON_NAME, + LOCAL_DATABASE_NAME, + PluginEvents +} from '../../constants'; import {formatId, mkFullTitle, mergeDatabasesForReuse, filterByEqualDiffSizes} from './utils'; import {getTestsTreeFromDatabase} from '../../db-utils/server'; import {formatTestResult} from '../../server-utils'; @@ -116,6 +125,7 @@ export class ToolRunner { this._collection = await this._readTests(); + this._hermione.htmlReporter.emit(PluginEvents.DATABASE_CREATED, dbClient.getRawConnection()); await this._reportBuilder.saveStaticFiles(); this._reportBuilder.setApiValues(this._hermione.htmlReporter.values); diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index dd2c92a7a..3d8910e2c 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -14,7 +14,7 @@ import { LOCAL_DATABASE_NAME, PluginEvents } from '../constants'; -import {PreparedTestResult, SqliteClient} from '../sqlite-client'; +import type {PreparedTestResult, SqliteClient} from '../sqlite-client'; import {ReporterTestResult} from '../test-adapter'; import {hasImage, saveStaticFilesToReportDir, writeDatabaseUrlsFile} from '../server-utils'; import {ReporterConfig} from '../types'; diff --git a/lib/sqlite-client.ts b/lib/sqlite-client.ts index e11e18176..525fb5df9 100644 --- a/lib/sqlite-client.ts +++ b/lib/sqlite-client.ts @@ -103,8 +103,6 @@ export class SqliteClient { debug('db connection opened'); createTablesQuery().forEach((query) => db?.prepare(query).run()); - - htmlReporter.emit(htmlReporter.events.DATABASE_CREATED, db); } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any throw new NestedError(`Error creating database at "${dbPath}"`, err); } @@ -120,6 +118,10 @@ export class SqliteClient { this._transformer = transformer; } + getRawConnection(): Database.Database { + return this._db; + } + close(): void { this._db.prepare('VACUUM').run(); diff --git a/test/unit/hermione.js b/test/unit/hermione.js index 18320cf95..bfdd261fc 100644 --- a/test/unit/hermione.js +++ b/test/unit/hermione.js @@ -45,12 +45,12 @@ describe('lib/hermione', () => { const {StaticReportBuilder} = proxyquire('lib/report-builder/static', { 'fs-extra': fs, '../server-utils': utils, - '../sqlite-client': {SqliteClient}, '../test-adapter': {TestAdapter}, '../image-handler': {ImageHandler} }); const {PluginAdapter} = proxyquire('lib/plugin-adapter', { + './sqlite-client': {SqliteClient}, './server-utils': utils, './report-builder/static': {StaticReportBuilder}, './plugin-api': proxyquire('lib/plugin-api', { @@ -165,7 +165,6 @@ describe('lib/hermione', () => { sandbox.stub(StaticReportBuilder.prototype, 'saveStaticFiles'); sandbox.stub(StaticReportBuilder.prototype, 'finalize'); - sandbox.stub(SqliteClient.prototype, 'init').resolves({}); sandbox.stub(SqliteClient.prototype, 'query'); sandbox.stub(fs, 'readFile').resolves(Buffer.from('')); diff --git a/test/unit/lib/gui/tool-runner/index.js b/test/unit/lib/gui/tool-runner/index.js index ccf975cfe..759627530 100644 --- a/test/unit/lib/gui/tool-runner/index.js +++ b/test/unit/lib/gui/tool-runner/index.js @@ -8,6 +8,9 @@ const {GuiReportBuilder} = require('lib/report-builder/gui'); const {LOCAL_DATABASE_NAME} = require('lib/constants/database'); const {logger} = require('lib/common-utils'); const {stubTool, stubConfig, mkImagesInfo, mkState, mkSuite} = require('test/unit/utils'); +const {SqliteClient} = require('lib/sqlite-client'); +const {TestAttemptManager} = require('lib/test-attempt-manager'); +const {PluginEvents} = require('lib/constants'); describe('lib/gui/tool-runner/index', () => { const sandbox = sinon.createSandbox(); @@ -21,6 +24,7 @@ describe('lib/gui/tool-runner/index', () => { let revertReferenceImage; let toolRunnerUtils; let createTestRunner; + let testAttemptManager; const mkTestCollection_ = (testsTree = {}) => { return { @@ -69,13 +73,15 @@ describe('lib/gui/tool-runner/index', () => { createTestRunner = sinon.stub(); + testAttemptManager = new TestAttemptManager(); + toolRunnerUtils = { findTestResult: sandbox.stub(), formatId: sandbox.stub().returns('some-id') }; reportBuilder = sinon.createStubInstance(GuiReportBuilder); - reportBuilder.getUpdatedAttempt.returns(0); + sandbox.stub(reportBuilder, 'testAttemptManager').get(() => testAttemptManager); subscribeOnToolEvents = sandbox.stub().named('reportSubscriber').resolves(); looksSame = sandbox.stub().named('looksSame').resolves({equal: true}); @@ -92,6 +98,7 @@ describe('lib/gui/tool-runner/index', () => { './runner': {createTestRunner}, './report-subscriber': {subscribeOnToolEvents}, './utils': toolRunnerUtils, + '../../sqlite-client': {SqliteClient: {create: () => sinon.createStubInstance(SqliteClient)}}, '../../db-utils/server': {getTestsTreeFromDatabase}, '../../reporter-helpers': { updateReferenceImage: sandbox.stub().resolves(), @@ -108,7 +115,7 @@ describe('lib/gui/tool-runner/index', () => { describe('initialize', () => { it('should set values added through api', () => { - const htmlReporter = {values: {foo: 'bar'}}; + const htmlReporter = {emit: sinon.stub(), values: {foo: 'bar'}}; hermione = stubTool(stubConfig(), {}, {}, htmlReporter); const gui = initGuiReporter(hermione); @@ -219,7 +226,8 @@ describe('lib/gui/tool-runner/index', () => { await gui.initialize(); - assert.callOrder(hermione.readTests, reportBuilder.init); + assert.callOrder(hermione.readTests, hermione.htmlReporter.emit); + assert.calledOnceWith(hermione.htmlReporter.emit, PluginEvents.DATABASE_CREATED, sinon.match.any); }); }); diff --git a/test/unit/lib/gui/tool-runner/report-subsciber.js b/test/unit/lib/gui/tool-runner/report-subsciber.js index ea19cb2ca..858e6685b 100644 --- a/test/unit/lib/gui/tool-runner/report-subsciber.js +++ b/test/unit/lib/gui/tool-runner/report-subsciber.js @@ -9,10 +9,12 @@ const {ClientEvents} = require('lib/gui/constants'); const {stubTool, stubConfig} = require('test/unit/utils'); const {HermioneTestAdapter} = require('lib/test-adapter'); const {ErrorName} = require('lib/errors'); +const {TestAttemptManager} = require('lib/test-attempt-manager'); +const {FAIL} = require('lib/constants'); describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const sandbox = sinon.createSandbox(); - let reportBuilder; + let reportBuilder, testAttemptManager; let client; const events = { @@ -34,10 +36,12 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { beforeEach(() => { reportBuilder = sinon.createStubInstance(GuiReportBuilder); - reportBuilder.getCurrAttempt.returns(0); + + testAttemptManager = new TestAttemptManager(); sandbox.stub(GuiReportBuilder, 'create').returns(reportBuilder); sandbox.stub(reportBuilder, 'imageHandler').value({saveTestImages: sinon.stub()}); + sandbox.stub(reportBuilder, 'testAttemptManager').value(testAttemptManager); sandbox.stub(HermioneTestAdapter.prototype, 'id').value('some-id'); client = new EventEmitter(); @@ -120,7 +124,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const hermione = mkHermione_(); const testResult = mkHermioneTestResult({assertViewResults: [{name: ErrorName.IMAGE_DIFF}]}); - reportBuilder.getCurrAttempt.returns(1); + testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); diff --git a/test/unit/lib/plugin-adapter.js b/test/unit/lib/plugin-adapter.js index a2dd57201..fb0948882 100644 --- a/test/unit/lib/plugin-adapter.js +++ b/test/unit/lib/plugin-adapter.js @@ -7,6 +7,7 @@ const {logger} = require('lib/common-utils'); const {StaticReportBuilder} = require('lib/report-builder/static'); const {HtmlReporter} = require('lib/plugin-api'); const {stubTool, stubConfig} = require('../utils'); +const {SqliteClient} = require('lib/sqlite-client'); const {GUI, MERGE_REPORTS, REMOVE_UNUSED_SCREENS} = require('lib/cli-commands').cliCommands; describe('lib/plugin-adapter', () => { @@ -64,7 +65,6 @@ describe('lib/plugin-adapter', () => { sandbox.stub(StaticReportBuilder, 'create').returns(Object.create(StaticReportBuilder.prototype)); sandbox.stub(StaticReportBuilder.prototype, 'saveStaticFiles').resolves(); - sandbox.stub(StaticReportBuilder.prototype, 'init').resolves(); sandbox.stub(StaticReportBuilder.prototype, 'finalize').resolves(); prepareData = sandbox.stub().resolves(); @@ -77,6 +77,7 @@ describe('lib/plugin-adapter', () => { toolReporter = proxyquire('lib/plugin-adapter', { './config': {parseConfig}, + './sqlite-client': {SqliteClient: {create: async () => sinon.createStubInstance(SqliteClient)}}, [`./cli-commands/${GUI}`]: cliCommands[GUI], [`./cli-commands/${MERGE_REPORTS}`]: cliCommands[MERGE_REPORTS], [`./cli-commands/${REMOVE_UNUSED_SCREENS}`]: cliCommands[REMOVE_UNUSED_SCREENS] diff --git a/test/unit/lib/report-builder/gui.js b/test/unit/lib/report-builder/gui.js index eb046112f..55ebe87a0 100644 --- a/test/unit/lib/report-builder/gui.js +++ b/test/unit/lib/report-builder/gui.js @@ -11,28 +11,33 @@ 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 {ErrorName} = require('lib/errors'); +const {TestAttemptManager} = require('lib/test-attempt-manager'); const TEST_REPORT_PATH = 'test'; const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`; describe('GuiReportBuilder', () => { const sandbox = sinon.sandbox.create(); - let hasImage, deleteFile, GuiReportBuilder; + let hasImage, deleteFile, GuiReportBuilder, dbClient, testAttemptManager; const mkGuiReportBuilder_ = async ({toolConfig, pluginConfig} = {}) => { toolConfig = _.defaults(toolConfig || {}, {getAbsoluteUrl: _.noop}); pluginConfig = _.defaults(pluginConfig || {}, {baseHost: '', path: TEST_REPORT_PATH, baseTestPath: ''}); + const htmlReporter = HtmlReporter.create({baseHost: ''}); + const browserConfigStub = {getAbsoluteUrl: toolConfig.getAbsoluteUrl}; const hermione = { forBrowser: sandbox.stub().returns(browserConfigStub), - htmlReporter: HtmlReporter.create() + htmlReporter }; HermioneTestAdapter.create = (obj) => obj; - const reportBuilder = GuiReportBuilder.create(hermione.htmlReporter, pluginConfig); - await reportBuilder.init(); + dbClient = await SqliteClient.create({htmlReporter, reportPath: TEST_REPORT_PATH}); + testAttemptManager = new TestAttemptManager(); + + const reportBuilder = GuiReportBuilder.create(hermione.htmlReporter, pluginConfig, {dbClient, testAttemptManager}); return reportBuilder; }; @@ -92,7 +97,6 @@ describe('GuiReportBuilder', () => { sandbox.stub(GuiTestsTreeBuilder.prototype, 'getTestsDataToUpdateRefs').returns([]); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getImageDataToFindEqualDiffs').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getImagesInfo').returns([]); - sandbox.stub(GuiTestsTreeBuilder.prototype, 'getLastResult').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'addTestResult').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'getResultDataToUnacceptImage').returns({}); sandbox.stub(GuiTestsTreeBuilder.prototype, 'updateImageInfo').returns({}); @@ -335,32 +339,6 @@ describe('GuiReportBuilder', () => { }); }); - describe('"getCurrAttempt" method', () => { - [IDLE, SKIPPED].forEach((status) => { - it(`should return attempt for last result if status is "${status}"`, async () => { - const formattedResult = {status, attempt: 100500}; - GuiTestsTreeBuilder.prototype.getLastResult.returns(formattedResult); - const reportBuilder = await mkGuiReportBuilder_(); - - const currAttempt = reportBuilder.getCurrAttempt(formattedResult); - - assert.equal(currAttempt, formattedResult.attempt); - }); - }); - - [SUCCESS, FAIL, ERROR, UPDATED].forEach((status) => { - it(`should return attempt for next result if status is "${status}"`, async () => { - const formattedResult = {status, attempt: 100500}; - GuiTestsTreeBuilder.prototype.getLastResult.returns(formattedResult); - const reportBuilder = await mkGuiReportBuilder_(); - - const currAttempt = reportBuilder.getCurrAttempt(formattedResult); - - assert.equal(currAttempt, formattedResult.attempt + 1); - }); - }); - }); - describe('"undoAcceptImages"', () => { let reportBuilder; diff --git a/test/unit/lib/report-builder/static.js b/test/unit/lib/report-builder/static.js index 996fb2a7c..ba80e2f5f 100644 --- a/test/unit/lib/report-builder/static.js +++ b/test/unit/lib/report-builder/static.js @@ -7,13 +7,14 @@ const proxyquire = require('proxyquire'); 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 {SqliteClient} = require('lib/sqlite-client'); const TEST_REPORT_PATH = 'test'; const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`; describe('StaticReportBuilder', () => { const sandbox = sinon.sandbox.create(); - let StaticReportBuilder, htmlReporter; + let StaticReportBuilder, htmlReporter, dbClient; const fs = _.clone(fsOriginal); @@ -25,16 +26,15 @@ describe('StaticReportBuilder', () => { const mkStaticReportBuilder_ = async ({pluginConfig} = {}) => { pluginConfig = _.defaults(pluginConfig, {baseHost: '', path: TEST_REPORT_PATH, baseTestPath: ''}); - htmlReporter = _.extend(HtmlReporter.create(), { + htmlReporter = _.extend(HtmlReporter.create({baseHost: ''}), { reportsSaver: { saveReportData: sandbox.stub() } }); - const reportBuilder = StaticReportBuilder.create(htmlReporter, pluginConfig); - await reportBuilder.init(); + dbClient = await SqliteClient.create({htmlReporter, reportPath: TEST_REPORT_PATH}); - return reportBuilder; + return StaticReportBuilder.create(htmlReporter, pluginConfig, {dbClient}); }; const stubTest_ = (opts = {}) => { @@ -69,7 +69,7 @@ describe('StaticReportBuilder', () => { }); it('should add skipped test', async () => { - await reportBuilder.addSkipped(stubTest_()); + await reportBuilder.addSkipped(stubTest_({status: SKIPPED})); const db = new Database(TEST_DB_PATH); const [{status}] = db.prepare('SELECT * from suites').all(); @@ -79,7 +79,7 @@ describe('StaticReportBuilder', () => { }); it('should add success test', async () => { - await reportBuilder.addSuccess(stubTest_()); + await reportBuilder.addSuccess(stubTest_({status: SUCCESS})); const db = new Database(TEST_DB_PATH); const [{status}] = db.prepare('SELECT * from suites').all(); @@ -89,7 +89,7 @@ describe('StaticReportBuilder', () => { }); it('should add failed test', async () => { - await reportBuilder.addFail(stubTest_()); + await reportBuilder.addFail(stubTest_({status: FAIL})); const db = new Database(TEST_DB_PATH); const [{status}] = db.prepare('SELECT * from suites').all(); @@ -99,7 +99,7 @@ describe('StaticReportBuilder', () => { }); it('should add error test', async () => { - await reportBuilder.addError(stubTest_()); + await reportBuilder.addError(stubTest_({status: ERROR})); const db = new Database(TEST_DB_PATH); const [{status}] = db.prepare('SELECT * from suites').all(); diff --git a/test/unit/lib/sqlite-client.js b/test/unit/lib/sqlite-client.js index a51e13785..7b697566e 100644 --- a/test/unit/lib/sqlite-client.js +++ b/test/unit/lib/sqlite-client.js @@ -12,13 +12,11 @@ describe('lib/sqlite-client', () => { let htmlReporter; const makeSqliteClient_ = async () => { - const sqliteClient = SqliteClient.create({htmlReporter, reportPath: 'test'}); - await sqliteClient.init(); - return sqliteClient; + return SqliteClient.create({htmlReporter, reportPath: 'test'}); }; beforeEach(() => { - htmlReporter = HtmlReporter.create(); + htmlReporter = HtmlReporter.create({baseHost: 'some-host'}); }); afterEach(() => { @@ -62,26 +60,15 @@ describe('lib/sqlite-client', () => { }); }); - it('should emit "DATABASE_CREATED" event with new database connection', async () => { - const onDatabaseCreated = sinon.spy(); - htmlReporter.on(htmlReporter.events.DATABASE_CREATED, onDatabaseCreated); - - await makeSqliteClient_(); - - assert.calledOnceWith(onDatabaseCreated, sinon.match.instanceOf(Database)); - }); - describe('query', () => { let getStub, prepareStub, sqliteClient; beforeEach(async () => { getStub = sandbox.stub(); prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({get: getStub}); - sqliteClient = proxyquire('lib/sqlite-client', { + sqliteClient = await proxyquire('lib/sqlite-client', { './db-utils/common': {createTablesQuery: () => []} }).SqliteClient.create({htmlReporter, reportPath: 'test'}); - - await sqliteClient.init(); }); describe('should create valid query string', () => { @@ -145,11 +132,9 @@ describe('lib/sqlite-client', () => { beforeEach(async () => { runStub = sandbox.stub(); prepareStub = sandbox.stub(Database.prototype, 'prepare').returns({run: runStub}); - sqliteClient = proxyquire('lib/sqlite-client', { + sqliteClient = await proxyquire('lib/sqlite-client', { './db-utils/common': {createTablesQuery: () => []} }).SqliteClient.create({htmlReporter, reportPath: 'test'}); - - await sqliteClient.init(); }); describe('should create valid sentence string', () => { diff --git a/test/unit/lib/test-adapter/hermione.ts b/test/unit/lib/test-adapter/hermione.ts index 891d145b0..26eb7983a 100644 --- a/test/unit/lib/test-adapter/hermione.ts +++ b/test/unit/lib/test-adapter/hermione.ts @@ -69,25 +69,6 @@ describe('HermioneTestAdapter', () => { afterEach(() => sandbox.restore()); - it('should return suite attempt', () => { - const firstTestResult = mkTestResult_({fullTitle: () => 'some-title'}); - const secondTestResult = mkTestResult_({fullTitle: () => 'other-title'}); - - mkHermioneTestResultAdapter(firstTestResult); - - assert.equal(mkHermioneTestResultAdapter(firstTestResult).attempt, 1); - assert.equal(mkHermioneTestResultAdapter(secondTestResult).attempt, 0); - }); - - it('should not increment attempt for skipped tests', () => { - const testResult = mkTestResult_({fullTitle: () => 'some-title'}); - - mkHermioneTestResultAdapter(testResult, {status: SKIPPED}); - const result = mkHermioneTestResultAdapter(testResult, {status: SKIPPED}); - - assert.equal(result.attempt, 0); - }); - it('should return full test error', () => { getCommandsHistory.withArgs([{name: 'foo'}], ['foo']).returns(['some-history']); const testResult = mkTestResult_({ diff --git a/test/unit/lib/tests-tree-builder/gui.js b/test/unit/lib/tests-tree-builder/gui.js index dff7b4061..e20961a1d 100644 --- a/test/unit/lib/tests-tree-builder/gui.js +++ b/test/unit/lib/tests-tree-builder/gui.js @@ -26,34 +26,6 @@ describe('GuiResultsTreeBuilder', () => { builder = mkGuiTreeBuilder(); }); - describe('"getLastResult" method', () => { - it('should return last result from tree', () => { - const formattedRes1 = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 0}); - const formattedRes2 = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 1}); - builder.addTestResult(mkTestResult_(), formattedRes1); - builder.addTestResult(mkTestResult_(), formattedRes2); - - const lastResult = builder.getLastResult({testPath: ['s'], browserId: 'b'}); - - assert.deepEqual(lastResult, builder.tree.results.byId['s b 1']); - }); - }); - - 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}); - const formattedRes3 = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 2}); - builder.addTestResult(mkTestResult_(), formattedRes1); - builder.addTestResult(mkTestResult_(), formattedRes2); - builder.addTestResult(mkTestResult_(), formattedRes3); - - const lastResult = builder.getLastActualResult({testPath: ['s'], browserId: 'b', attempt: 1}); - - assert.deepEqual(lastResult, builder.tree.results.byId['s b 0']); - }); - }); - describe('"getImagesInfo" method', () => { it('should return images from tree for passed test result id', () => { const formattedRes = mkFormattedResult_({testPath: ['s'], browserId: 'b', attempt: 0}); diff --git a/test/unit/utils.js b/test/unit/utils.js index 08f1e8945..3ba25086d 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const EventEmitter2 = require('eventemitter2'); +const {HtmlReporter} = require('lib/plugin-api'); function stubConfig(config = {}) { const browsers = config.browsers || {}; @@ -30,7 +31,7 @@ function stubTool(config = stubConfig(), events = {}, errors = {}, htmlReporter) tool.run = sinon.stub().resolves(false); tool.readTests = sinon.stub().resolves(stubTestCollection()); - tool.htmlReporter = htmlReporter || sinon.stub(); + tool.htmlReporter = htmlReporter || sinon.createStubInstance(HtmlReporter); _.defaultsDeep(tool.htmlReporter, { emitAsync: sinon.stub(), events: {REPORT_SAVED: 'reportSaved'} From e7f1ae81321d47bbddc4f50891e706eb0261a76c Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 5 Dec 2023 17:54:36 +0300 Subject: [PATCH 07/17] refactor: get rid of plugin-adapter --- hermione.ts | 76 ++++++++++-- lib/plugin-adapter.ts | 90 -------------- test/unit/hermione.js | 75 +++++++----- test/unit/lib/plugin-adapter.js | 211 -------------------------------- 4 files changed, 110 insertions(+), 342 deletions(-) delete mode 100644 lib/plugin-adapter.ts delete mode 100644 test/unit/lib/plugin-adapter.js diff --git a/hermione.ts b/hermione.ts index e6515c85e..b85cc13ca 100644 --- a/hermione.ts +++ b/hermione.ts @@ -1,15 +1,22 @@ import os from 'os'; -import PQueue from 'p-queue'; +import path from 'path'; import Hermione, {TestResult as HermioneTestResult} from 'hermione'; +import _ from 'lodash'; +import PQueue from 'p-queue'; +import {CommanderStatic} from '@gemini-testing/commander'; -import {PluginAdapter} from './lib/plugin-adapter'; -import {createWorkers, CreateWorkersRunner} from './lib/workers/create-workers'; -import {FAIL, SUCCESS} from './lib/constants'; +import {cliCommands} from './lib/cli-commands'; import {hasDiff} from './lib/common-utils'; -import {formatTestResult} from './lib/server-utils'; -import {ReporterConfig, ReporterOptions} from './lib/types'; +import {parseConfig} from './lib/config'; +import {FAIL, SUCCESS, ToolName} from './lib/constants'; +import {HtmlReporter} from './lib/plugin-api'; import {StaticReportBuilder} from './lib/report-builder/static'; +import {formatTestResult, logPathToHtmlReport, logError} from './lib/server-utils'; +import {SqliteClient} from './lib/sqlite-client'; import {HermioneTestAdapter, ReporterTestResult} from './lib/test-adapter'; +import {TestAttemptManager} from './lib/test-attempt-manager'; +import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './lib/types'; +import {createWorkers, CreateWorkersRunner} from './lib/workers/create-workers'; let workers: ReturnType; @@ -17,23 +24,66 @@ export = (hermione: Hermione, opts: Partial): void => { if (hermione.isWorker()) { return; } - const plugin = PluginAdapter.create(hermione, opts); - if (!plugin.isEnabled()) { + const config = parseConfig(opts); + + if (!config.enabled) { return; } - plugin - .addApi() - .addCliCommands() - .init(prepare); + const htmlReporter = HtmlReporter.create(config, {toolName: ToolName.Hermione}); + + (hermione as Hermione & HtmlReporterApi).htmlReporter = htmlReporter; + + let isCliCommandLaunched = false; + let handlingTestResults: Promise; + + hermione.on(hermione.events.CLI, (commander: CommanderStatic) => { + _.values(cliCommands).forEach((command: string) => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + require(path.resolve(__dirname, 'lib/cli-commands', command))(commander, config, hermione); + + commander.prependListener(`command:${command}`, () => { + isCliCommandLaunched = true; + }); + }); + }); + + hermione.on(hermione.events.INIT, async () => { + if (isCliCommandLaunched) { + return; + } + + const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); + const testAttemptManager = new TestAttemptManager(); + const staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, testAttemptManager}); + + handlingTestResults = Promise.all([ + staticReportBuilder.saveStaticFiles(), + handleTestResults(hermione, staticReportBuilder, config) + ]).then(async () => { + await staticReportBuilder.finalize(); + }).then(async () => { + await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: config.path}); + }); + }); hermione.on(hermione.events.RUNNER_START, (runner) => { workers = createWorkers(runner as unknown as CreateWorkersRunner); }); + + hermione.on(hermione.events.RUNNER_END, async () => { + try { + await handlingTestResults; + + logPathToHtmlReport(config); + } catch (e: unknown) { + logError(e as Error); + } + }); }; -async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, pluginConfig: ReporterConfig): Promise { +async function handleTestResults(hermione: Hermione, reportBuilder: StaticReportBuilder, pluginConfig: ReporterConfig): Promise { const {path: reportPath} = pluginConfig; const {imageHandler} = reportBuilder; diff --git a/lib/plugin-adapter.ts b/lib/plugin-adapter.ts deleted file mode 100644 index dbd29b3ab..000000000 --- a/lib/plugin-adapter.ts +++ /dev/null @@ -1,90 +0,0 @@ -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'; -import {ToolName} from './constants'; -import {SqliteClient} from './sqlite-client'; -import {TestAttemptManager} from './test-attempt-manager'; - -type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise; - -export class PluginAdapter { - protected _hermione: Hermione & Partial; - protected _config: ReporterConfig; - - static create( - this: new (hermione: Hermione, opts: Partial) => T, - hermione: Hermione, - opts: Partial - ): T { - return new this(hermione, opts); - } - - constructor(hermione: Hermione, opts: Partial) { - this._hermione = hermione; - this._config = parseConfig(opts); - } - - isEnabled(): boolean { - return this._config.enabled; - } - - addApi(): this { - this._hermione.htmlReporter = HtmlReporter.create(this._config, {toolName: ToolName.Hermione}); - 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 { - if (!this._hermione.htmlReporter) { - throw new Error('Html-reporter API has to be added to hermione before usage'); - } - - const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._config.path}); - const testAttemptManager = new TestAttemptManager(); - - const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient, testAttemptManager}); - - return Promise - .all([ - staticReportBuilder.saveStaticFiles(), - prepareData(this._hermione as Hermione & HtmlReporterApi, staticReportBuilder, this._config) - ]) - .then(() => staticReportBuilder.finalize()) - .then(async () => { - const htmlReporter = this._hermione.htmlReporter as 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/test/unit/hermione.js b/test/unit/hermione.js index bfdd261fc..e54082ae8 100644 --- a/test/unit/hermione.js +++ b/test/unit/hermione.js @@ -20,6 +20,8 @@ describe('lib/hermione', () => { let hermione; let cacheExpectedPaths = new Map(), cacheAllImages = new Map(), cacheDiffImages = new Map(); + let program; + const fs = _.clone(fsOriginal); const originalUtils = proxyquire('lib/server-utils', { 'fs-extra': fs @@ -49,19 +51,17 @@ describe('lib/hermione', () => { '../image-handler': {ImageHandler} }); - const {PluginAdapter} = proxyquire('lib/plugin-adapter', { - './sqlite-client': {SqliteClient}, - './server-utils': utils, - './report-builder/static': {StaticReportBuilder}, - './plugin-api': proxyquire('lib/plugin-api', { - './local-images-saver': proxyquire('lib/local-images-saver', { - './server-utils': utils - }) + const HtmlReporter = proxyquire('lib/plugin-api', { + './local-images-saver': proxyquire('lib/local-images-saver', { + './server-utils': utils }) - }); + }).HtmlReporter; - const HermioneReporter = proxyquire('../../hermione', { - './lib/plugin-adapter': {PluginAdapter} + const runHtmlReporter = proxyquire('../../hermione', { + './lib/sqlite-client': {SqliteClient}, + './lib/server-utils': utils, + './lib/report-builder/static': {StaticReportBuilder}, + './lib/plugin-api': {HtmlReporter} }); const events = { @@ -103,6 +103,25 @@ describe('lib/hermione', () => { }, events, {ImageDiffError, NoRefImageError}); } + function mkCommander() { + const commander = {}; + const props = [ + 'command', + 'allowUnknownOption', + 'description', + 'option', + 'action', + 'on', + 'prependListener' + ]; + + for (const prop of props) { + commander[prop] = sinon.stub().returns(commander); + } + + return commander; + } + function initReporter_(opts) { opts = _.defaults(opts, { enabled: true, @@ -110,7 +129,9 @@ describe('lib/hermione', () => { baseHost: '' }); - HermioneReporter(hermione, opts); + runHtmlReporter(hermione, opts); + + hermione.emit(hermione.events.CLI, program); return hermione.emitAsync(hermione.events.INIT); } @@ -139,9 +160,9 @@ describe('lib/hermione', () => { beforeEach(async () => { hermione = mkHermione_(); - sandbox.spy(PluginAdapter.prototype, 'addApi'); - sandbox.spy(PluginAdapter.prototype, 'addCliCommands'); - sandbox.spy(PluginAdapter.prototype, 'init'); + program = mkCommander(); + + sandbox.spy(HtmlReporter, 'create'); sandbox.stub(fs, 'ensureDir').resolves(); sandbox.stub(fs, 'writeFile').resolves(); @@ -178,24 +199,22 @@ describe('lib/hermione', () => { sandbox.restore(); }); - it('should do nothing if plugin is disabled', () => { - return initReporter_({enabled: false}).then(() => { - assert.notCalled(PluginAdapter.prototype.addCliCommands); - }); - }); + it('should do nothing if plugin is disabled', async () => { + await initReporter_({enabled: false}); - it('should add api', () => { - return initReporter_() - .then(() => assert.calledOnce(PluginAdapter.prototype.addCliCommands)); + assert.notCalled(HtmlReporter.create); }); - it('should add cli commands', () => { - return initReporter_() - .then(() => assert.calledOnce(PluginAdapter.prototype.addCliCommands)); + it('should add api', async () => { + await initReporter_(); + + assert.isObject(hermione.htmlReporter); }); - it('should init plugin', () => { - return initReporter_().then(() => assert.calledOnce(PluginAdapter.prototype.init)); + it('should add cli commands', async () => { + await initReporter_(); + + assert.called(program.command); }); it('should add skipped test to result', async () => { diff --git a/test/unit/lib/plugin-adapter.js b/test/unit/lib/plugin-adapter.js deleted file mode 100644 index fb0948882..000000000 --- a/test/unit/lib/plugin-adapter.js +++ /dev/null @@ -1,211 +0,0 @@ -'use strict'; - -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 {HtmlReporter} = require('lib/plugin-api'); -const {stubTool, stubConfig} = require('../utils'); -const {SqliteClient} = require('lib/sqlite-client'); -const {GUI, MERGE_REPORTS, REMOVE_UNUSED_SCREENS} = require('lib/cli-commands').cliCommands; - -describe('lib/plugin-adapter', () => { - const sandbox = sinon.createSandbox(); - const cliCommands = {}; - let parseConfig; - let tool; - let toolReporter; - let commander; - let prepareData; - - const events = { - CLI: 'cli', - INIT: 'init' - }; - - function mkHermione_() { - return stubTool(stubConfig(), Object.assign(events, {RUNNER_END: 'runnerEnd'})); - } - - function initReporter_(opts = {}) { - opts = _.defaults(opts, {enabled: true, path: ''}); - parseConfig.returns(opts); - - return toolReporter.create(tool, opts) - .addCliCommands() - .init(prepareData); - } - - function initApiReporter_(opts) { - initReporter_(opts); - return tool.emitAsync(tool.events.INIT); - } - - function initCliReporter_(opts, {command = 'foo'} = {}) { - initReporter_(opts); - - const commander = mkCommander_(command); - tool.emit(tool.events.CLI, commander); - commander.emit(`command:${command}`); - - return tool.emitAsync(tool.events.INIT); - } - - function mkCommander_(commands = ['default-command']) { - commander = new EventEmitter(); - commander.commands = [].concat(commands).map((cmd) => ({name: () => cmd})); - - return commander; - } - - beforeEach(() => { - sandbox.stub(logger, 'log'); - sandbox.stub(logger, 'error'); - - sandbox.stub(StaticReportBuilder, 'create').returns(Object.create(StaticReportBuilder.prototype)); - sandbox.stub(StaticReportBuilder.prototype, 'saveStaticFiles').resolves(); - sandbox.stub(StaticReportBuilder.prototype, 'finalize').resolves(); - - prepareData = sandbox.stub().resolves(); - - tool = mkHermione_(); - parseConfig = sandbox.stub().returns({enabled: true}); - cliCommands[GUI] = sandbox.stub(); - cliCommands[MERGE_REPORTS] = sandbox.stub(); - cliCommands[REMOVE_UNUSED_SCREENS] = sandbox.stub(); - - toolReporter = proxyquire('lib/plugin-adapter', { - './config': {parseConfig}, - './sqlite-client': {SqliteClient: {create: async () => sinon.createStubInstance(SqliteClient)}}, - [`./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()); - - it('should parse config using passed options', () => { - const opts = {path: 'some/path', enabled: false, baseHost: 'some-host'}; - - toolReporter.create(tool, opts); - - assert.calledWith(parseConfig, {path: 'some/path', enabled: false, baseHost: 'some-host'}); - }); - - describe('isEnabled', () => { - it('should be enabled', () => { - const plugin = toolReporter.create(tool, {enabled: true}); - - assert.isTrue(plugin.isEnabled()); - }); - - it('should be disabled', () => { - const opts = {enabled: false}; - parseConfig.withArgs(opts).returns(opts); - - const plugin = toolReporter.create(tool, opts); - - assert.isFalse(plugin.isEnabled()); - }); - }); - - [ - 'gui', - 'merge-reports' - ].forEach((commandName) => { - describe(`${commandName} command`, () => { - it('should register command on "CLI" event', () => { - const opts = {enabled: true}; - const commander = mkCommander_(commandName); - - parseConfig.withArgs(opts).returns(opts); - const plugin = toolReporter.create(tool, opts); - - plugin.addCliCommands(); - tool.emit(tool.events.CLI, commander); - - assert.calledOnceWith(cliCommands[commandName], commander, opts, tool); - }); - - it('should add api', () => { - const opts = {enabled: true}; - const plugin = toolReporter.create(tool, opts); - - assert.deepEqual(plugin.addApi(), plugin); - assert.instanceOf(tool.htmlReporter, HtmlReporter); - }); - - it(`should not register command if hermione called via API`, () => { - return initApiReporter_({}).then(() => assert.notCalled(cliCommands[commandName])); - }); - - it('should not init html-reporter on running command', () => { - return initCliReporter_({}, {command: commandName}).then(() => assert.notCalled(StaticReportBuilder.create)); - }); - }); - }); - - describe('html-reporter', () => { - let reportBuilder; - - beforeEach(() => { - reportBuilder = Object.create(StaticReportBuilder.prototype); - StaticReportBuilder.create.returns(reportBuilder); - }); - - it(`should init html-reporter if hermione called via API`, () => { - return initApiReporter_({}).then(() => assert.calledOnce(StaticReportBuilder.create)); - }); - - it('should prepare data', () => { - return initCliReporter_({}, {}) - .then(() => assert.calledOnceWith(prepareData, tool, reportBuilder)); - }); - - it('should save report', () => { - return initCliReporter_({}, {}) - .then(() => { - tool.emit(tool.events.END); - - return tool.emitAsync(tool.events.RUNNER_END).then(() => { - assert.calledOnce(StaticReportBuilder.prototype.finalize); - }); - }); - }); - - it('should emit REPORT_SAVED event', async () => { - await initCliReporter_({path: '/some/report/path'}, {}); - - tool.emit(tool.events.END); - await tool.emitAsync(tool.events.RUNNER_END); - - assert.calledOnceWith(tool.htmlReporter.emitAsync, 'reportSaved', {reportPath: '/some/report/path'}); - }); - - it('should log correct path to html report', () => { - return initCliReporter_({path: 'some/path'}, {}) - .then(() => { - tool.emit(tool.events.END); - - return tool.emitAsync(tool.events.RUNNER_END).then(() => { - assert.calledWithMatch(logger.log, 'some/path'); - }); - }); - }); - - it('should log an error', () => { - StaticReportBuilder.prototype.finalize.rejects('some-error'); - - return initCliReporter_({}, {}) - .then(() => { - tool.emit(tool.events.END); - - return tool.emitAsync(tool.events.RUNNER_END).then(() => { - assert.calledWith(logger.error, sinon.match('Html-reporter runtime error: some-error')); - }); - }); - }); - }); -}); From 6d6ba056ba3d5fbb1adcb7275e81cc005c89d02c Mon Sep 17 00:00:00 2001 From: shadowusr Date: Wed, 6 Dec 2023 10:41:58 +0300 Subject: [PATCH 08/17] fix: fix review issues --- lib/report-builder/gui.ts | 2 +- lib/test-adapter/hermione.ts | 1 - lib/test-attempt-manager.ts | 63 ++++++++++++++---------------------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts index e6b527441..4a7738273 100644 --- a/lib/report-builder/gui.ts +++ b/lib/report-builder/gui.ts @@ -186,7 +186,7 @@ export class GuiReportBuilder extends StaticReportBuilder { } private _updateTestResultStatus(testResult: PreparedTestResult, formattedResult: ReporterTestResult): void { - if (!hasResultFails(testResult) && !isSkippedStatus(testResult.status)) { + if (!hasResultFails(testResult) && !isSkippedStatus(testResult.status) && _.isEmpty(testResult.error)) { testResult.status = SUCCESS; return; } diff --git a/lib/test-adapter/hermione.ts b/lib/test-adapter/hermione.ts index 829fea3d4..5e99154d8 100644 --- a/lib/test-adapter/hermione.ts +++ b/lib/test-adapter/hermione.ts @@ -16,7 +16,6 @@ import { import {ImagesInfoFormatter} from '../image-handler'; import {ReporterTestResult} from './index'; import {getSuitePath} from '../plugin-utils'; -// import {testsAttempts} from './cache/hermione'; const getSkipComment = (suite: HermioneTestResult | HermioneSuite): string | null | undefined => { return suite.skipReason || suite.parent && getSkipComment(suite.parent); diff --git a/lib/test-attempt-manager.ts b/lib/test-attempt-manager.ts index a6e3305a1..6de18d2b2 100644 --- a/lib/test-attempt-manager.ts +++ b/lib/test-attempt-manager.ts @@ -1,71 +1,56 @@ -import _ from 'lodash'; import {ReporterTestResult} from './test-adapter'; -import {IDLE, RUNNING, SKIPPED, TestStatus, UPDATED} from './constants'; +import {IDLE, RUNNING, TestStatus} from './constants'; type TestSpec = Pick -const INVALID_ATTEMPT = -1; +interface AttemptData { + statuses: TestStatus[]; +} export class TestAttemptManager { - private _attemptMap: Map; - private _statusMap: Map; + private _attempts: Map; constructor() { - this._attemptMap = new Map(); - this._statusMap = new Map(); + this._attempts = new Map(); } removeAttempt(testResult: TestSpec): number { - const hash = this._getHash(testResult); - let value = this._attemptMap.get(hash); + const [hash, data] = this._getData(testResult); - if (_.isNil(value) || value === 0) { - value = INVALID_ATTEMPT; - } else { - value -= 1; - - const statuses = this._statusMap.get(hash) as TestStatus[]; - statuses.pop(); + if (data.statuses.length > 0) { + data.statuses.pop(); } - this._attemptMap.set(hash, value); + this._attempts.set(hash, data); - return value; + return Math.max(data.statuses.length - 1, 0); } getCurrentAttempt(testResult: TestSpec): number { - const hash = this._getHash(testResult); + const [, data] = this._getData(testResult); - return this._attemptMap.get(hash) ?? INVALID_ATTEMPT; + return Math.max(data.statuses.length - 1, 0); } registerAttempt(testResult: TestSpec, status: TestStatus): number { - const hash = this._getHash(testResult); - - let attempt = this._attemptMap.get(hash); - const statuses = this._statusMap.get(hash) ?? []; - - if (_.isNil(attempt)) { - this._attemptMap.set(hash, 0); - statuses.push(status); - this._statusMap.set(hash, statuses); + const [hash, data] = this._getData(testResult); - return 0; + if (![IDLE, RUNNING].includes(status)) { + data.statuses.push(status); } - if ([IDLE, RUNNING, SKIPPED, UPDATED].includes(_.last(statuses) as TestStatus)) { - return attempt; - } - - attempt += 1; - this._attemptMap.set(hash, attempt); - statuses.push(status); - this._statusMap.set(hash, statuses); + this._attempts.set(hash, data); - return attempt; + return Math.max(data.statuses.length - 1, 0); } private _getHash(testResult: TestSpec): string { return `${testResult.fullName}.${testResult.browserId}`; } + + private _getData(testResult: TestSpec): [string, AttemptData] { + const hash = this._getHash(testResult); + + return [hash, this._attempts.get(hash) ?? {statuses: []}]; + } } From fc00f1cdd01892021ac68d85035fef8f3d908a6e Mon Sep 17 00:00:00 2001 From: shadowusr Date: Wed, 6 Dec 2023 12:00:29 +0300 Subject: [PATCH 09/17] test: fix tests building --- test/unit/lib/test-adapter/hermione.ts | 4 ++-- tsconfig.spec.json | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/unit/lib/test-adapter/hermione.ts b/test/unit/lib/test-adapter/hermione.ts index 26eb7983a..153044060 100644 --- a/test/unit/lib/test-adapter/hermione.ts +++ b/test/unit/lib/test-adapter/hermione.ts @@ -4,7 +4,7 @@ import proxyquire from 'proxyquire'; import sinon from 'sinon'; import tmpOriginal from 'tmp'; -import {SKIPPED, TestStatus} from 'lib/constants/test-statuses'; +import {TestStatus} from 'lib/constants/test-statuses'; import {ERROR_DETAILS_PATH} from 'lib/constants/paths'; import {HermioneTestAdapter, HermioneTestAdapterOptions, ReporterTestResult} from 'lib/test-adapter'; import {ErrorDetails, HermioneTestResult} from 'lib/types'; @@ -30,7 +30,7 @@ describe('HermioneTestAdapter', () => { testResult: HermioneTestResult, {status = TestStatus.SUCCESS, imagesInfoFormatter = mkImagesInfoFormatter()}: {status?: TestStatus, imagesInfoFormatter?: ImagesInfoFormatter} = {} ): HermioneTestAdapter => { - return new HermioneTestAdapter(testResult, {status, imagesInfoFormatter}) as HermioneTestAdapter; + return new HermioneTestAdapter(testResult, {status, imagesInfoFormatter, attempt: 0}) as HermioneTestAdapter; }; const mkTestResult_ = (result: Partial): HermioneTestResult => _.defaults(result, { diff --git a/tsconfig.spec.json b/tsconfig.spec.json index fc10e273c..52affde5f 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -3,11 +3,10 @@ "include": ["lib", "test", "hermione.ts", "gemini.js", "playwright.ts"], "exclude": ["test/func"], "compilerOptions": { - "baseUrl": ".", "jsx": "react", "noEmit": true, "paths": { - "lib/*": ["lib/*"] + "lib/*": ["./lib/*"] } }, } From ab23dd4f31c48f88aadbd04af5a2afe8897008ff Mon Sep 17 00:00:00 2001 From: shadowusr Date: Thu, 7 Dec 2023 02:55:11 +0300 Subject: [PATCH 10/17] fix: fix test attempt manager, review issues, status computing --- lib/common-utils.ts | 23 ++++++++++- lib/gui/tool-runner/index.ts | 39 +++++++++++++----- lib/report-builder/gui.ts | 40 ++---------------- lib/report-builder/static.ts | 5 ++- lib/sqlite-client.ts | 1 + lib/static/modules/reducers/tree/index.js | 19 ++++++++- .../modules/reducers/tree/nodes/suites.js | 4 +- lib/test-adapter/utils/index.ts | 41 ++++++++++++++++++- lib/test-attempt-manager.ts | 10 +++-- lib/tests-tree-builder/base.ts | 4 +- lib/tests-tree-builder/static.ts | 1 + lib/types.ts | 1 + test/unit/lib/common-utils.ts | 36 ++++++++-------- test/unit/lib/tests-tree-builder/base.js | 8 ++-- 14 files changed, 147 insertions(+), 85 deletions(-) diff --git a/lib/common-utils.ts b/lib/common-utils.ts index adc2f6251..50f499362 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -7,6 +7,7 @@ import {SUCCESS, FAIL, ERROR, SKIPPED, UPDATED, IDLE, RUNNING, QUEUED, TestStatu import {UNCHECKED, INDETERMINATE, CHECKED} from './constants/checked-statuses'; import {ImageData, ImageBase64, ImageInfoFull, TestError, ImageInfoError} from './types'; import {ErrorName, ImageDiffError, NoRefImageError} from './errors'; +import {ReporterTestResult} from './test-adapter'; export const getShortMD5 = (str: string): string => { return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7); }; @@ -29,7 +30,7 @@ export const isErrorStatus = (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: TestStatus[]): TestStatus | null => { +export const determineFinalStatus = (statuses: TestStatus[]): TestStatus | null => { if (!statuses.length) { return SUCCESS; } @@ -103,7 +104,7 @@ export const hasNoRefImageErrors = ({assertViewResults = []}: {assertViewResults return assertViewResults.some((assertViewResult) => isNoRefImageError(assertViewResult)); }; -const hasFailedImages = (result: {imagesInfo?: ImageInfoFull[]}): boolean => { +export const hasFailedImages = (result: {imagesInfo?: ImageInfoFull[]}): boolean => { const {imagesInfo = []} = result; return imagesInfo.some((imageInfo: ImageInfoFull) => { @@ -128,6 +129,24 @@ export const hasDiff = (assertViewResults: {name?: string}[]): boolean => { return assertViewResults.some((result) => isImageDiffError(result as {name?: string})); }; +/* This method tries to determine true status of testResult by using fields like error, imagesInfo */ +export const determineStatus = (testResult: Pick): TestStatus => { + if (!hasFailedImages(testResult) && !isSkippedStatus(testResult.status) && isEmpty(testResult.error)) { + return SUCCESS; + } + + const imageErrors = (testResult.imagesInfo ?? []).map(imagesInfo => (imagesInfo as {error: {name?: string}}).error ?? {}); + if (hasDiff(imageErrors) || hasNoRefImageErrors({assertViewResults: imageErrors})) { + return FAIL; + } + + if (!isEmpty(testResult.error)) { + return ERROR; + } + + return testResult.status; +}; + export const isBase64Image = (image: ImageData | ImageBase64 | null | undefined): image is ImageBase64 => { return Boolean((image as ImageBase64 | undefined)?.base64); }; diff --git a/lib/gui/tool-runner/index.ts b/lib/gui/tool-runner/index.ts index 8d50d32e2..0fa45ca8c 100644 --- a/lib/gui/tool-runner/index.ts +++ b/lib/gui/tool-runner/index.ts @@ -72,6 +72,8 @@ const formatTestResultUnsafe = ( return formatTestResult(test as HermioneTestResult, status, attempt, {imageHandler}); }; +const HERMIONE_TITLE_DELIMITER = ' '; + export class ToolRunner { private _testFiles: string[]; private _hermione: Hermione & HtmlReporterApi; @@ -84,6 +86,7 @@ export class ToolRunner { private _eventSource: EventSource; protected _reportBuilder: GuiReportBuilder | null; private _tests: Record; + private readonly _testAttemptManager: TestAttemptManager; static create(this: new (...args: ToolRunnerArgs) => T, ...args: ToolRunnerArgs): T { return new this(...args); @@ -104,6 +107,7 @@ export class ToolRunner { this._reportBuilder = null; this._tests = {}; + this._testAttemptManager = new TestAttemptManager(); } get config(): HermioneConfig { @@ -118,9 +122,8 @@ export class ToolRunner { await mergeDatabasesForReuse(this._reportPath); const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._reportPath, reuse: true}); - const testAttemptManager = new TestAttemptManager(); - this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient, testAttemptManager}); + this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient, testAttemptManager: this._testAttemptManager}); this._subscribeOnEvents(); this._collection = await this._readTests(); @@ -185,10 +188,11 @@ export class ToolRunner { return Promise.all(tests.map(async (test): Promise => { const updateResult = this._prepareTestResult(test); - const fullName = test.suite.path.join(' '); + const fullName = [...test.suite.path, test.state.name].join(HERMIONE_TITLE_DELIMITER); const updateAttempt = reportBuilder.testAttemptManager.registerAttempt({fullName, browserId: test.browserId}, UPDATED); const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt, reportBuilder); - const failResultId = formattedResult.id; + + const failResultId = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt - 1, reportBuilder).id; updateResult.attempt = updateAttempt; @@ -213,8 +217,8 @@ export class ToolRunner { await Promise.all(tests.map(async (test) => { const updateResult = this._prepareTestResult(test); - const fullName = test.suite.path.join(' '); - const attempt = reportBuilder.testAttemptManager.removeAttempt({fullName, browserId: test.browserId}); + const fullName = [...test.suite.path, test.state.name].join(' '); + const attempt = reportBuilder.testAttemptManager.getCurrentAttempt({fullName, browserId: test.browserId}); const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, attempt, reportBuilder); await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => { @@ -318,11 +322,10 @@ export class ToolRunner { const testId = formatId(test.id.toString(), browserId); this._tests[testId] = _.extend(test, {browserId}); + const attempt = 0; if (test.pending) { - const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, SKIPPED); reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, attempt, reportBuilder)); } else { - const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, IDLE); reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, attempt, reportBuilder)); } }); @@ -358,7 +361,15 @@ export class ToolRunner { return _.extend(imageInfo, {expectedImg: refImg}); }); - const res = _.merge({}, rawTest, {assertViewResults, imagesInfo, sessionId, attempt, meta: {url}, updated: true}); + const res = _.merge({}, rawTest, { + assertViewResults, + err: test.error, + 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 @@ -380,8 +391,16 @@ export class ToolRunner { const {autoRun} = this._guiOpts; const testsTree = await this._loadDataFromDatabase(); - if (!_.isEmpty(testsTree)) { + if (testsTree && !_.isEmpty(testsTree)) { reportBuilder.reuseTestsTree(testsTree); + + // Fill test attempt manager with data from db + for (const [, testResult] of Object.entries(testsTree.results.byId)) { + this._testAttemptManager.registerAttempt({ + fullName: testResult.suitePath.join(HERMIONE_TITLE_DELIMITER), + browserId: testResult.name + }, testResult.status, testResult.attempt); + } } this._tree = {...reportBuilder.getResult(), autoRun}; diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts index 4a7738273..0b17c7b2d 100644 --- a/lib/report-builder/gui.ts +++ b/lib/report-builder/gui.ts @@ -2,14 +2,14 @@ import * as _ from 'lodash'; import {StaticReportBuilder} from './static'; import {GuiTestsTreeBuilder, TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui'; import { - IDLE, RUNNING, FAIL, SUCCESS, UPDATED, TestStatus, DB_COLUMNS, ToolName, ERROR + IDLE, RUNNING, UPDATED, TestStatus, DB_COLUMNS, ToolName } from '../constants'; import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils'; import {ReporterTestResult} from '../test-adapter'; import {PreparedTestResult} from '../sqlite-client'; -import {Tree, TreeImage, TreeResult} from '../tests-tree-builder/base'; +import {Tree, TreeImage} from '../tests-tree-builder/base'; import {ImageInfoWithState, ReporterConfig} from '../types'; -import {hasDiff, hasNoRefImageErrors, hasResultFails, isSkippedStatus, isUpdatedStatus} from '../common-utils'; +import {isUpdatedStatus} from '../common-utils'; import {HtmlReporterValues} from '../plugin-api'; import {SkipItem} from '../tests-tree-builder/static'; import {copyAndUpdate} from '../test-adapter/utils'; @@ -164,45 +164,11 @@ export class GuiReportBuilder extends StaticReportBuilder { 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) && _.isEmpty(testResult.error)) { - 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; diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index 3d8910e2c..05c28429f 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -143,7 +143,7 @@ export class StaticReportBuilder { protected _createTestResult(result: ReporterTestResult, props: {attempt?: number | null, status: TestStatus, timestamp: number;} & Partial): PreparedTestResult { const { browserId, file, sessionId, description, history, - imagesInfo = [], screenshot, multipleTabs, errorDetails + imagesInfo = [], screenshot, multipleTabs, errorDetails, testPath } = result; const {baseHost, saveErrorDetails} = this._pluginConfig; @@ -153,7 +153,8 @@ export class StaticReportBuilder { const testResult: PreparedTestResult = Object.assign({ suiteUrl, name: browserId, metaInfo, description, history, - imagesInfo, screenshot: Boolean(screenshot), multipleTabs + imagesInfo, screenshot: Boolean(screenshot), multipleTabs, + suitePath: testPath }, props); const error = getError(result.error); diff --git a/lib/sqlite-client.ts b/lib/sqlite-client.ts index 525fb5df9..d399aeb01 100644 --- a/lib/sqlite-client.ts +++ b/lib/sqlite-client.ts @@ -44,6 +44,7 @@ export interface PreparedTestResult { status: TestStatus; timestamp: number; errorDetails?: ErrorDetails; + suitePath: string[]; } export interface DbTestResult { diff --git a/lib/static/modules/reducers/tree/index.js b/lib/static/modules/reducers/tree/index.js index 4c7236100..91667cd65 100644 --- a/lib/static/modules/reducers/tree/index.js +++ b/lib/static/modules/reducers/tree/index.js @@ -1,4 +1,4 @@ -import {findLast, isEmpty} from 'lodash'; +import {findLast, isEmpty, pick} from 'lodash'; import {produce} from 'immer'; import actionNames from '../../action-names'; import { @@ -16,7 +16,7 @@ import { import {ViewMode} from '../../../../constants/view-modes'; import {EXPAND_RETRIES} from '../../../../constants/expand-modes'; import {FAIL} from '../../../../constants/test-statuses'; -import {isSuccessStatus} from '../../../../common-utils'; +import {determineStatus, isSuccessStatus, isUpdatedStatus} from '../../../../common-utils'; import {applyStateUpdate, ensureDiffProperty, getUpdatedProperty} from '../../utils/state'; export default ((state, action) => { @@ -37,6 +37,7 @@ export default ((state, action) => { updateAllSuitesStatus(tree, filteredBrowsers); initNodesStates({tree, view: state.view}); + // TODO: handle "updated" status here return {...state, tree}; } @@ -350,6 +351,20 @@ function addNodesToTree(state, payload) { const {tree, view} = state; [].concat(payload).forEach(({result, images, suites}) => { + if (isUpdatedStatus(result.status)) { + const computedStatus = determineStatus({ + status: result.status, + error: result.error, + imagesInfo: Object.values(pick(tree.images.byId, result.imageIds)) + }); + + result.status = computedStatus; + + for (const suite of suites.filter(suite => isUpdatedStatus(suite.status))) { + suite.status = computedStatus; + } + } + addResult(tree, result); setBrowsersLastRetry(tree, result.parentId); addImages(tree, images, view.expand); diff --git a/lib/static/modules/reducers/tree/nodes/suites.js b/lib/static/modules/reducers/tree/nodes/suites.js index 5a51430a3..56c674e72 100644 --- a/lib/static/modules/reducers/tree/nodes/suites.js +++ b/lib/static/modules/reducers/tree/nodes/suites.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import {getSuiteResults} from '../../../selectors/tree'; import {isNodeFailed} from '../../../utils'; import {ensureDiffProperty, getUpdatedProperty} from '../../../utils/state'; -import {determineStatus, isFailStatus} from '../../../../../common-utils'; +import {determineFinalStatus, isFailStatus} from '../../../../../common-utils'; import {changeNodeState, getShownCheckedChildCount, shouldNodeBeOpened} from '../helpers'; import {EXPAND_RETRIES} from '../../../../../constants/expand-modes'; import {FAIL} from '../../../../../constants/test-statuses'; @@ -285,5 +285,5 @@ function getChildSuitesStatus(tree, suite, filteredBrowsers, diff = tree) { childStatuses = childStatuses.concat(suiteBrowserStatuses); } - return determineStatus(childStatuses); + return determineFinalStatus(childStatuses); } diff --git a/lib/test-adapter/utils/index.ts b/lib/test-adapter/utils/index.ts index 58e91efa7..76d96f36d 100644 --- a/lib/test-adapter/utils/index.ts +++ b/lib/test-adapter/utils/index.ts @@ -1,5 +1,42 @@ import _ from 'lodash'; import {ReporterTestResult} from '../index'; +import {TupleToUnion} from 'type-fest'; -export const copyAndUpdate = (original: ReporterTestResult, updates: Partial): ReporterTestResult => - _.assign({}, original, updates); +export const copyAndUpdate = (original: ReporterTestResult, updates: Partial): ReporterTestResult => { + const keys = [ + 'assertViewResults', + 'attempt', + 'browserId', + 'description', + 'error', + 'errorDetails', + 'file', + 'fullName', + 'history', + 'id', + 'image', + 'imageDir', + 'imagesInfo', + 'isUpdated', + 'meta', + 'multipleTabs', + 'screenshot', + 'sessionId', + 'skipReason', + 'state', + 'status', + 'testPath', + 'timestamp', + 'url' + ] as const; + + // Type-level check that we didn't forget to include any keys + type A = TupleToUnion; + type B = keyof ReporterTestResult; + + const keysTypeChecked: B extends A ? + B extends A ? typeof keys : never + : never = keys; + + return _.assign({}, _.pick(original, keysTypeChecked) as ReporterTestResult, updates); +}; diff --git a/lib/test-attempt-manager.ts b/lib/test-attempt-manager.ts index 6de18d2b2..2b65263e5 100644 --- a/lib/test-attempt-manager.ts +++ b/lib/test-attempt-manager.ts @@ -32,12 +32,14 @@ export class TestAttemptManager { return Math.max(data.statuses.length - 1, 0); } - registerAttempt(testResult: TestSpec, status: TestStatus): number { + registerAttempt(testResult: TestSpec, status: TestStatus, index: number | null = null): number { const [hash, data] = this._getData(testResult); - if (![IDLE, RUNNING].includes(status)) { - data.statuses.push(status); - } + const isManualOverride = index !== null; + const isLastStatusTemporary = [IDLE, RUNNING].includes(data.statuses.at(-1) as TestStatus); + const shouldReplace = Number(isManualOverride || isLastStatusTemporary); + + data.statuses.splice(index ?? data.statuses.length - shouldReplace, shouldReplace, status); this._attempts.set(hash, data); diff --git a/lib/tests-tree-builder/base.ts b/lib/tests-tree-builder/base.ts index 69ea40d17..ee71e270f 100644 --- a/lib/tests-tree-builder/base.ts +++ b/lib/tests-tree-builder/base.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import {determineStatus} from '../common-utils'; +import {determineFinalStatus} from '../common-utils'; import {BrowserVersions, PWT_TITLE_DELIMITER, TestStatus, ToolName} from '../constants'; import {ReporterTestResult} from '../test-adapter'; import {ImageInfoFull, ParsedSuitesRow} from '../types'; @@ -253,7 +253,7 @@ export class BaseTestsTreeBuilder { const childrenSuiteStatuses = _.compact(([] as (string | undefined)[]).concat(suite.suiteIds)) .map((childSuiteId: string) => this._tree.suites.byId[childSuiteId].status); - const status = determineStatus(_.compact([...resultStatuses, ...childrenSuiteStatuses])); + const status = determineFinalStatus(_.compact([...resultStatuses, ...childrenSuiteStatuses])); // if newly determined status is the same as current status, do nothing if (suite.status === status) { diff --git a/lib/tests-tree-builder/static.ts b/lib/tests-tree-builder/static.ts index b7a44aa39..b053d46f4 100644 --- a/lib/tests-tree-builder/static.ts +++ b/lib/tests-tree-builder/static.ts @@ -201,6 +201,7 @@ function mkTestResult(row: RawSuitesRow, data: {attempt: number}): ParsedSuitesR name: row[DB_COLUMN_INDEXES.name] as string, screenshot: Boolean(row[DB_COLUMN_INDEXES.screenshot]), status: row[DB_COLUMN_INDEXES.status] as TestStatus, + suitePath: JSON.parse(row[DB_COLUMN_INDEXES.suitePath] as string), 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), diff --git a/lib/types.ts b/lib/types.ts index 88107fd06..d74b76ee8 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -133,6 +133,7 @@ export interface ParsedSuitesRow { screenshot: boolean; skipReason?: string; status: TestStatus; + suitePath: string[]; suiteUrl: string; timestamp: number; } diff --git a/test/unit/lib/common-utils.ts b/test/unit/lib/common-utils.ts index 98b0a0f1d..995d651de 100644 --- a/test/unit/lib/common-utils.ts +++ b/test/unit/lib/common-utils.ts @@ -1,4 +1,4 @@ -import {determineStatus, getError, hasDiff, getUrlWithBase} from 'lib/common-utils'; +import {determineFinalStatus, getError, hasDiff, getUrlWithBase} from 'lib/common-utils'; import {RUNNING, QUEUED, ERROR, FAIL, UPDATED, SUCCESS, IDLE, SKIPPED} from 'lib/constants/test-statuses'; import {ErrorName} from 'lib/errors'; @@ -66,13 +66,13 @@ describe('common-utils', () => { describe('determineStatus', () => { it(`should not rewrite suite status to "${IDLE}" if some test already has final status`, () => { - const status = determineStatus([SUCCESS, IDLE]); + const status = determineFinalStatus([SUCCESS, IDLE]); assert.equal(status, SUCCESS); }); it(`should return "${SUCCESS}" if statuses is not passed`, () => { - const status = determineStatus([]); + const status = determineFinalStatus([]); assert.equal(status, SUCCESS); }); @@ -81,8 +81,8 @@ describe('common-utils', () => { it(`should return "${RUNNING}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS, UPDATED, FAIL, ERROR, QUEUED, RUNNING]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, RUNNING); assert.equal(statusInReverseOrder, RUNNING); @@ -91,8 +91,8 @@ describe('common-utils', () => { it(`should return "${QUEUED}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS, UPDATED, FAIL, ERROR, QUEUED]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, QUEUED); assert.equal(statusInReverseOrder, QUEUED); @@ -101,8 +101,8 @@ describe('common-utils', () => { it(`should return "${ERROR}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS, UPDATED, FAIL, ERROR]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, ERROR); assert.equal(statusInReverseOrder, ERROR); @@ -111,8 +111,8 @@ describe('common-utils', () => { it(`should return "${FAIL}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS, UPDATED, FAIL]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, FAIL); assert.equal(statusInReverseOrder, FAIL); @@ -121,8 +121,8 @@ describe('common-utils', () => { it(`should return "${UPDATED}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS, UPDATED]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, UPDATED); assert.equal(statusInReverseOrder, UPDATED); @@ -131,8 +131,8 @@ describe('common-utils', () => { it(`should return "${SUCCESS}" regardless of order`, () => { const statuses = [SKIPPED, IDLE, SUCCESS]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, SUCCESS); assert.equal(statusInReverseOrder, SUCCESS); @@ -141,15 +141,15 @@ describe('common-utils', () => { it(`should return "${IDLE}" regardless of order`, () => { const statuses = [SKIPPED, IDLE]; - const statusInDirectOrder = determineStatus(statuses); - const statusInReverseOrder = determineStatus(statuses.reverse()); + const statusInDirectOrder = determineFinalStatus(statuses); + const statusInReverseOrder = determineFinalStatus(statuses.reverse()); assert.equal(statusInDirectOrder, IDLE); assert.equal(statusInReverseOrder, IDLE); }); it(`should return "${SKIPPED}"`, () => { - const status = determineStatus([SKIPPED]); + const status = determineFinalStatus([SKIPPED]); assert.equal(status, SKIPPED); }); diff --git a/test/unit/lib/tests-tree-builder/base.js b/test/unit/lib/tests-tree-builder/base.js index de57131ea..cd0b3ab05 100644 --- a/test/unit/lib/tests-tree-builder/base.js +++ b/test/unit/lib/tests-tree-builder/base.js @@ -247,7 +247,7 @@ describe('ResultsTreeBuilder', () => { }); describe('determine statuses for suites', () => { - it('should call "determineStatus" with test result status', () => { + it('should call "determineFinalStatus" with test result status', () => { builder.addTestResult( mkTestResult_({status: SUCCESS}), mkFormattedResult_({testPath: ['s1']}) @@ -256,7 +256,7 @@ describe('ResultsTreeBuilder', () => { assert.calledOnceWith(determineStatus, [SUCCESS]); }); - it('should call "determineStatus" with test result status from last attempt', () => { + it('should call "determineFinalStatus" with test result status from last attempt', () => { builder.addTestResult( mkTestResult_({status: FAIL}), mkFormattedResult_({testPath: ['s1'], attempt: 0}) @@ -269,7 +269,7 @@ describe('ResultsTreeBuilder', () => { assert.calledWith(determineStatus.lastCall, [SUCCESS]); }); - it('should call "determineStatus" with all test statuses from each browser', () => { + it('should call "determineFinalStatus" with all test statuses from each browser', () => { builder.addTestResult( mkTestResult_({status: FAIL}), mkFormattedResult_({testPath: ['s1'], browserId: 'b1'}) @@ -282,7 +282,7 @@ describe('ResultsTreeBuilder', () => { assert.calledWith(determineStatus.secondCall, [FAIL, SUCCESS]); }); - it('should call "determineStatus" with statuses from child suites', () => { + it('should call "determineFinalStatus" with statuses from child suites', () => { determineStatus.withArgs([FAIL]).returns('s1 s2 status'); determineStatus.withArgs([ERROR]).returns('s1 s3 status'); builder.addTestResult( From 9ec97dd72febd8842d6edb7a0761b938fb7f44f0 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Thu, 7 Dec 2023 11:32:04 +0300 Subject: [PATCH 11/17] draft: save changes --- hermione.ts | 11 ++++----- lib/static/modules/reducers/tree/helpers.js | 26 ++++++++++++++++++++- lib/static/modules/reducers/tree/index.js | 19 ++++----------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/hermione.ts b/hermione.ts index b85cc13ca..eabb750f1 100644 --- a/hermione.ts +++ b/hermione.ts @@ -8,7 +8,7 @@ import {CommanderStatic} from '@gemini-testing/commander'; import {cliCommands} from './lib/cli-commands'; import {hasDiff} from './lib/common-utils'; import {parseConfig} from './lib/config'; -import {FAIL, SUCCESS, ToolName} from './lib/constants'; +import {ERROR, FAIL, SUCCESS, ToolName} from './lib/constants'; import {HtmlReporter} from './lib/plugin-api'; import {StaticReportBuilder} from './lib/report-builder/static'; import {formatTestResult, logPathToHtmlReport, logError} from './lib/server-utils'; @@ -88,8 +88,9 @@ async function handleTestResults(hermione: Hermione, reportBuilder: StaticReport const {imageHandler} = reportBuilder; const failHandler = async (testResult: HermioneTestResult): Promise => { - const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); - const formattedResult = formatTestResult(testResult, FAIL, attempt, reportBuilder); + const status = hasDiff(testResult.assertViewResults as {name?: string}[]) ? FAIL : ERROR; + const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); + const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; @@ -103,9 +104,7 @@ async function handleTestResults(hermione: Hermione, reportBuilder: StaticReport }; const addFail = (formattedResult: ReporterTestResult): ReporterTestResult => { - return hasDiff(formattedResult.assertViewResults as {name?: string}[]) - ? reportBuilder.addFail(formattedResult) - : reportBuilder.addError(formattedResult); + return reportBuilder.addFail(formattedResult); }; return new Promise((resolve, reject) => { diff --git a/lib/static/modules/reducers/tree/helpers.js b/lib/static/modules/reducers/tree/helpers.js index afcc132bd..5cb15a46f 100644 --- a/lib/static/modules/reducers/tree/helpers.js +++ b/lib/static/modules/reducers/tree/helpers.js @@ -1,6 +1,7 @@ -import {get, set} from 'lodash'; +import {get, pick, set} from 'lodash'; import {EXPAND_ALL, EXPAND_ERRORS, EXPAND_RETRIES} from '../../../../constants/expand-modes'; import {getUpdatedProperty} from '../../utils/state'; +import {determineStatus, isUpdatedStatus} from '../../../../common-utils'; export function changeNodeState(nodesStateById, nodeId, state, diff = nodesStateById) { Object.keys(state).forEach((stateName) => { @@ -45,3 +46,26 @@ export function getShownCheckedChildCount(tree, suiteId, diff = tree) { return checkedChildBrowserCount + checkedChildSuitesCount; } + +export function resolveUpdatedStatuses(results, imagesById, suites) { + for (const result of Object.values(results)) { + if (!isUpdatedStatus(result.status)) { + continue; + } + const computedStatus = determineStatus({ + status: result.status, + error: result.error, + imagesInfo: Object.values(pick(imagesById, result.imageIds)) + }); + + result.status = computedStatus; + + const suitesFilteredKeys = Object.entries(suites) + .filter(([, suite]) => result.id.startsWith(suite.id) && isUpdatedStatus(suite.status)) + .map(([key]) => key); + + for (const suiteKey of suitesFilteredKeys) { + suites[suiteKey].status = computedStatus; + } + } +} diff --git a/lib/static/modules/reducers/tree/index.js b/lib/static/modules/reducers/tree/index.js index 91667cd65..bdcac6e56 100644 --- a/lib/static/modules/reducers/tree/index.js +++ b/lib/static/modules/reducers/tree/index.js @@ -16,8 +16,9 @@ import { import {ViewMode} from '../../../../constants/view-modes'; import {EXPAND_RETRIES} from '../../../../constants/expand-modes'; import {FAIL} from '../../../../constants/test-statuses'; -import {determineStatus, isSuccessStatus, isUpdatedStatus} from '../../../../common-utils'; +import {isSuccessStatus} from '../../../../common-utils'; import {applyStateUpdate, ensureDiffProperty, getUpdatedProperty} from '../../utils/state'; +import {resolveUpdatedStatuses} from './helpers'; export default ((state, action) => { const diff = {tree: {}}; @@ -37,7 +38,7 @@ export default ((state, action) => { updateAllSuitesStatus(tree, filteredBrowsers); initNodesStates({tree, view: state.view}); - // TODO: handle "updated" status here + resolveUpdatedStatuses(tree.results.byId, tree.images.byId, tree.suites.byId); return {...state, tree}; } @@ -351,19 +352,7 @@ function addNodesToTree(state, payload) { const {tree, view} = state; [].concat(payload).forEach(({result, images, suites}) => { - if (isUpdatedStatus(result.status)) { - const computedStatus = determineStatus({ - status: result.status, - error: result.error, - imagesInfo: Object.values(pick(tree.images.byId, result.imageIds)) - }); - - result.status = computedStatus; - - for (const suite of suites.filter(suite => isUpdatedStatus(suite.status))) { - suite.status = computedStatus; - } - } + resolveUpdatedStatuses([result], tree.images.byId, suites); addResult(tree, result); setBrowsersLastRetry(tree, result.parentId); From 182022557400a93d2877ce71fa34c7cb25fafb97 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 01:14:11 +0300 Subject: [PATCH 12/17] fix: fix unit and e2e tests, further refactoring --- hermione.ts | 108 ++++++------- lib/common-utils.ts | 22 +-- lib/constants/tests.ts | 4 + lib/gui/tool-runner/index.ts | 66 +++----- lib/gui/tool-runner/report-subscriber.ts | 68 +++----- lib/image-handler.ts | 45 +++--- lib/report-builder/gui.ts | 56 ++++--- lib/report-builder/static.ts | 85 +++++++--- lib/reporter-helpers.ts | 5 +- lib/server-utils.ts | 14 ++ lib/sqlite-client.ts | 1 + lib/static/components/retry-switcher/item.jsx | 14 +- lib/static/modules/reducers/tree/index.js | 2 +- lib/test-adapter/hermione.ts | 37 +---- lib/test-adapter/index.ts | 1 - lib/test-adapter/playwright.ts | 4 - lib/test-adapter/reporter.ts | 121 ++++++++++++++ lib/test-adapter/utils/index.ts | 34 +++- lib/tests-tree-builder/gui.ts | 15 -- lib/tests-tree-builder/static.ts | 1 + lib/types.ts | 4 +- playwright.ts | 22 +-- test/unit/hermione.js | 113 +------------ test/unit/lib/gui/tool-runner/index.js | 16 +- .../lib/gui/tool-runner/report-subsciber.js | 47 ++---- test/unit/lib/report-builder/gui.js | 81 ++++++---- test/unit/lib/report-builder/static.js | 148 +++++++++++++++++- test/unit/lib/test-adapter/hermione.ts | 61 ++------ test/unit/lib/tests-tree-builder/base.js | 18 +-- test/unit/lib/tests-tree-builder/static.js | 16 +- test/unit/utils.js | 22 ++- 31 files changed, 701 insertions(+), 550 deletions(-) create mode 100644 lib/test-adapter/reporter.ts diff --git a/hermione.ts b/hermione.ts index eabb750f1..385c3a700 100644 --- a/hermione.ts +++ b/hermione.ts @@ -6,37 +6,42 @@ import PQueue from 'p-queue'; import {CommanderStatic} from '@gemini-testing/commander'; import {cliCommands} from './lib/cli-commands'; -import {hasDiff} from './lib/common-utils'; +import {hasFailedImages} from './lib/common-utils'; import {parseConfig} from './lib/config'; -import {ERROR, FAIL, SUCCESS, ToolName} from './lib/constants'; +import {SKIPPED, SUCCESS, TestStatus, ToolName, UNKNOWN_ATTEMPT} from './lib/constants'; import {HtmlReporter} from './lib/plugin-api'; import {StaticReportBuilder} from './lib/report-builder/static'; import {formatTestResult, logPathToHtmlReport, logError} from './lib/server-utils'; import {SqliteClient} from './lib/sqlite-client'; -import {HermioneTestAdapter, ReporterTestResult} from './lib/test-adapter'; -import {TestAttemptManager} from './lib/test-attempt-manager'; -import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './lib/types'; +import {HtmlReporterApi, ImageInfoFull, ReporterOptions} from './lib/types'; import {createWorkers, CreateWorkersRunner} from './lib/workers/create-workers'; -let workers: ReturnType; - export = (hermione: Hermione, opts: Partial): void => { - if (hermione.isWorker()) { + if (hermione.isWorker() || !opts.enabled) { return; } const config = parseConfig(opts); - if (!config.enabled) { - return; - } - const htmlReporter = HtmlReporter.create(config, {toolName: ToolName.Hermione}); (hermione as Hermione & HtmlReporterApi).htmlReporter = htmlReporter; let isCliCommandLaunched = false; let handlingTestResults: Promise; + let staticReportBuilder: StaticReportBuilder; + + const withMiddleware = unknown>(fn: T): + (...args: Parameters) => ReturnType | undefined => { + return (...args: unknown[]) => { + // If any CLI command was launched, e.g. merge-reports, we need to interrupt regular flow + if (isCliCommandLaunched) { + return; + } + + return fn.call(undefined, ...args) as ReturnType; + }; + }; hermione.on(hermione.events.CLI, (commander: CommanderStatic) => { _.values(cliCommands).forEach((command: string) => { @@ -49,30 +54,27 @@ export = (hermione: Hermione, opts: Partial): void => { }); }); - hermione.on(hermione.events.INIT, async () => { - if (isCliCommandLaunched) { - return; - } - + hermione.on(hermione.events.INIT, withMiddleware(async () => { const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); - const testAttemptManager = new TestAttemptManager(); - const staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, testAttemptManager}); + staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient}); handlingTestResults = Promise.all([ staticReportBuilder.saveStaticFiles(), - handleTestResults(hermione, staticReportBuilder, config) + handleTestResults(hermione, staticReportBuilder) ]).then(async () => { await staticReportBuilder.finalize(); }).then(async () => { await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: config.path}); }); - }); - hermione.on(hermione.events.RUNNER_START, (runner) => { - workers = createWorkers(runner as unknown as CreateWorkersRunner); - }); + htmlReporter.emit(htmlReporter.events.DATABASE_CREATED, dbClient.getRawConnection()); + })); - hermione.on(hermione.events.RUNNER_END, async () => { + hermione.on(hermione.events.RUNNER_START, withMiddleware((runner) => { + staticReportBuilder.registerWorkers(createWorkers(runner as unknown as CreateWorkersRunner)); + })); + + hermione.on(hermione.events.RUNNER_END, withMiddleware(async () => { try { await handlingTestResults; @@ -80,57 +82,47 @@ export = (hermione: Hermione, opts: Partial): void => { } catch (e: unknown) { logError(e as Error); } - }); + })); }; -async function handleTestResults(hermione: Hermione, reportBuilder: StaticReportBuilder, pluginConfig: ReporterConfig): Promise { - const {path: reportPath} = pluginConfig; - const {imageHandler} = reportBuilder; - - const failHandler = async (testResult: HermioneTestResult): Promise => { - const status = hasDiff(testResult.assertViewResults as {name?: string}[]) ? FAIL : ERROR; - const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); - const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); - - const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; - - if (pluginConfig.saveErrorDetails && formattedResult.errorDetails) { - actions.push((formattedResult as HermioneTestAdapter).saveErrorDetails(reportPath)); - } - - await Promise.all(actions); - - return formattedResult; - }; - - const addFail = (formattedResult: ReporterTestResult): ReporterTestResult => { - return reportBuilder.addFail(formattedResult); - }; - +async function handleTestResults(hermione: Hermione, reportBuilder: StaticReportBuilder): Promise { return new Promise((resolve, reject) => { const queue = new PQueue({concurrency: os.cpus().length}); const promises: Promise[] = []; hermione.on(hermione.events.TEST_PASS, testResult => { promises.push(queue.add(async () => { - const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); - const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder); - await imageHandler.saveTestImages(formattedResult, workers); - - return reportBuilder.addSuccess(formattedResult); + const formattedResult = formatTestResult(testResult, SUCCESS, UNKNOWN_ATTEMPT, reportBuilder); + await reportBuilder.addSuccess(formattedResult); }).catch(reject)); }); hermione.on(hermione.events.RETRY, testResult => { - promises.push(queue.add(() => failHandler(testResult).then(addFail)).catch(reject)); + promises.push(queue.add(async () => { + const status = hasFailedImages(testResult.assertViewResults as ImageInfoFull[]) ? TestStatus.FAIL : TestStatus.ERROR; + + const formattedResult = formatTestResult(testResult, status, UNKNOWN_ATTEMPT, reportBuilder); + + await reportBuilder.addFail(formattedResult); + }).catch(reject)); }); hermione.on(hermione.events.TEST_FAIL, testResult => { - promises.push(queue.add(() => failHandler(testResult).then(addFail)).catch(reject)); + promises.push(queue.add(async () => { + const status = hasFailedImages(testResult.assertViewResults as ImageInfoFull[]) ? TestStatus.FAIL : TestStatus.ERROR; + + const formattedResult = formatTestResult(testResult, status, UNKNOWN_ATTEMPT, reportBuilder); + + await reportBuilder.addFail(formattedResult); + }).catch(reject)); }); hermione.on(hermione.events.TEST_PENDING, testResult => { - promises.push(queue.add(() => failHandler(testResult as HermioneTestResult).then((testResult) => reportBuilder.addSkipped(testResult)).catch(reject))); + promises.push(queue.add(async () => { + const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, UNKNOWN_ATTEMPT, reportBuilder); + + await reportBuilder.addSkipped(formattedResult); + }).catch(reject)); }); hermione.on(hermione.events.RUNNER_END, () => { diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 50f499362..9fdd4628b 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 {ImageData, ImageBase64, ImageInfoFull, TestError, ImageInfoError} from './types'; +import {ImageData, ImageBase64, ImageInfoFull, TestError, ImageInfoFail} from './types'; import {ErrorName, ImageDiffError, NoRefImageError} from './errors'; import {ReporterTestResult} from './test-adapter'; export const getShortMD5 = (str: string): string => { @@ -104,17 +104,17 @@ export const hasNoRefImageErrors = ({assertViewResults = []}: {assertViewResults return assertViewResults.some((assertViewResult) => isNoRefImageError(assertViewResult)); }; -export const hasFailedImages = (result: {imagesInfo?: ImageInfoFull[]}): boolean => { - const {imagesInfo = []} = result; - +export const hasFailedImages = (imagesInfo: ImageInfoFull[] = []): boolean => { return imagesInfo.some((imageInfo: ImageInfoFull) => { - return !isAssertViewError((imageInfo as ImageInfoError).error) && - (isErrorStatus(imageInfo.status) || isFailStatus(imageInfo.status)); + return (imageInfo as ImageInfoFail).stateName && + (isErrorStatus(imageInfo.status) || isFailStatus(imageInfo.status) || isNoRefImageError(imageInfo) || isImageDiffError(imageInfo)); }); }; -export const hasResultFails = (testResult: {status: TestStatus, imagesInfo?: ImageInfoFull[]}): boolean => { - return hasFailedImages(testResult) || isErrorStatus(testResult.status) || isFailStatus(testResult.status); +export const hasUnrelatedToScreenshotsErrors = (error: TestError): boolean => { + return !isNoRefImageError(error) && + !isImageDiffError(error) && + !isAssertViewError(error); }; export const getError = (error?: TestError): undefined | Pick => { @@ -131,7 +131,11 @@ export const hasDiff = (assertViewResults: {name?: string}[]): boolean => { /* This method tries to determine true status of testResult by using fields like error, imagesInfo */ export const determineStatus = (testResult: Pick): TestStatus => { - if (!hasFailedImages(testResult) && !isSkippedStatus(testResult.status) && isEmpty(testResult.error)) { + if ( + !hasFailedImages(testResult.imagesInfo) && + !isSkippedStatus(testResult.status) && + (!testResult.error || !hasUnrelatedToScreenshotsErrors(testResult.error)) + ) { return SUCCESS; } diff --git a/lib/constants/tests.ts b/lib/constants/tests.ts index 072614418..808755def 100644 --- a/lib/constants/tests.ts +++ b/lib/constants/tests.ts @@ -1 +1,5 @@ +export const HERMIONE_TITLE_DELIMITER = ' '; + export const PWT_TITLE_DELIMITER = ' › '; + +export const UNKNOWN_ATTEMPT = -1; diff --git a/lib/gui/tool-runner/index.ts b/lib/gui/tool-runner/index.ts index 0fa45ca8c..6117dbcc0 100644 --- a/lib/gui/tool-runner/index.ts +++ b/lib/gui/tool-runner/index.ts @@ -12,7 +12,7 @@ import {createTestRunner} from './runner'; import {subscribeOnToolEvents} from './report-subscriber'; import {GuiReportBuilder, GuiReportBuilderResult} from '../../report-builder/gui'; import {EventSource} from '../event-source'; -import {logger, getShortMD5} from '../../common-utils'; +import {logger, getShortMD5, isUpdatedStatus} from '../../common-utils'; import * as reporterHelper from '../../reporter-helpers'; import { UPDATED, @@ -22,7 +22,7 @@ import { ToolName, DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME, - PluginEvents + PluginEvents, UNKNOWN_ATTEMPT } from '../../constants'; import {formatId, mkFullTitle, mergeDatabasesForReuse, filterByEqualDiffSizes} from './utils'; import {getTestsTreeFromDatabase} from '../../db-utils/server'; @@ -43,7 +43,8 @@ import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../../tests-tre import {ReporterTestResult} from '../../test-adapter'; import {ImagesInfoFormatter} from '../../image-handler'; import {SqliteClient} from '../../sqlite-client'; -import {TestAttemptManager} from '../../test-attempt-manager'; +import PQueue from 'p-queue'; +import os from 'os'; type ToolRunnerArgs = [paths: string[], hermione: Hermione & HtmlReporterApi, configs: GuiConfigs]; @@ -55,7 +56,7 @@ interface HermioneTestExtended extends HermioneTest { imagesInfo: Pick[]; } -type HermioneTestPlain = Pick; +type HermioneTestPlain = Pick; export interface UndoAcceptImagesResult { updatedImages: TreeImage[]; @@ -72,8 +73,6 @@ const formatTestResultUnsafe = ( return formatTestResult(test as HermioneTestResult, status, attempt, {imageHandler}); }; -const HERMIONE_TITLE_DELIMITER = ' '; - export class ToolRunner { private _testFiles: string[]; private _hermione: Hermione & HtmlReporterApi; @@ -86,7 +85,6 @@ export class ToolRunner { private _eventSource: EventSource; protected _reportBuilder: GuiReportBuilder | null; private _tests: Record; - private readonly _testAttemptManager: TestAttemptManager; static create(this: new (...args: ToolRunnerArgs) => T, ...args: ToolRunnerArgs): T { return new this(...args); @@ -107,7 +105,6 @@ export class ToolRunner { this._reportBuilder = null; this._tests = {}; - this._testAttemptManager = new TestAttemptManager(); } get config(): HermioneConfig { @@ -123,7 +120,7 @@ export class ToolRunner { const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._reportPath, reuse: true}); - this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient, testAttemptManager: this._testAttemptManager}); + this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient}); this._subscribeOnEvents(); this._collection = await this._readTests(); @@ -188,13 +185,11 @@ export class ToolRunner { return Promise.all(tests.map(async (test): Promise => { const updateResult = this._prepareTestResult(test); - const fullName = [...test.suite.path, test.state.name].join(HERMIONE_TITLE_DELIMITER); - const updateAttempt = reportBuilder.testAttemptManager.registerAttempt({fullName, browserId: test.browserId}, UPDATED); - const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResultUnsafe(updateResult, UPDATED, UNKNOWN_ATTEMPT, reportBuilder); - const failResultId = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt - 1, reportBuilder).id; + const formattedResult = await reportBuilder.addUpdated(formattedResultWithoutAttempt); - updateResult.attempt = updateAttempt; + updateResult.attempt = formattedResult.attempt; await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => { const {stateName} = imageInfo; @@ -205,26 +200,22 @@ export class ToolRunner { this._emitUpdateReference(result, stateName); })); - reportBuilder.addUpdated(formattedResult, failResultId); - return reportBuilder.getTestBranch(formattedResult.id); })); } async undoAcceptImages(tests: TestRefUpdateData[]): Promise { - const updatedImages: TreeImage[] = [], removedResults: string[] = []; + const updatedImages: TreeImage[] = [], removedResultIds: string[] = []; const reportBuilder = this._ensureReportBuilder(); await Promise.all(tests.map(async (test) => { const updateResult = this._prepareTestResult(test); - const fullName = [...test.suite.path, test.state.name].join(' '); - const attempt = reportBuilder.testAttemptManager.getCurrentAttempt({fullName, browserId: test.browserId}); - const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, attempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResultUnsafe(updateResult, UPDATED, UNKNOWN_ATTEMPT, reportBuilder); await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => { const {stateName} = imageInfo; - const undoResultData = reportBuilder.undoAcceptImage(formattedResult, stateName); + const undoResultData = reportBuilder.undoAcceptImage(formattedResultWithoutAttempt, stateName); if (undoResultData === null) { return; } @@ -234,18 +225,19 @@ export class ToolRunner { removedResult, previousExpectedPath, shouldRemoveReference, - shouldRevertReference + shouldRevertReference, + newResult } = undoResultData; updatedImage && updatedImages.push(updatedImage); - removedResult && removedResults.push(removedResult); + removedResult && removedResultIds.push(removedResult.id); if (shouldRemoveReference) { - await reporterHelper.removeReferenceImage(formattedResult, stateName); + await reporterHelper.removeReferenceImage(newResult, stateName); } if (shouldRevertReference && removedResult) { - await reporterHelper.revertReferenceImage(removedResult, formattedResult, stateName); + await reporterHelper.revertReferenceImage(removedResult, newResult, stateName); } if (previousExpectedPath && (updateResult as HermioneTest).fullTitle) { @@ -257,7 +249,7 @@ export class ToolRunner { })); })); - return {updatedImages, removedResults}; + return {updatedImages, removedResults: removedResultIds}; } async findEqualDiffs(images: TestEqualDiffsData[]): Promise { @@ -312,6 +304,7 @@ export class ToolRunner { protected async _handleRunnableCollection(): Promise { const reportBuilder = this._ensureReportBuilder(); + const queue = new PQueue({concurrency: os.cpus().length}); this._ensureTestCollection().eachTest((test, browserId) => { if (test.disabled || this._isSilentlySkipped(test)) { @@ -322,14 +315,14 @@ export class ToolRunner { const testId = formatId(test.id.toString(), browserId); this._tests[testId] = _.extend(test, {browserId}); - const attempt = 0; if (test.pending) { - reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, attempt, reportBuilder)); + queue.add(async () => reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, UNKNOWN_ATTEMPT, reportBuilder))); } else { - reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, attempt, reportBuilder)); + queue.add(async () => reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, UNKNOWN_ATTEMPT, reportBuilder))); } }); + await queue.onIdle(); await this._fillTestsTree(); } @@ -338,7 +331,7 @@ export class ToolRunner { } protected _subscribeOnEvents(): void { - subscribeOnToolEvents(this._hermione, this._ensureReportBuilder(), this._eventSource, this._reportPath); + subscribeOnToolEvents(this._hermione, this._ensureReportBuilder(), this._eventSource); } protected _prepareTestResult(test: TestRefUpdateData): HermioneTestExtended | HermioneTestPlain { @@ -356,7 +349,7 @@ export class ToolRunner { const path = this._hermione.config.browsers[browserId].getScreenshotPath(rawTest, stateName); const refImg = {path, size: actualImg.size}; - assertViewResults.push({stateName, refImg, currImg: actualImg}); + assertViewResults.push({stateName, refImg, currImg: actualImg, isUpdated: isUpdatedStatus(imageInfo.status)}); return _.extend(imageInfo, {expectedImg: refImg}); }); @@ -367,8 +360,7 @@ export class ToolRunner { imagesInfo, sessionId, attempt, - meta: {url}, - updated: true + meta: {url} }); // _.merge can't fully clone test object since hermione@7+ @@ -393,14 +385,6 @@ export class ToolRunner { if (testsTree && !_.isEmpty(testsTree)) { reportBuilder.reuseTestsTree(testsTree); - - // Fill test attempt manager with data from db - for (const [, testResult] of Object.entries(testsTree.results.byId)) { - this._testAttemptManager.registerAttempt({ - fullName: testResult.suitePath.join(HERMIONE_TITLE_DELIMITER), - browserId: testResult.name - }, testResult.status, testResult.attempt); - } } this._tree = {...reportBuilder.getResult(), autoRun}; diff --git a/lib/gui/tool-runner/report-subscriber.ts b/lib/gui/tool-runner/report-subscriber.ts index 344bb6982..a307aaa1c 100644 --- a/lib/gui/tool-runner/report-subscriber.ts +++ b/lib/gui/tool-runner/report-subscriber.ts @@ -5,32 +5,17 @@ import {ClientEvents} from '../constants'; import {getSuitePath} from '../../plugin-utils'; import {createWorkers, CreateWorkersRunner} from '../../workers/create-workers'; import {logError, formatTestResult} from '../../server-utils'; -import {hasDiff} from '../../common-utils'; -import {TestStatus, RUNNING, SUCCESS, SKIPPED} from '../../constants'; +import {hasFailedImages} from '../../common-utils'; +import {TestStatus, RUNNING, SUCCESS, SKIPPED, UNKNOWN_ATTEMPT} 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'; +import {HermioneTestResult, ImageInfoFull} from '../../types'; -let workers: ReturnType; - -export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiReportBuilder, client: EventSource, reportPath: string): void => { +export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiReportBuilder, client: EventSource): void => { const queue = new PQueue({concurrency: os.cpus().length}); - const {imageHandler, testAttemptManager} = reportBuilder; - - async function failHandler(formattedResult: ReporterTestResult): Promise { - const actions: Promise[] = [imageHandler.saveTestImages(formattedResult, workers)]; - - if (formattedResult.errorDetails) { - actions.push((formattedResult as HermioneTestAdapter).saveErrorDetails(reportPath)); - } - - await Promise.all(actions); - } hermione.on(hermione.events.RUNNER_START, (runner) => { - workers = createWorkers(runner as unknown as CreateWorkersRunner); + reportBuilder.registerWorkers(createWorkers(runner as unknown as CreateWorkersRunner)); }); hermione.on(hermione.events.SUITE_BEGIN, (suite) => { @@ -45,22 +30,21 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo }); hermione.on(hermione.events.TEST_BEGIN, (data) => { - const attempt = testAttemptManager.registerAttempt({fullName: data.fullTitle(), browserId: data.browserId}, RUNNING); - const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, attempt, reportBuilder); + queue.add(async () => { + const formattedResultWithoutAttempt = formatTestResult(data as HermioneTestResult, RUNNING, UNKNOWN_ATTEMPT, reportBuilder); - reportBuilder.addRunning(formattedResult); - const testBranch = reportBuilder.getTestBranch(formattedResult.id); + const formattedResult = await reportBuilder.addRunning(formattedResultWithoutAttempt); + 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) => { queue.add(async () => { - const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SUCCESS); - const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResult(testResult, SUCCESS, UNKNOWN_ATTEMPT, reportBuilder); - await imageHandler.saveTestImages(formattedResult, workers); - reportBuilder.addSuccess(formattedResult); + const formattedResult = await reportBuilder.addSuccess(formattedResultWithoutAttempt); const testBranch = reportBuilder.getTestBranch(formattedResult.id); client.emit(ClientEvents.TEST_RESULT, testBranch); @@ -69,13 +53,11 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.RETRY, (testResult) => { queue.add(async () => { - const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; - const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); + const status = hasFailedImages(testResult.assertViewResults as ImageInfoFull[]) ? TestStatus.FAIL : TestStatus.ERROR; - const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResult(testResult, status, UNKNOWN_ATTEMPT, reportBuilder); - await failHandler(formattedResult); - reportBuilder.addRetry(formattedResult); + const formattedResult = await reportBuilder.addRetry(formattedResultWithoutAttempt); const testBranch = reportBuilder.getTestBranch(formattedResult.id); client.emit(ClientEvents.TEST_RESULT, testBranch); @@ -84,15 +66,13 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.TEST_FAIL, (testResult) => { queue.add(async () => { - const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR; - const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status); + const status = hasFailedImages(testResult.assertViewResults as ImageInfoFull[]) ? TestStatus.FAIL : TestStatus.ERROR; - const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResult(testResult, status, UNKNOWN_ATTEMPT, reportBuilder); - await failHandler(formattedResult); - status === TestStatus.FAIL - ? reportBuilder.addFail(formattedResult) - : reportBuilder.addError(formattedResult); + const formattedResult = status === TestStatus.FAIL + ? await reportBuilder.addFail(formattedResultWithoutAttempt) + : await reportBuilder.addError(formattedResultWithoutAttempt); const testBranch = reportBuilder.getTestBranch(formattedResult.id); client.emit(ClientEvents.TEST_RESULT, testBranch); @@ -101,11 +81,9 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo hermione.on(hermione.events.TEST_PENDING, async (testResult) => { queue.add(async () => { - const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SKIPPED); - const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, attempt, reportBuilder); + const formattedResultWithoutAttempt = formatTestResult(testResult as HermioneTestResult, SKIPPED, UNKNOWN_ATTEMPT, reportBuilder); - await failHandler(formattedResult); - reportBuilder.addSkipped(formattedResult); + const formattedResult = await reportBuilder.addSkipped(formattedResultWithoutAttempt); const testBranch = reportBuilder.getTestBranch(formattedResult.id); client.emit(ClientEvents.TEST_RESULT, testBranch); diff --git a/lib/image-handler.ts b/lib/image-handler.ts index 791d07a40..c53badaf9 100644 --- a/lib/image-handler.ts +++ b/lib/image-handler.ts @@ -17,7 +17,7 @@ import { ImagesSaver, ImageInfoPageSuccess } from './types'; -import {ERROR, FAIL, PluginEvents, SUCCESS, TestStatus, UPDATED} from './constants'; +import {ERROR, FAIL, PluginEvents, SUCCESS, TestStatus, UNKNOWN_ATTEMPT, UPDATED} from './constants'; import { getError, getShortMD5, @@ -142,7 +142,7 @@ export class ImageHandler extends EventEmitter2 implements ImagesInfoFormatter { const imagesInfo: ImageInfoFull[] = testResult.assertViewResults?.map((assertResult): ImageInfoFull => { let status: TestStatus, error: {message: string; stack?: string;} | undefined; - if (testResult.isUpdated === true) { + if (assertResult.isUpdated === true) { status = UPDATED; } else if (isImageDiffError(assertResult)) { status = FAIL; @@ -258,33 +258,38 @@ export class ImageHandler extends EventEmitter2 implements ImagesInfoFormatter { private _getExpectedPath(testResult: ReporterTestResultPlain, stateName?: string): {path: string, reused: boolean} { const key = this._getExpectedKey(testResult, stateName); + let result: {path: string; reused: boolean}; if (testResult.status === UPDATED) { const expectedPath = utils.getReferencePath({attempt: testResult.attempt, browserId: testResult.browserId, imageDir: testResult.imageDir, stateName}); - cacheExpectedPaths.set(key, expectedPath); - - return {path: expectedPath, reused: false}; - } - if (cacheExpectedPaths.has(key)) { - return {path: cacheExpectedPaths.get(key) as string, reused: true}; - } - - const imageInfo = this._imageStore.getLastImageInfoFromDb(testResult, stateName); + result = {path: expectedPath, reused: false}; + } else if (cacheExpectedPaths.has(key)) { + result = {path: cacheExpectedPaths.get(key) as string, reused: true}; + } else { + const imageInfo = this._imageStore.getLastImageInfoFromDb(testResult, stateName); - if (imageInfo && (imageInfo as ImageInfoFail).expectedImg) { - const expectedPath = (imageInfo as ImageInfoFail).expectedImg.path; + if (imageInfo && (imageInfo as ImageInfoFail).expectedImg) { + const expectedPath = (imageInfo as ImageInfoFail).expectedImg.path; - cacheExpectedPaths.set(key, expectedPath); - - return {path: expectedPath, reused: true}; + result = {path: expectedPath, reused: true}; + } else { + const expectedPath = utils.getReferencePath({ + attempt: testResult.attempt, + browserId: testResult.browserId, + imageDir: testResult.imageDir, + stateName + }); + + result = {path: expectedPath, reused: false}; + } } - const expectedPath = utils.getReferencePath({attempt: testResult.attempt, browserId: testResult.browserId, imageDir: testResult.imageDir, stateName}); - - cacheExpectedPaths.set(key, expectedPath); + if (testResult.attempt !== UNKNOWN_ATTEMPT) { + cacheExpectedPaths.set(key, result.path); + } - return {path: expectedPath, reused: false}; + return result; } private _getImgFromStorage(imgPath: string): string { diff --git a/lib/report-builder/gui.ts b/lib/report-builder/gui.ts index 0b17c7b2d..074406f41 100644 --- a/lib/report-builder/gui.ts +++ b/lib/report-builder/gui.ts @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import {StaticReportBuilder} from './static'; import {GuiTestsTreeBuilder, TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui'; import { - IDLE, RUNNING, UPDATED, TestStatus, DB_COLUMNS, ToolName + IDLE, RUNNING, UPDATED, TestStatus, DB_COLUMNS, ToolName, HERMIONE_TITLE_DELIMITER } from '../constants'; import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils'; import {ReporterTestResult} from '../test-adapter'; @@ -16,11 +16,11 @@ import {copyAndUpdate} from '../test-adapter/utils'; interface UndoAcceptImageResult { updatedImage: TreeImage | undefined; - removedResult: string | undefined; + removedResult: ReporterTestResult | undefined; previousExpectedPath: string | null; shouldRemoveReference: boolean; shouldRevertReference: boolean; - newTestResult: ReporterTestResult; + newResult: ReporterTestResult; } export interface GuiReportBuilderResult { @@ -43,16 +43,16 @@ export class GuiReportBuilder extends StaticReportBuilder { this._skips = []; } - addIdle(result: ReporterTestResult): ReporterTestResult { + async addIdle(result: ReporterTestResult): Promise { return this._addTestResult(result, {status: IDLE}); } - addRunning(result: ReporterTestResult): ReporterTestResult { + async addRunning(result: ReporterTestResult): Promise { return this._addTestResult(result, {status: RUNNING}); } - addSkipped(result: ReporterTestResult): ReporterTestResult { - const formattedResult = super.addSkipped(result); + override async addSkipped(result: ReporterTestResult): Promise { + const formattedResult = await super.addSkipped(result); const { fullName: suite, skipReason: comment, @@ -64,8 +64,8 @@ export class GuiReportBuilder extends StaticReportBuilder { return formattedResult; } - addUpdated(result: ReporterTestResult, failResultId: string): ReporterTestResult { - return this._addTestResult(result, {status: UPDATED}, {failResultId}); + async addUpdated(result: ReporterTestResult): Promise { + return this._addTestResult(result, {status: UPDATED}); } setApiValues(values: HtmlReporterValues): this { @@ -75,6 +75,14 @@ export class GuiReportBuilder extends StaticReportBuilder { reuseTestsTree(tree: Tree): void { this._testsTree.reuseTestsTree(tree); + + // Fill test attempt manager with data from db + for (const [, testResult] of Object.entries(tree.results.byId)) { + this._testAttemptManager.registerAttempt({ + fullName: testResult.suitePath.join(HERMIONE_TITLE_DELIMITER), + browserId: testResult.name + }, testResult.status, testResult.attempt); + } } getResult(): GuiReportBuilderResult { @@ -104,7 +112,11 @@ export class GuiReportBuilder extends StaticReportBuilder { return this._testsTree.getImageDataToFindEqualDiffs(imageIds); } - undoAcceptImage(testResult: ReporterTestResult, stateName: string): UndoAcceptImageResult | null { + undoAcceptImage(testResultWithoutAttempt: ReporterTestResult, stateName: string): UndoAcceptImageResult | null { + const attempt = this._testAttemptManager.getCurrentAttempt(testResultWithoutAttempt); + const imagesInfoFormatter = this.imageHandler; + const testResult = copyAndUpdate(testResultWithoutAttempt, {attempt}, {imagesInfoFormatter}); + const resultId = testResult.id; const suitePath = testResult.testPath; const browserName = testResult.browserId; @@ -127,21 +139,19 @@ export class GuiReportBuilder extends StaticReportBuilder { const shouldRemoveReference = _.isNull(previousImageRefImgSize); const shouldRevertReference = !shouldRemoveReference; - let updatedImage: TreeImage | undefined, removedResult: string | undefined, newTestResult: ReporterTestResult; + let updatedImage: TreeImage | undefined, removedResult: ReporterTestResult | undefined; if (shouldRemoveResult) { this._testsTree.removeTestResult(resultId); this._testAttemptManager.removeAttempt(testResult); - newTestResult = copyAndUpdate(testResult, {attempt: this._testAttemptManager.getCurrentAttempt(testResult)}); - - removedResult = resultId; + removedResult = testResult; } else { updatedImage = this._testsTree.updateImageInfo(imageId, previousImage); - - newTestResult = testResult; } + const newResult = copyAndUpdate(testResult, {attempt: this._testAttemptManager.getCurrentAttempt(testResult)}, {imagesInfoFormatter}); + this._deleteTestResultFromDb({where: [ `${DB_COLUMNS.SUITE_PATH} = ?`, `${DB_COLUMNS.NAME} = ?`, @@ -150,11 +160,11 @@ export class GuiReportBuilder extends StaticReportBuilder { `json_extract(${DB_COLUMNS.IMAGES_INFO}, '$[0].stateName') = ?` ].join(' AND ')}, JSON.stringify(suitePath), browserName, status, timestamp.toString(), stateName); - return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newTestResult}; + return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newResult}; } - protected override _addTestResult(formattedResult: ReporterTestResult, props: {status: TestStatus} & Partial, opts: {failResultId?: string} = {}): ReporterTestResult { - super._addTestResult(formattedResult, props); + protected override async _addTestResult(formattedResultOriginal: ReporterTestResult, props: {status: TestStatus} & Partial): Promise { + const formattedResult = await super._addTestResult(formattedResultOriginal, props); const testResult = this._createTestResult(formattedResult, { ...props, @@ -162,22 +172,24 @@ export class GuiReportBuilder extends StaticReportBuilder { attempt: formattedResult.attempt }); - this._extendTestWithImagePaths(testResult, formattedResult, opts); + this._extendTestWithImagePaths(testResult, formattedResult); this._testsTree.addTestResult(testResult, formattedResult); return formattedResult; } - private _extendTestWithImagePaths(testResult: PreparedTestResult, formattedResult: ReporterTestResult, opts: {failResultId?: string} = {}): void { + private _extendTestWithImagePaths(testResult: PreparedTestResult, formattedResult: ReporterTestResult): void { const newImagesInfo = formattedResult.imagesInfo; + const imagesInfoFormatter = this._imageHandler; if (testResult.status !== UPDATED) { _.set(testResult, 'imagesInfo', newImagesInfo); return; } - const failImagesInfo = opts.failResultId ? this._testsTree.getImagesInfo(opts.failResultId) : []; + const failResultId = copyAndUpdate(formattedResult, {attempt: formattedResult.attempt - 1}, {imagesInfoFormatter}).id; + const failImagesInfo = this._testsTree.getImagesInfo(failResultId); if (failImagesInfo.length) { testResult.imagesInfo = _.clone(failImagesInfo); diff --git a/lib/report-builder/static.ts b/lib/report-builder/static.ts index 05c28429f..ee7bb8743 100644 --- a/lib/report-builder/static.ts +++ b/lib/report-builder/static.ts @@ -12,11 +12,11 @@ import { SUCCESS, TestStatus, LOCAL_DATABASE_NAME, - PluginEvents + PluginEvents, UNKNOWN_ATTEMPT, UPDATED } from '../constants'; import type {PreparedTestResult, SqliteClient} from '../sqlite-client'; import {ReporterTestResult} from '../test-adapter'; -import {hasImage, saveStaticFilesToReportDir, writeDatabaseUrlsFile} from '../server-utils'; +import {hasImage, saveErrorDetails, saveStaticFilesToReportDir, writeDatabaseUrlsFile} from '../server-utils'; import {ReporterConfig} from '../types'; import {HtmlReporter} from '../plugin-api'; import {ImageHandler} from '../image-handler'; @@ -25,12 +25,13 @@ import {getUrlWithBase, getError, getRelativeUrl, hasDiff, hasNoRefImageErrors} import {getTestFromDb} from '../db-utils/server'; import {ImageDiffError} from '../errors'; import {TestAttemptManager} from '../test-attempt-manager'; +import {copyAndUpdate} from '../test-adapter/utils'; +import {RegisterWorkers} from '../workers/create-workers'; const ignoredStatuses = [RUNNING, IDLE]; interface StaticReportBuilderOptions { dbClient: SqliteClient; - testAttemptManager: TestAttemptManager; } export class StaticReportBuilder { @@ -39,6 +40,7 @@ export class StaticReportBuilder { protected _dbClient: SqliteClient; protected _imageHandler: ImageHandler; protected _testAttemptManager: TestAttemptManager; + private _workers: RegisterWorkers<['saveDiffTo']> | null; static create( this: new (htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, options: StaticReportBuilderOptions) => T, @@ -49,17 +51,19 @@ export class StaticReportBuilder { return new this(htmlReporter, pluginConfig, options); } - constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient, testAttemptManager}: StaticReportBuilderOptions) { + constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient}: StaticReportBuilderOptions) { this._htmlReporter = htmlReporter; this._pluginConfig = pluginConfig; this._dbClient = dbClient; - this._testAttemptManager = testAttemptManager; + this._testAttemptManager = new TestAttemptManager(); const imageStore = new SqliteImageStore(this._dbClient); this._imageHandler = new ImageHandler(imageStore, htmlReporter.imagesSaver, {reportPath: pluginConfig.path}); + this._workers = null; + this._htmlReporter.on(PluginEvents.IMAGES_SAVER_UPDATED, (newImagesSaver) => { this._imageHandler.setImagesSaver(newImagesSaver); }); @@ -71,10 +75,6 @@ export class StaticReportBuilder { return this._imageHandler; } - get testAttemptManager(): TestAttemptManager { - return this._testAttemptManager; - } - async saveStaticFiles(): Promise { const destPath = this._pluginConfig.path; @@ -84,26 +84,26 @@ export class StaticReportBuilder { ]); } - addSkipped(result: ReporterTestResult): ReporterTestResult { + async addSkipped(result: ReporterTestResult): Promise { return this._addTestResult(result, { status: SKIPPED, skipReason: result.skipReason }); } - addSuccess(result: ReporterTestResult): ReporterTestResult { + async addSuccess(result: ReporterTestResult): Promise { return this._addTestResult(result, {status: SUCCESS}); } - addFail(result: ReporterTestResult): ReporterTestResult { + async addFail(result: ReporterTestResult): Promise { return this._addFailResult(result); } - addError(result: ReporterTestResult): ReporterTestResult { + async addError(result: ReporterTestResult): Promise { return this._addErrorResult(result); } - addRetry(result: ReporterTestResult): ReporterTestResult { + async addRetry(result: ReporterTestResult): Promise { if (hasDiff(result.assertViewResults as ImageDiffError[])) { return this._addFailResult(result); } else { @@ -111,15 +111,64 @@ export class StaticReportBuilder { } } - protected _addFailResult(formattedResult: ReporterTestResult): ReporterTestResult { + registerWorkers(workers: RegisterWorkers<['saveDiffTo']>): void { + this._workers = workers; + } + + private _ensureWorkers(): RegisterWorkers<['saveDiffTo']> { + if (!this._workers) { + throw new Error('You must register workers before using report builder.' + + 'Make sure registerWorkers() was called before adding any test results.'); + } + + return this._workers; + } + + /** If passed test result doesn't have attempt, this method registers new attempt and sets attempt number */ + private _provideAttempt(testResultOriginal: ReporterTestResult): ReporterTestResult { + let formattedResult = testResultOriginal; + + if (testResultOriginal.attempt === UNKNOWN_ATTEMPT) { + const imagesInfoFormatter = this._imageHandler; + const attempt = this._testAttemptManager.registerAttempt(testResultOriginal, testResultOriginal.status); + formattedResult = copyAndUpdate(testResultOriginal, {attempt}, {imagesInfoFormatter}); + } + + return formattedResult; + } + + private async _saveTestResultData(testResult: ReporterTestResult): Promise { + if ([IDLE, RUNNING, UPDATED].includes(testResult.status)) { + return; + } + + const actions: Promise[] = []; + + if (!_.isEmpty(testResult.assertViewResults)) { + actions.push(this._imageHandler.saveTestImages(testResult, this._ensureWorkers())); + } + + if (this._pluginConfig.saveErrorDetails && testResult.errorDetails) { + actions.push(saveErrorDetails(testResult, this._pluginConfig.path)); + } + + await Promise.all(actions); + } + + protected async _addFailResult(formattedResult: ReporterTestResult): Promise { return this._addTestResult(formattedResult, {status: FAIL}); } - protected _addErrorResult(formattedResult: ReporterTestResult): ReporterTestResult { + protected async _addErrorResult(formattedResult: ReporterTestResult): Promise { return this._addTestResult(formattedResult, {status: ERROR}); } - protected _addTestResult(formattedResult: ReporterTestResult, props: {status: TestStatus} & Partial): ReporterTestResult { + protected async _addTestResult(formattedResultOriginal: ReporterTestResult, props: {status: TestStatus} & Partial): Promise { + const formattedResult = this._provideAttempt(formattedResultOriginal); + + // Test result data has to be saved before writing to db, because user may save data to custom location + await this._saveTestResultData(formattedResult); + formattedResult.image = hasImage(formattedResult); const testResult = this._createTestResult(formattedResult, _.extend(props, { @@ -154,7 +203,7 @@ export class StaticReportBuilder { const testResult: PreparedTestResult = Object.assign({ suiteUrl, name: browserId, metaInfo, description, history, imagesInfo, screenshot: Boolean(screenshot), multipleTabs, - suitePath: testPath + suitePath: testPath, suiteName: _.last(testPath) as string }, props); const error = getError(result.error); diff --git a/lib/reporter-helpers.ts b/lib/reporter-helpers.ts index f0d278a41..f5d4ec2a2 100644 --- a/lib/reporter-helpers.ts +++ b/lib/reporter-helpers.ts @@ -29,10 +29,11 @@ export const updateReferenceImage = async (testResult: ReporterTestResult, repor ]); }; -export const revertReferenceImage = async (referenceId: string, testResult: ReporterTestResult, stateName: string): Promise => { +export const revertReferenceImage = async (removedResult: ReporterTestResult, newResult: ReporterTestResult, stateName: string): Promise => { + const referenceId = removedResult.id; const referenceHash = mkReferenceHash(referenceId, stateName); const oldReferencePath = path.resolve(tmp.tmpdir, referenceHash); - const referencePath = ImageHandler.getRefImg(testResult.assertViewResults, stateName)?.path; + const referencePath = ImageHandler.getRefImg(newResult.assertViewResults, stateName)?.path; if (!referencePath) { return; diff --git a/lib/server-utils.ts b/lib/server-utils.ts index ccd28fab3..1db16430e 100644 --- a/lib/server-utils.ts +++ b/lib/server-utils.ts @@ -319,3 +319,17 @@ export const formatTestResult = ( ): ReporterTestResult => { return new HermioneTestAdapter(rawResult, {attempt, status, imagesInfoFormatter: imageHandler}); }; + +export const saveErrorDetails = async (testResult: ReporterTestResult, reportPath: string): Promise => { + if (!testResult.errorDetails) { + return; + } + + const detailsFilePath = path.resolve(reportPath, testResult.errorDetails.filePath); + const detailsData = _.isObject(testResult.errorDetails.data) + ? JSON.stringify(testResult.errorDetails.data, null, 2) + : testResult.errorDetails.data; + + await makeDirFor(detailsFilePath); + await fs.writeFile(detailsFilePath, detailsData); +}; diff --git a/lib/sqlite-client.ts b/lib/sqlite-client.ts index d399aeb01..4575056f2 100644 --- a/lib/sqlite-client.ts +++ b/lib/sqlite-client.ts @@ -45,6 +45,7 @@ export interface PreparedTestResult { timestamp: number; errorDetails?: ErrorDetails; suitePath: string[]; + suiteName: string; } export interface DbTestResult { diff --git a/lib/static/components/retry-switcher/item.jsx b/lib/static/components/retry-switcher/item.jsx index 326d46172..802b88c54 100644 --- a/lib/static/components/retry-switcher/item.jsx +++ b/lib/static/components/retry-switcher/item.jsx @@ -5,10 +5,8 @@ import {connect} from 'react-redux'; import {get} from 'lodash'; import {ERROR} from '../../../constants'; import { - isAssertViewError, - isFailStatus, - isImageDiffError, - isNoRefImageError + hasUnrelatedToScreenshotsErrors, + isFailStatus } from '../../../common-utils'; class RetrySwitcherItem extends Component { @@ -46,7 +44,7 @@ export default connect( const {status, attempt, error} = result; return { - status: hasUnrelatedToScreenshotsErrors(status, error) ? `${status}_${ERROR}` : status, + status: isFailStatus(status) && hasUnrelatedToScreenshotsErrors(error) ? `${status}_${ERROR}` : status, attempt, keyToGroupTestsBy, matchedSelectedGroup @@ -54,9 +52,3 @@ export default connect( } )(RetrySwitcherItem); -function hasUnrelatedToScreenshotsErrors(status, error) { - return isFailStatus(status) && - !isNoRefImageError(error) && - !isImageDiffError(error) && - !isAssertViewError(error); -} diff --git a/lib/static/modules/reducers/tree/index.js b/lib/static/modules/reducers/tree/index.js index bdcac6e56..0c511a47a 100644 --- a/lib/static/modules/reducers/tree/index.js +++ b/lib/static/modules/reducers/tree/index.js @@ -1,4 +1,4 @@ -import {findLast, isEmpty, pick} from 'lodash'; +import {findLast, isEmpty} from 'lodash'; import {produce} from 'immer'; import actionNames from '../../action-names'; import { diff --git a/lib/test-adapter/hermione.ts b/lib/test-adapter/hermione.ts index 5e99154d8..f307998d9 100644 --- a/lib/test-adapter/hermione.ts +++ b/lib/test-adapter/hermione.ts @@ -1,11 +1,9 @@ import _ from 'lodash'; -import fs from 'fs-extra'; import path from 'path'; import {getCommandsHistory} from '../history-utils'; -import {ERROR_DETAILS_PATH, TestStatus} from '../constants'; +import {TestStatus} from '../constants'; import {wrapLinkByTag} from '../common-utils'; -import * as utils from '../server-utils'; import { AssertViewResult, ErrorDetails, @@ -16,6 +14,7 @@ import { import {ImagesInfoFormatter} from '../image-handler'; import {ReporterTestResult} from './index'; import {getSuitePath} from '../plugin-utils'; +import {extractErrorDetails} from './utils'; const getSkipComment = (suite: HermioneTestResult | HermioneSuite): string | null | undefined => { return suite.skipReason || suite.parent && getSkipComment(suite.parent); @@ -116,10 +115,6 @@ export class HermioneTestAdapter implements ReporterTestResult { return this.testPath.concat(this.browserId, this.attempt.toString()).join(' '); } - get isUpdated(): boolean | undefined { - return this._testResult.updated; - } - get screenshot(): ImageBase64 | undefined { return _.get(this._testResult, 'err.screenshot'); } @@ -137,19 +132,7 @@ export class HermioneTestAdapter implements ReporterTestResult { return this._errorDetails; } - const details = _.get(this._testResult, 'err.details', null); - - if (details) { - this._errorDetails = { - title: details.title || 'error details', - data: details.data, - filePath: `${ERROR_DETAILS_PATH}/${utils.getDetailsFileName( - this._testResult.id, this._testResult.browserId, this.attempt - )}` - }; - } else { - this._errorDetails = null; - } + this._errorDetails = extractErrorDetails(this); return this._errorDetails; } @@ -175,18 +158,4 @@ export class HermioneTestAdapter implements ReporterTestResult { this._timestamp = timestamp; } } - - async saveErrorDetails(reportPath: string): Promise { - if (!this.errorDetails) { - return; - } - - const detailsFilePath = path.resolve(reportPath, this.errorDetails.filePath); - const detailsData = _.isObject(this.errorDetails.data) - ? JSON.stringify(this.errorDetails.data, null, 2) - : this.errorDetails.data; - - await utils.makeDirFor(detailsFilePath); - await fs.writeFile(detailsFilePath, detailsData); - } } diff --git a/lib/test-adapter/index.ts b/lib/test-adapter/index.ts index 71378e66a..ffc2b3456 100644 --- a/lib/test-adapter/index.ts +++ b/lib/test-adapter/index.ts @@ -17,7 +17,6 @@ export interface ReporterTestResult { image?: boolean; readonly imageDir: string; readonly imagesInfo: ImageInfoFull[] | undefined; - readonly isUpdated?: boolean; readonly meta: Record; readonly multipleTabs: boolean; readonly screenshot: ImageBase64 | ImageData | null | undefined; diff --git a/lib/test-adapter/playwright.ts b/lib/test-adapter/playwright.ts index d65ba8bc7..be38db2f6 100644 --- a/lib/test-adapter/playwright.ts +++ b/lib/test-adapter/playwright.ts @@ -241,10 +241,6 @@ export class PlaywrightTestAdapter implements ReporterTestResult { return this._imagesInfoFormatter.getImagesInfo(this); } - get isUpdated(): boolean { - return false; - } - get meta(): Record { return Object.fromEntries(this._testCase.annotations.map(a => [a.type, a.description ?? ''])); } diff --git a/lib/test-adapter/reporter.ts b/lib/test-adapter/reporter.ts new file mode 100644 index 000000000..15f402d4f --- /dev/null +++ b/lib/test-adapter/reporter.ts @@ -0,0 +1,121 @@ +import {TestStatus} from '../constants'; +import {AssertViewResult, TestError, ErrorDetails, ImageInfoFull, ImageBase64, ImageData} from '../types'; +import {ReporterTestResult} from './index'; +import {ImagesInfoFormatter} from '../image-handler'; +import _ from 'lodash'; +import {extractErrorDetails} from './utils'; +import {getShortMD5} from '../common-utils'; + +interface ReporterTestAdapterOptions { + imagesInfoFormatter: ImagesInfoFormatter; +} + +// This class is primarily useful when cloning ReporterTestResult. +// It allows to override some properties while keeping computable +// properties valid, e.g. id + +export class ReporterTestAdapter implements ReporterTestResult { + private _testResult: ReporterTestResult; + private _imagesInfoFormatter: ImagesInfoFormatter; + private _errorDetails: ErrorDetails | null; + + constructor(testResult: ReporterTestResult, {imagesInfoFormatter}: ReporterTestAdapterOptions) { + this._testResult = testResult; + this._imagesInfoFormatter = imagesInfoFormatter; + this._errorDetails = null; + } + + get assertViewResults(): AssertViewResult[] { + return this._testResult.assertViewResults; + } + + get attempt(): number { + return this._testResult.attempt; + } + + get browserId(): string { + return this._testResult.browserId; + } + + get description(): string | undefined { + return this._testResult.description; + } + + get error(): TestError | undefined { + return this._testResult.error; + } + + get errorDetails(): ErrorDetails | null { + if (!_.isNil(this._errorDetails)) { + return this._errorDetails; + } + + this._errorDetails = extractErrorDetails(this); + + return this._errorDetails; + } + + get file(): string { + return this._testResult.file; + } + + get fullName(): string { + return this._testResult.fullName; + } + + get history(): string[] { + return this._testResult.history; + } + + get id(): string { + return this.testPath.concat(this.browserId, this.attempt.toString()).join(' '); + } + + get imageDir(): string { + return getShortMD5(this.fullName); + } + + get imagesInfo(): ImageInfoFull[] | undefined { + return this._imagesInfoFormatter.getImagesInfo(this); + } + + get meta(): Record { + return this._testResult.meta; + } + + get multipleTabs(): boolean { + return this._testResult.multipleTabs; + } + + get screenshot(): ImageBase64 | ImageData | null | undefined { + return this.error?.screenshot; + } + + get sessionId(): string { + return this._testResult.sessionId; + } + + get skipReason(): string | undefined { + return this._testResult.skipReason; + } + + get state(): {name: string;} { + return {name: this.testPath.at(-1) as string}; + } + + get status(): TestStatus { + return this._testResult.status; + } + + get testPath(): string[] { + return this._testResult.testPath; + } + + get timestamp(): number | undefined { + return this._testResult.timestamp; + } + + get url(): string | undefined { + return this._testResult.url; + } +} diff --git a/lib/test-adapter/utils/index.ts b/lib/test-adapter/utils/index.ts index 76d96f36d..48e5c7d29 100644 --- a/lib/test-adapter/utils/index.ts +++ b/lib/test-adapter/utils/index.ts @@ -1,8 +1,17 @@ import _ from 'lodash'; import {ReporterTestResult} from '../index'; import {TupleToUnion} from 'type-fest'; +import {ErrorDetails} from '../../types'; +import {ERROR_DETAILS_PATH} from '../../constants'; +import * as utils from '../../server-utils'; +import {ReporterTestAdapter} from '../reporter'; +import {ImagesInfoFormatter} from '../../image-handler'; -export const copyAndUpdate = (original: ReporterTestResult, updates: Partial): ReporterTestResult => { +export const copyAndUpdate = ( + original: ReporterTestResult, + updates: Partial, + {imagesInfoFormatter}: {imagesInfoFormatter: ImagesInfoFormatter} +): ReporterTestResult => { const keys = [ 'assertViewResults', 'attempt', @@ -17,7 +26,6 @@ export const copyAndUpdate = (original: ReporterTestResult, updates: Partial { + const details = testResult.error?.details ?? null; + + if (details) { + return { + title: details.title || 'error details', + data: details.data, + filePath: `${ERROR_DETAILS_PATH}/${utils.getDetailsFileName( + testResult.imageDir, testResult.browserId, testResult.attempt + )}` + }; + } + + return null; }; diff --git a/lib/tests-tree-builder/gui.ts b/lib/tests-tree-builder/gui.ts index 0a660ece4..83c74ec59 100644 --- a/lib/tests-tree-builder/gui.ts +++ b/lib/tests-tree-builder/gui.ts @@ -2,7 +2,6 @@ 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 { @@ -235,18 +234,4 @@ export class GuiTestsTreeBuilder extends BaseTestsTreeBuilder { this._reuseSuiteStatus(testsTree, suite.parentId); } - - protected _buildBrowserId(formattedResult: Pick): string { - const {testPath, browserId: browserName} = formattedResult; - const suiteId = this._buildId(testPath); - const browserId = this._buildId(suiteId, browserName); - - return browserId; - } - - protected _buildResultId(formattedResult: ReporterTestResult, attempt: number): string { - const browserId = this._buildBrowserId(formattedResult); - - return `${browserId} ${attempt}`; - } } diff --git a/lib/tests-tree-builder/static.ts b/lib/tests-tree-builder/static.ts index b053d46f4..2c1ffdb5e 100644 --- a/lib/tests-tree-builder/static.ts +++ b/lib/tests-tree-builder/static.ts @@ -201,6 +201,7 @@ function mkTestResult(row: RawSuitesRow, data: {attempt: number}): ParsedSuitesR name: row[DB_COLUMN_INDEXES.name] as string, screenshot: Boolean(row[DB_COLUMN_INDEXES.screenshot]), status: row[DB_COLUMN_INDEXES.status] as TestStatus, + suiteName: row[DB_COLUMN_INDEXES.suiteName] as string, suitePath: JSON.parse(row[DB_COLUMN_INDEXES.suitePath] as string), suiteUrl: row[DB_COLUMN_INDEXES.suiteUrl] as string, skipReason: row[DB_COLUMN_INDEXES.skipReason] as string, diff --git a/lib/types.ts b/lib/types.ts index d74b76ee8..8c1d5db43 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -12,7 +12,6 @@ export {Suite as HermioneSuite} from 'hermione'; export interface HermioneTestResult extends HermioneTestResultOriginal { timestamp?: number; - updated?: boolean; } export interface ImagesSaver { @@ -98,7 +97,7 @@ export type ImageInfo = | Omit | Omit; -export type AssertViewResult = AssertViewSuccess | ImageDiffError | NoRefImageError; +export type AssertViewResult = (AssertViewSuccess | ImageDiffError | NoRefImageError) & {isUpdated?: boolean}; export interface TestError { name: string; @@ -133,6 +132,7 @@ export interface ParsedSuitesRow { screenshot: boolean; skipReason?: string; status: TestStatus; + suiteName: string; suitePath: string[]; suiteUrl: string; timestamp: number; diff --git a/playwright.ts b/playwright.ts index 285628a1b..03464bd9e 100644 --- a/playwright.ts +++ b/playwright.ts @@ -1,7 +1,10 @@ import {promisify} from 'util'; + +import {EventEmitter} from 'events'; import _ from 'lodash'; -import type {Reporter, TestCase, TestResult as PwtTestResult} from '@playwright/test/reporter'; +import PQueue from 'p-queue'; import workerFarm, {Workers} from 'worker-farm'; +import type {Reporter, TestCase, TestResult as PwtTestResult} from '@playwright/test/reporter'; import {StaticReportBuilder} from './lib/report-builder/static'; import {HtmlReporter} from './lib/plugin-api'; @@ -9,11 +12,8 @@ import {ReporterConfig} from './lib/types'; import {parseConfig} from './lib/config'; import {PluginEvents, TestStatus, ToolName} from './lib/constants'; import {RegisterWorkers} from './lib/workers/create-workers'; -import {EventEmitter} from 'events'; import {PlaywrightTestAdapter, getStatus} from './lib/test-adapter/playwright'; -import PQueue from 'p-queue'; import {SqliteClient} from './lib/sqlite-client'; -import {TestAttemptManager} from './lib/test-attempt-manager'; export {ReporterConfig} from './lib/types'; @@ -39,9 +39,10 @@ class MyReporter implements Reporter { this._initPromise = (async (htmlReporter: HtmlReporter, config: ReporterConfig): Promise => { const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); - const testAttemptManager = new TestAttemptManager(); - this._staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, testAttemptManager}); + this._staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient}); + this._staticReportBuilder.registerWorkers(workers); + await this._staticReportBuilder.saveStaticFiles(); })(this._htmlReporter, this._config); @@ -59,16 +60,15 @@ class MyReporter implements Reporter { if (status === TestStatus.FAIL) { if (formattedResult.status === TestStatus.FAIL) { - staticReportBuilder.addFail(formattedResult); + await staticReportBuilder.addFail(formattedResult); } else { - staticReportBuilder.addError(formattedResult); + await staticReportBuilder.addError(formattedResult); } } else if (status === TestStatus.SUCCESS) { - staticReportBuilder.addSuccess(formattedResult); + await staticReportBuilder.addSuccess(formattedResult); } else if (status === TestStatus.SKIPPED) { - staticReportBuilder.addSkipped(formattedResult); + await staticReportBuilder.addSkipped(formattedResult); } - await staticReportBuilder.imageHandler.saveTestImages(formattedResult, this._workers); }); } diff --git a/test/unit/hermione.js b/test/unit/hermione.js index e54082ae8..6dd21fdbf 100644 --- a/test/unit/hermione.js +++ b/test/unit/hermione.js @@ -5,7 +5,7 @@ const Database = require('better-sqlite3'); const fsOriginal = require('fs-extra'); const proxyquire = require('proxyquire').noPreserveCache(); const {logger} = require('lib/common-utils'); -const {stubTool} = require('./utils'); +const {stubTool, NoRefImageError, ImageDiffError} = require('./utils'); const mkSqliteDb = () => { const instance = sinon.createStubInstance(Database); @@ -75,24 +75,6 @@ describe('lib/hermione', () => { AFTER_TESTS_READ: 'afterTestsRead' }; - class ImageDiffError extends Error { - name = 'ImageDiffError'; - constructor() { - super(); - this.stateName = ''; - this.currImg = { - path: '' - }; - this.refImg = { - path: '' - }; - } - } - - class NoRefImageError extends Error { - name = 'NoRefImageError'; - } - function mkHermione_() { return stubTool({ forBrowser: sinon.stub().returns({ @@ -178,11 +160,11 @@ describe('lib/hermione', () => { sandbox.stub(logger, 'log'); sandbox.stub(logger, 'warn'); - sandbox.stub(StaticReportBuilder.prototype, 'addSkipped'); - sandbox.stub(StaticReportBuilder.prototype, 'addSuccess'); - sandbox.stub(StaticReportBuilder.prototype, 'addError'); - sandbox.stub(StaticReportBuilder.prototype, 'addFail'); - sandbox.stub(StaticReportBuilder.prototype, 'addRetry'); + sandbox.stub(StaticReportBuilder.prototype, 'addSkipped').callsFake(_.identity); + sandbox.stub(StaticReportBuilder.prototype, 'addSuccess').callsFake(_.identity); + sandbox.stub(StaticReportBuilder.prototype, 'addError').callsFake(_.identity); + sandbox.stub(StaticReportBuilder.prototype, 'addFail').callsFake(_.identity); + sandbox.stub(StaticReportBuilder.prototype, 'addRetry').callsFake(_.identity); sandbox.stub(StaticReportBuilder.prototype, 'saveStaticFiles'); sandbox.stub(StaticReportBuilder.prototype, 'finalize'); @@ -242,7 +224,7 @@ describe('lib/hermione', () => { hermione.emit(events[event], testResult); await hermione.emitAsync(hermione.events.RUNNER_END); - assert.deepEqual(StaticReportBuilder.prototype.addError.args[0][0].state, {name: 'some-title'}); + assert.deepEqual(StaticReportBuilder.prototype.addFail.args[0][0].state, {name: 'some-title'}); }); it(`errored assert view to result on ${event} event`, async () => { @@ -253,7 +235,7 @@ describe('lib/hermione', () => { hermione.emit(events[event], mkStubResult_({title: 'some-title', assertViewResults: [err]})); await hermione.emitAsync(hermione.events.RUNNER_END); - assert.deepEqual(StaticReportBuilder.prototype.addError.args[0][0].state, {name: 'some-title'}); + assert.deepEqual(StaticReportBuilder.prototype.addFail.args[0][0].state, {name: 'some-title'}); }); it(`failed test to result on ${event} event`, async () => { @@ -285,83 +267,4 @@ describe('lib/hermione', () => { }); }); }); - - it('should save image from passed test', async () => { - utils.getReferencePath.callsFake(({stateName}) => `report/${stateName}`); - - await initReporter_({path: '/absolute'}); - const testData = mkStubResult_({assertViewResults: [{refImg: {path: 'ref/path'}, stateName: 'plain'}]}); - hermione.emit(events.TEST_PASS, testData); - await hermione.emitAsync(events.RUNNER_END); - - assert.calledOnceWith(utils.copyFileAsync, 'ref/path', 'report/plain', {reportDir: '/absolute'}); - }); - - it('should save image from assert view error', async () => { - utils.getCurrentPath.callsFake(({stateName}) => `report/${stateName}`); - await initReporter_({path: '/absolute'}); - const err = new NoRefImageError(); - err.stateName = 'plain'; - err.currImg = {path: 'current/path'}; - - hermione.emit(events.RETRY, mkStubResult_({assertViewResults: [err]})); - await hermione.emitAsync(events.RUNNER_END); - - assert.calledOnceWith(utils.copyFileAsync, 'current/path', 'report/plain', {reportDir: '/absolute'}); - }); - - it('should save reference image from assert view fail', async () => { - utils.getReferencePath.callsFake(({stateName}) => `report/${stateName}`); - await initReporter_({path: '/absolute'}); - await stubWorkers(); - - const err = new ImageDiffError(); - err.stateName = 'plain'; - err.refImg = {path: 'reference/path'}; - - hermione.emit(events.TEST_FAIL, mkStubResult_({assertViewResults: [err]})); - await hermione.emitAsync(events.RUNNER_END); - - assert.calledWith(utils.copyFileAsync, 'reference/path', 'report/plain', {reportDir: '/absolute'}); - }); - - it('should save current image from assert view fail', async () => { - utils.getCurrentPath.callsFake(({stateName}) => `report/${stateName}`); - await initReporter_({path: '/absolute'}); - await hermione.emitAsync(events.RUNNER_START, { - registerWorkers: () => { - return {saveDiffTo: sandbox.stub()}; - } - }); - const err = new ImageDiffError(); - err.stateName = 'plain'; - err.currImg = {path: 'current/path'}; - - hermione.emit(events.TEST_FAIL, mkStubResult_({assertViewResults: [err]})); - await hermione.emitAsync(events.RUNNER_END); - - assert.calledWith(utils.copyFileAsync, 'current/path', 'report/plain', {reportDir: '/absolute'}); - }); - - it('should save current diff image from assert view fail', async () => { - fs.readFile.resolves(Buffer.from('some-buff')); - utils.getDiffPath.callsFake(({stateName}) => `report/${stateName}`); - const saveDiffTo = sinon.stub().resolves(); - const err = new ImageDiffError(); - err.stateName = 'plain'; - - await initReporter_(); - - await hermione.emitAsync(events.RUNNER_START, { - registerWorkers: () => { - return {saveDiffTo}; - } - }); - hermione.emit(events.TEST_FAIL, mkStubResult_({assertViewResults: [err]})); - await hermione.emitAsync(events.RUNNER_END); - - assert.calledWith( - saveDiffTo, sinon.match.instanceOf(ImageDiffError), sinon.match('/report/plain') - ); - }); }); diff --git a/test/unit/lib/gui/tool-runner/index.js b/test/unit/lib/gui/tool-runner/index.js index 759627530..f9949ab97 100644 --- a/test/unit/lib/gui/tool-runner/index.js +++ b/test/unit/lib/gui/tool-runner/index.js @@ -9,8 +9,7 @@ const {LOCAL_DATABASE_NAME} = require('lib/constants/database'); const {logger} = require('lib/common-utils'); const {stubTool, stubConfig, mkImagesInfo, mkState, mkSuite} = require('test/unit/utils'); const {SqliteClient} = require('lib/sqlite-client'); -const {TestAttemptManager} = require('lib/test-attempt-manager'); -const {PluginEvents} = require('lib/constants'); +const {PluginEvents, TestStatus} = require('lib/constants'); describe('lib/gui/tool-runner/index', () => { const sandbox = sinon.createSandbox(); @@ -24,7 +23,6 @@ describe('lib/gui/tool-runner/index', () => { let revertReferenceImage; let toolRunnerUtils; let createTestRunner; - let testAttemptManager; const mkTestCollection_ = (testsTree = {}) => { return { @@ -73,15 +71,13 @@ describe('lib/gui/tool-runner/index', () => { createTestRunner = sinon.stub(); - testAttemptManager = new TestAttemptManager(); - toolRunnerUtils = { findTestResult: sandbox.stub(), formatId: sandbox.stub().returns('some-id') }; reportBuilder = sinon.createStubInstance(GuiReportBuilder); - sandbox.stub(reportBuilder, 'testAttemptManager').get(() => testAttemptManager); + reportBuilder.addUpdated.callsFake(_.identity); subscribeOnToolEvents = sandbox.stub().named('reportSubscriber').resolves(); looksSame = sandbox.stub().named('looksSame').resolves({equal: true}); @@ -320,7 +316,10 @@ describe('lib/gui/tool-runner/index', () => { const mkUndoTestData_ = async (stubResult, {stateName = 'plain'} = {}) => { reportBuilder.undoAcceptImage.withArgs(sinon.match({ fullName: 'some-title' - }), 'plain').returns(stubResult); + }), 'plain').returns({ + newResult: {fullName: 'some-title'}, + ...stubResult + }); const tests = [{ id: 'some-id', fullTitle: () => 'some-title', @@ -333,7 +332,8 @@ describe('lib/gui/tool-runner/index', () => { stateName, actualImg: { size: {height: 100, width: 200} - } + }, + status: TestStatus.UPDATED }) ] }]; diff --git a/test/unit/lib/gui/tool-runner/report-subsciber.js b/test/unit/lib/gui/tool-runner/report-subsciber.js index 858e6685b..5616a8ae0 100644 --- a/test/unit/lib/gui/tool-runner/report-subsciber.js +++ b/test/unit/lib/gui/tool-runner/report-subsciber.js @@ -8,13 +8,11 @@ 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'); -const {TestAttemptManager} = require('lib/test-attempt-manager'); -const {FAIL} = require('lib/constants'); +const {UNKNOWN_ATTEMPT} = require('lib/constants'); describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const sandbox = sinon.createSandbox(); - let reportBuilder, testAttemptManager; + let reportBuilder; let client; const events = { @@ -36,12 +34,13 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { beforeEach(() => { reportBuilder = sinon.createStubInstance(GuiReportBuilder); - - testAttemptManager = new TestAttemptManager(); + reportBuilder.addFail.callsFake(_.identity); + reportBuilder.addError.callsFake(_.identity); + reportBuilder.addRunning.callsFake(_.identity); + reportBuilder.addSkipped.callsFake(_.identity); sandbox.stub(GuiReportBuilder, 'create').returns(reportBuilder); sandbox.stub(reportBuilder, 'imageHandler').value({saveTestImages: sinon.stub()}); - sandbox.stub(reportBuilder, 'testAttemptManager').value(testAttemptManager); sandbox.stub(HermioneTestAdapter.prototype, 'id').value('some-id'); client = new EventEmitter(); @@ -65,7 +64,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { const testResult = mkHermioneTestResult(); const mediator = sinon.spy().named('mediator'); - reportBuilder.imageHandler.saveTestImages.callsFake(() => Promise.delay(100).then(mediator)); + reportBuilder.addError.callsFake(() => Promise.delay(100).then(mediator).then(() => ({id: 'some-id'}))); subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_FAIL, testResult); @@ -76,16 +75,18 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { }); describe('TEST_BEGIN', () => { - it('should emit "BEGIN_STATE" event for client with correct data', () => { + it('should emit "BEGIN_STATE" event for client with correct data', async () => { const hermione = mkHermione_(); const testResult = mkHermioneTestResult(); + reportBuilder.addRunning.resolves({id: 'some-id'}); reportBuilder.getTestBranch.withArgs('some-id').returns('test-tree-branch'); subscribeOnToolEvents(hermione, reportBuilder, client); hermione.emit(hermione.events.TEST_BEGIN, testResult); + await hermione.emitAsync(hermione.events.RUNNER_END); - assert.calledOnceWith(client.emit, ClientEvents.BEGIN_STATE, 'test-tree-branch'); + assert.calledWith(client.emit, ClientEvents.BEGIN_STATE, 'test-tree-branch'); }); }); @@ -101,7 +102,7 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { assert.calledOnceWith(reportBuilder.addSkipped, sinon.match({ fullName: 'some-title', browserId: 'some-browser', - attempt: 0 + attempt: UNKNOWN_ATTEMPT })); }); @@ -120,30 +121,6 @@ describe('lib/gui/tool-runner/hermione/report-subscriber', () => { }); describe('TEST_FAIL', () => { - it('should add correct attempt', async () => { - const hermione = mkHermione_(); - const testResult = mkHermioneTestResult({assertViewResults: [{name: ErrorName.IMAGE_DIFF}]}); - - testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL); - - subscribeOnToolEvents(hermione, reportBuilder, client); - hermione.emit(hermione.events.TEST_FAIL, testResult); - await hermione.emitAsync(hermione.events.RUNNER_END); - - assert.calledWithMatch(reportBuilder.addFail, {attempt: 1}); - }); - - it('should save images before fail adding', async () => { - const hermione = mkHermione_(); - const testResult = mkHermioneTestResult({assertViewResults: [{name: ErrorName.IMAGE_DIFF}]}); - - subscribeOnToolEvents(hermione, reportBuilder, client); - hermione.emit(hermione.events.TEST_FAIL, testResult); - await hermione.emitAsync(hermione.events.RUNNER_END); - - assert.callOrder(reportBuilder.imageHandler.saveTestImages, reportBuilder.addFail); - }); - it('should emit "TEST_RESULT" event for client with test data', async () => { const hermione = mkHermione_(); const testResult = mkHermioneTestResult(); diff --git a/test/unit/lib/report-builder/gui.js b/test/unit/lib/report-builder/gui.js index 55ebe87a0..a269c6c71 100644 --- a/test/unit/lib/report-builder/gui.js +++ b/test/unit/lib/report-builder/gui.js @@ -10,15 +10,16 @@ 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'); -const {ErrorName} = require('lib/errors'); const {TestAttemptManager} = require('lib/test-attempt-manager'); +const {ImageDiffError} = require('../../utils'); +const {ImageHandler} = require('lib/image-handler'); const TEST_REPORT_PATH = 'test'; const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`; describe('GuiReportBuilder', () => { const sandbox = sinon.sandbox.create(); - let hasImage, deleteFile, GuiReportBuilder, dbClient, testAttemptManager; + let hasImage, deleteFile, GuiReportBuilder, dbClient, testAttemptManager, copyAndUpdate; const mkGuiReportBuilder_ = async ({toolConfig, pluginConfig} = {}) => { toolConfig = _.defaults(toolConfig || {}, {getAbsoluteUrl: _.noop}); @@ -39,6 +40,9 @@ describe('GuiReportBuilder', () => { const reportBuilder = GuiReportBuilder.create(hermione.htmlReporter, pluginConfig, {dbClient, testAttemptManager}); + const workers = {saveDiffTo: () => {}}; + reportBuilder.registerWorkers(workers); + return reportBuilder; }; @@ -79,15 +83,23 @@ describe('GuiReportBuilder', () => { sandbox.stub(fs, 'writeFileSync'); sandbox.stub(serverUtils, 'prepareCommonJSData'); + copyAndUpdate = sandbox.stub().callsFake(_.identity); + + const imageHandler = sandbox.createStubInstance(ImageHandler); + hasImage = sandbox.stub().returns(true); deleteFile = sandbox.stub().resolves(); GuiReportBuilder = proxyquire('lib/report-builder/gui', { './static': { StaticReportBuilder: proxyquire('lib/report-builder/static', { - '../sqlite-client': {SqliteClient} + '../sqlite-client': {SqliteClient}, + '../image-handler': {ImageHandler: function() { + return imageHandler; + }} }).StaticReportBuilder }, - '../server-utils': {hasImage, deleteFile} + '../server-utils': {hasImage, deleteFile}, + '../test-adapter/utils': {copyAndUpdate} }).GuiReportBuilder; sandbox.stub(GuiTestsTreeBuilder, 'create').returns(Object.create(GuiTestsTreeBuilder.prototype)); @@ -112,7 +124,7 @@ describe('GuiReportBuilder', () => { it(`should add "${IDLE}" status to result`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addIdle(stubTest_()); + await reportBuilder.addIdle(stubTest_()); assert.equal(getTestResult_().status, IDLE); }); @@ -122,7 +134,7 @@ describe('GuiReportBuilder', () => { it(`should add "${RUNNING}" status to result`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addRunning(stubTest_()); + await reportBuilder.addRunning(stubTest_()); assert.equal(getTestResult_().status, RUNNING); }); @@ -132,7 +144,7 @@ describe('GuiReportBuilder', () => { it('should add skipped test to results', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSkipped(stubTest_({ + await reportBuilder.addSkipped(stubTest_({ browserId: 'bro1', skipReason: 'some skip comment', fullName: 'suite-full-name' @@ -151,7 +163,7 @@ describe('GuiReportBuilder', () => { it('should add success test to result', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSuccess(stubTest_({ + await reportBuilder.addSuccess(stubTest_({ browserId: 'bro1' })); @@ -166,7 +178,7 @@ describe('GuiReportBuilder', () => { it('should add failed test to result', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addFail(stubTest_({ + await reportBuilder.addFail(stubTest_({ browserId: 'bro1', imageDir: 'some-image-dir' })); @@ -182,7 +194,7 @@ describe('GuiReportBuilder', () => { it('should add error test to result', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addError(stubTest_({error: {message: 'some-error-message'}})); + await reportBuilder.addError(stubTest_({error: {message: 'some-error-message'}})); assert.match(getTestResult_(), { status: ERROR, @@ -195,7 +207,7 @@ describe('GuiReportBuilder', () => { it('should add "fail" status to result if test result has not equal images', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addRetry(stubTest_({assertViewResults: [{name: ErrorName.IMAGE_DIFF}]})); + await reportBuilder.addRetry(stubTest_({assertViewResults: [new ImageDiffError()]})); assert.equal(getTestResult_().status, FAIL); }); @@ -203,31 +215,32 @@ describe('GuiReportBuilder', () => { it('should add "error" status to result if test result has no image', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addRetry(stubTest_({assertViewResults: [{name: 'some-error-name'}]})); + await reportBuilder.addRetry(stubTest_({assertViewResults: [{name: 'some-error-name'}]})); assert.equal(getTestResult_().status, ERROR); }); }); describe('"addUpdated" method', () => { - it(`should add "${SUCCESS}" status to result if all images updated`, async () => { + it(`should add "${UPDATED}" status to result if all images updated`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addUpdated(stubTest_({imagesInfo: [{status: UPDATED}]})); + await reportBuilder.addUpdated(stubTest_({testPath: [], imagesInfo: [{status: UPDATED}]})); - assert.equal(getTestResult_().status, SUCCESS); + assert.equal(getTestResult_().status, UPDATED); }); - it(`should correctly determine the status based on current result`, async () => { + it(`should add "${UPDATED}" status even if result has errors`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addUpdated(stubTest_({ + await reportBuilder.addUpdated(stubTest_({ + testPath: [], error: {name: 'some-error', message: 'some-message'}, imagesInfo: [{status: FAIL}], attempt: 4 })); - assert.equal(getTestResult_().status, ERROR); + assert.equal(getTestResult_().status, UPDATED); }); it('should update test image for current state name', async () => { @@ -244,12 +257,13 @@ describe('GuiReportBuilder', () => { imagesInfo: [ {stateName: 'plain1', status: UPDATED}, {stateName: 'plain2', status: FAIL} - ] + ], + testPath: [] }); - reportBuilder.addFail(failedTest); + await reportBuilder.addFail(failedTest); GuiTestsTreeBuilder.prototype.getImagesInfo.returns(failedTest.imagesInfo); - reportBuilder.addUpdated(updatedTest); + await reportBuilder.addUpdated(updatedTest); const updatedTestResult = GuiTestsTreeBuilder.prototype.addTestResult.secondCall.args[0]; @@ -270,12 +284,13 @@ describe('GuiReportBuilder', () => { id: 'result-2', imagesInfo: [ {status: UPDATED} - ] + ], + testPath: [] }); - reportBuilder.addFail(failedTest); + await reportBuilder.addFail(failedTest); GuiTestsTreeBuilder.prototype.getImagesInfo.returns(failedTest.imagesInfo); - reportBuilder.addUpdated(updatedTest, 'result-2'); + await reportBuilder.addUpdated(updatedTest, 'result-2'); const {imagesInfo} = GuiTestsTreeBuilder.prototype.addTestResult.secondCall.args[0]; @@ -297,7 +312,7 @@ describe('GuiReportBuilder', () => { [ { method: 'reuseTestsTree', - arg: 'some-tree' + arg: {results: {byId: {}}} }, { method: 'getTestBranch', @@ -412,7 +427,7 @@ describe('GuiReportBuilder', () => { assert.calledOnceWith(GuiTestsTreeBuilder.prototype.removeTestResult, 'result-id'); }); - it('should resolve removed result id', async () => { + it('should resolve removed result', async () => { const resultId = 'result-id'; const stateName = 's-name'; const formattedResult = mkFormattedResultStub_({id: resultId, stateName}); @@ -420,7 +435,7 @@ describe('GuiReportBuilder', () => { const {removedResult} = await reportBuilder.undoAcceptImage(formattedResult, stateName); - assert.deepEqual(removedResult, 'result-id'); + assert.deepEqual(removedResult, formattedResult); }); it('should update image info if "shouldRemoveResult" is false', async () => { @@ -507,7 +522,7 @@ describe('GuiReportBuilder', () => { it('"suiteUrl" field', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSuccess(stubTest_({ + await reportBuilder.addSuccess(stubTest_({ url: 'some-url' })); @@ -517,7 +532,7 @@ describe('GuiReportBuilder', () => { it('"name" field as browser id', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSuccess(stubTest_({browserId: 'yabro'})); + await reportBuilder.addSuccess(stubTest_({browserId: 'yabro'})); assert.equal(getTestResult_().name, 'yabro'); }); @@ -525,7 +540,7 @@ describe('GuiReportBuilder', () => { it('"metaInfo" field', async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSuccess(stubTest_({ + await reportBuilder.addSuccess(stubTest_({ meta: {some: 'value', sessionId: '12345'}, file: '/path/file.js', url: '/test/url' @@ -545,7 +560,7 @@ describe('GuiReportBuilder', () => { it(`add "${name}" field`, async () => { const reportBuilder = await mkGuiReportBuilder_(); - reportBuilder.addSuccess(stubTest_({[name]: value})); + await reportBuilder.addSuccess(stubTest_({[name]: value})); assert.deepEqual(getTestResult_()[name], value); }); @@ -556,7 +571,7 @@ describe('GuiReportBuilder', () => { const reportBuilder = await mkGuiReportBuilder_({pluginConfig: {saveErrorDetails: false}}); const errorDetails = {title: 'some-title', filePath: 'some-path'}; - reportBuilder.addFail(stubTest_({errorDetails})); + await reportBuilder.addFail(stubTest_({errorDetails})); assert.isUndefined(getTestResult_().errorDetails); }); @@ -565,7 +580,7 @@ describe('GuiReportBuilder', () => { const reportBuilder = await mkGuiReportBuilder_({pluginConfig: {saveErrorDetails: true}}); const errorDetails = {title: 'some-title', filePath: 'some-path'}; - reportBuilder.addFail(stubTest_({errorDetails})); + await reportBuilder.addFail(stubTest_({errorDetails})); assert.deepEqual(getTestResult_().errorDetails, errorDetails); }); diff --git a/test/unit/lib/report-builder/static.js b/test/unit/lib/report-builder/static.js index ba80e2f5f..081dd5c10 100644 --- a/test/unit/lib/report-builder/static.js +++ b/test/unit/lib/report-builder/static.js @@ -8,6 +8,9 @@ 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 {SqliteClient} = require('lib/sqlite-client'); +const {NoRefImageError, ImageDiffError} = require('../../utils'); +const sinon = require('sinon'); +const path = require('path'); const TEST_REPORT_PATH = 'test'; const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`; @@ -15,6 +18,7 @@ const TEST_DB_PATH = `${TEST_REPORT_PATH}/${LOCAL_DATABASE_NAME}`; describe('StaticReportBuilder', () => { const sandbox = sinon.sandbox.create(); let StaticReportBuilder, htmlReporter, dbClient; + let cacheExpectedPaths = new Map(), cacheAllImages = new Map(), cacheDiffImages = new Map(); const fs = _.clone(fsOriginal); @@ -23,18 +27,34 @@ describe('StaticReportBuilder', () => { }); const utils = _.clone(originalUtils); - const mkStaticReportBuilder_ = async ({pluginConfig} = {}) => { + const {ImageHandler} = proxyquire('lib/image-handler', { + 'fs-extra': fs, + './image-cache': {cacheExpectedPaths, cacheAllImages, cacheDiffImages}, + './server-utils': utils + }); + + const {LocalImagesSaver} = proxyquire('lib/local-images-saver', { + './server-utils': utils + }); + + const mkStaticReportBuilder_ = async ({pluginConfig, workers} = {}) => { pluginConfig = _.defaults(pluginConfig, {baseHost: '', path: TEST_REPORT_PATH, baseTestPath: ''}); htmlReporter = _.extend(HtmlReporter.create({baseHost: ''}), { reportsSaver: { saveReportData: sandbox.stub() - } + }, + imagesSaver: LocalImagesSaver }); dbClient = await SqliteClient.create({htmlReporter, reportPath: TEST_REPORT_PATH}); - return StaticReportBuilder.create(htmlReporter, pluginConfig, {dbClient}); + const reportBuilder = StaticReportBuilder.create(htmlReporter, pluginConfig, {dbClient}); + workers = workers ?? {saveDiffTo: () => {}}; + + reportBuilder.registerWorkers(workers); + + return reportBuilder; }; const stubTest_ = (opts = {}) => { @@ -52,11 +72,16 @@ describe('StaticReportBuilder', () => { StaticReportBuilder = proxyquire('lib/report-builder/static', { 'fs-extra': fs, - '../server-utils': utils + '../server-utils': utils, + '../image-handler': {ImageHandler} }).StaticReportBuilder; }); afterEach(() => { + cacheAllImages.clear(); + cacheExpectedPaths.clear(); + cacheDiffImages.clear(); + fs.removeSync(TEST_DB_PATH); sandbox.restore(); }); @@ -129,6 +154,121 @@ describe('StaticReportBuilder', () => { }); }); + describe('saving images', () => { + let reportBuilder, saveDiffTo; + + beforeEach(async () => { + saveDiffTo = sinon.stub(); + + reportBuilder = await mkStaticReportBuilder_({workers: {saveDiffTo}}); + + sandbox.stub(utils, 'getReferencePath'); + sandbox.stub(utils, 'getCurrentPath'); + sandbox.stub(utils, 'getDiffPath').returns('diff'); + sandbox.stub(utils, 'copyFileAsync'); + sandbox.stub(utils, 'makeDirFor'); + + sandbox.stub(fs, 'readFile').resolves(Buffer.from('')); + }); + + it('should save image from passed test', async () => { + utils.getReferencePath.callsFake(({stateName}) => `report/${stateName}`); + + await reportBuilder.addSuccess(stubTest_({assertViewResults: [{refImg: {path: 'ref/path'}, stateName: 'plain'}]})); + + assert.calledOnceWith(utils.copyFileAsync, 'ref/path', 'report/plain', {reportDir: 'test'}); + }); + + it('should save image from assert view error', async () => { + utils.getCurrentPath.callsFake(({stateName}) => `report/${stateName}`); + + const err = new NoRefImageError(); + err.stateName = 'plain'; + err.currImg = {path: 'current/path'}; + + await reportBuilder.addFail(stubTest_({assertViewResults: [err]})); + + assert.calledOnceWith(utils.copyFileAsync, 'current/path', 'report/plain', {reportDir: 'test'}); + }); + + it('should save reference image from assert view fail', async () => { + utils.getReferencePath.callsFake(({stateName}) => `report/${stateName}`); + + const err = new ImageDiffError(); + err.stateName = 'plain'; + err.refImg = {path: 'reference/path'}; + + await reportBuilder.addFail(stubTest_({assertViewResults: [err]})); + + assert.calledWith(utils.copyFileAsync, 'reference/path', 'report/plain', {reportDir: 'test'}); + }); + + it('should save current image from assert view fail', async () => { + utils.getCurrentPath.callsFake(({stateName}) => `report/${stateName}`); + + const err = new ImageDiffError(); + err.stateName = 'plain'; + err.currImg = {path: 'current/path'}; + + await reportBuilder.addFail(stubTest_({assertViewResults: [err]})); + + assert.calledWith(utils.copyFileAsync, 'current/path', 'report/plain', {reportDir: 'test'}); + }); + + it('should save current diff image from assert view fail', async () => { + fs.readFile.resolves(Buffer.from('some-buff')); + utils.getDiffPath.callsFake(({stateName}) => `report/${stateName}`); + + const err = new ImageDiffError(); + err.stateName = 'plain'; + + await reportBuilder.addFail(stubTest_({assertViewResults: [err]})); + + assert.calledWith( + saveDiffTo, sinon.match.instanceOf(ImageDiffError), sinon.match('/report/plain') + ); + }); + }); + + describe('saving error details', () => { + let reportBuilder; + + beforeEach(async () => { + reportBuilder = await mkStaticReportBuilder_({pluginConfig: {saveErrorDetails: true}}); + + sandbox.stub(utils, 'makeDirFor').resolves(); + sandbox.stub(utils, 'getDetailsFileName').returns('md5-bro-n-time'); + + sandbox.stub(fs, 'writeFile'); + sandbox.stub(fs, 'mkdirs'); + }); + + it('should do nothing if no error details are available', async () => { + await reportBuilder.addFail(stubTest_()); + + assert.notCalled(fs.writeFile); + }); + + it('should save error details to correct path', async () => { + await reportBuilder.addFail(stubTest_({errorDetails: {filePath: 'some-path'}})); + + assert.calledWithMatch(fs.writeFile, path.resolve(`${TEST_REPORT_PATH}/some-path`), sinon.match.any); + }); + + it('should create directory for error details', async () => { + await reportBuilder.addFail(stubTest_({errorDetails: {filePath: `some-dir/some-path`}})); + + assert.calledOnceWith(fs.mkdirs, path.resolve(TEST_REPORT_PATH, 'some-dir')); + }); + + it('should save error details', async () => { + const data = {foo: 'bar'}; + await reportBuilder.addFail(stubTest_({errorDetails: {filePath: 'some-path', data}})); + + assert.calledWith(fs.writeFile, sinon.match.any, JSON.stringify(data, null, 2)); + }); + }); + describe('finalization', () => { let reportBuilder; diff --git a/test/unit/lib/test-adapter/hermione.ts b/test/unit/lib/test-adapter/hermione.ts index 153044060..fad52c465 100644 --- a/test/unit/lib/test-adapter/hermione.ts +++ b/test/unit/lib/test-adapter/hermione.ts @@ -7,9 +7,10 @@ import tmpOriginal from 'tmp'; import {TestStatus} from 'lib/constants/test-statuses'; import {ERROR_DETAILS_PATH} from 'lib/constants/paths'; import {HermioneTestAdapter, HermioneTestAdapterOptions, ReporterTestResult} from 'lib/test-adapter'; -import {ErrorDetails, HermioneTestResult} from 'lib/types'; +import {HermioneTestResult} from 'lib/types'; import {ImagesInfoFormatter} from 'lib/image-handler'; import * as originalUtils from 'lib/server-utils'; +import * as originalTestAdapterUtils from 'lib/test-adapter/utils'; describe('HermioneTestAdapter', () => { const sandbox = sinon.sandbox.create(); @@ -21,6 +22,7 @@ describe('HermioneTestAdapter', () => { let fs: sinon.SinonStubbedInstance; let tmp: typeof tmpOriginal; let hermioneCache: typeof import('lib/test-adapter/cache/hermione'); + let testAdapterUtils: sinon.SinonStubbedInstance; const mkImagesInfoFormatter = (): sinon.SinonStubbedInstance => { return {} as sinon.SinonStubbedInstance; @@ -50,13 +52,19 @@ describe('HermioneTestAdapter', () => { }); utils = _.clone(originalUtils); + const originalTestAdapterUtils = proxyquire('lib/test-adapter/utils', { + '../../server-utils': utils + }); + testAdapterUtils = _.clone(originalTestAdapterUtils); + HermioneTestAdapter = proxyquire('lib/test-adapter/hermione', { tmp, 'fs-extra': fs, '../plugin-utils': {getSuitePath}, '../history-utils': {getCommandsHistory}, '../server-utils': utils, - './cache/hermione': hermioneCache + './cache/hermione': hermioneCache, + './utils': testAdapterUtils }).HermioneTestAdapter; sandbox.stub(utils, 'getCurrentPath').returns(''); sandbox.stub(utils, 'getDiffPath').returns(''); @@ -159,6 +167,7 @@ describe('HermioneTestAdapter', () => { }); it('should be memoized', () => { + const extractErrorDetails = sandbox.stub(testAdapterUtils, 'extractErrorDetails').returns({}); const testResult = mkTestResult_({ err: { details: {title: 'some-title', data: {foo: 'bar'}} @@ -169,7 +178,7 @@ describe('HermioneTestAdapter', () => { const firstErrDetails = hermioneTestAdapter.errorDetails; const secondErrDetails = hermioneTestAdapter.errorDetails; - assert.calledOnce(getDetailsFileName); + assert.calledOnce(extractErrorDetails); assert.deepEqual(firstErrDetails, secondErrDetails); }); @@ -198,52 +207,6 @@ describe('HermioneTestAdapter', () => { }); }); - describe('saveErrorDetails', () => { - beforeEach(() => { - sandbox.stub(utils, 'makeDirFor').resolves(); - sandbox.stub(utils, 'getDetailsFileName').returns('md5-bro-n-time'); - }); - - it('should do nothing if no error details are available', async () => { - const hermioneTestAdapter = mkHermioneTestResultAdapter(mkTestResult_({err: {} as any})); - - await hermioneTestAdapter.saveErrorDetails(''); - - assert.notCalled(fs.writeFile); - }); - - it('should save error details to correct path', async () => { - const testResult = mkTestResult_({err: { - details: {title: 'some-title', data: {}} - } as any}); - const hermioneTestAdapter = mkHermioneTestResultAdapter(testResult); - const {filePath} = hermioneTestAdapter.errorDetails as ErrorDetails; - - await hermioneTestAdapter.saveErrorDetails('report-path'); - - assert.calledWithMatch(fs.writeFile, `report-path/${filePath}`, sinon.match.any); - }); - - it('should create directory for error details', async () => { - const testResult = mkTestResult_({err: {details: {data: {}}} as any}); - const hermioneTestAdapter = mkHermioneTestResultAdapter(testResult); - - await hermioneTestAdapter.saveErrorDetails('report-path'); - - assert.calledOnceWith(utils.makeDirFor, sinon.match(`report-path/${ERROR_DETAILS_PATH}`)); - }); - - it('should save error details', async () => { - const data = {foo: 'bar'}; - const testResult = mkTestResult_({err: {details: {data}} as any}); - const hermioneTestAdapter = mkHermioneTestResultAdapter(testResult); - - await hermioneTestAdapter.saveErrorDetails(''); - - assert.calledWith(fs.writeFile, sinon.match.any, JSON.stringify(data, null, 2)); - }); - }); - it('should return image dir', () => { const testResult = mkTestResult_({id: 'some-id'}); diff --git a/test/unit/lib/tests-tree-builder/base.js b/test/unit/lib/tests-tree-builder/base.js index cd0b3ab05..fb0ce8e36 100644 --- a/test/unit/lib/tests-tree-builder/base.js +++ b/test/unit/lib/tests-tree-builder/base.js @@ -8,7 +8,7 @@ const {ToolName} = require('lib/constants'); describe('ResultsTreeBuilder', () => { const sandbox = sinon.sandbox.create(); - let ResultsTreeBuilder, builder, determineStatus; + let ResultsTreeBuilder, builder, determineFinalStatus; const mkTestResult_ = (result) => { return _.defaults(result, {imagesInfo: [], metaInfo: {}}); @@ -23,9 +23,9 @@ describe('ResultsTreeBuilder', () => { }; beforeEach(() => { - determineStatus = sandbox.stub().returns(SUCCESS); + determineFinalStatus = sandbox.stub().returns(SUCCESS); ResultsTreeBuilder = proxyquire('lib/tests-tree-builder/base', { - '../common-utils': {determineStatus} + '../common-utils': {determineFinalStatus} }).BaseTestsTreeBuilder; builder = ResultsTreeBuilder.create({toolName: ToolName.Hermione}); @@ -253,7 +253,7 @@ describe('ResultsTreeBuilder', () => { mkFormattedResult_({testPath: ['s1']}) ); - assert.calledOnceWith(determineStatus, [SUCCESS]); + assert.calledOnceWith(determineFinalStatus, [SUCCESS]); }); it('should call "determineFinalStatus" with test result status from last attempt', () => { @@ -266,7 +266,7 @@ describe('ResultsTreeBuilder', () => { mkFormattedResult_({testPath: ['s1'], attempt: 1}) ); - assert.calledWith(determineStatus.lastCall, [SUCCESS]); + assert.calledWith(determineFinalStatus.lastCall, [SUCCESS]); }); it('should call "determineFinalStatus" with all test statuses from each browser', () => { @@ -279,12 +279,12 @@ describe('ResultsTreeBuilder', () => { mkFormattedResult_({testPath: ['s1'], browserId: 'b2'}) ); - assert.calledWith(determineStatus.secondCall, [FAIL, SUCCESS]); + assert.calledWith(determineFinalStatus.secondCall, [FAIL, SUCCESS]); }); it('should call "determineFinalStatus" with statuses from child suites', () => { - determineStatus.withArgs([FAIL]).returns('s1 s2 status'); - determineStatus.withArgs([ERROR]).returns('s1 s3 status'); + determineFinalStatus.withArgs([FAIL]).returns('s1 s2 status'); + determineFinalStatus.withArgs([ERROR]).returns('s1 s3 status'); builder.addTestResult( mkTestResult_({status: FAIL}), mkFormattedResult_({testPath: ['s1', 's2']}) @@ -294,7 +294,7 @@ describe('ResultsTreeBuilder', () => { mkFormattedResult_({testPath: ['s1', 's3']}) ); - assert.calledWith(determineStatus.getCall(3), ['s1 s2 status', 's1 s3 status']); + assert.calledWith(determineFinalStatus.getCall(3), ['s1 s2 status', 's1 s3 status']); }); }); }); diff --git a/test/unit/lib/tests-tree-builder/static.js b/test/unit/lib/tests-tree-builder/static.js index 7a2521c21..6270e7085 100644 --- a/test/unit/lib/tests-tree-builder/static.js +++ b/test/unit/lib/tests-tree-builder/static.js @@ -86,13 +86,13 @@ describe('StaticResultsTreeBuilder', () => { assert.calledWith( StaticTestsTreeBuilder.prototype.addTestResult.firstCall, - formatToTestResult(dataFromDb1, {attempt: 0}), - {browserId: 'yabro', testPath: ['s1'], attempt: 0} + sinon.match(formatToTestResult(dataFromDb1, {attempt: 0})), + sinon.match({browserId: 'yabro', testPath: ['s1'], attempt: 0}) ); assert.calledWith( StaticTestsTreeBuilder.prototype.addTestResult.secondCall, - formatToTestResult(dataFromDb2, {attempt: 0}), - {browserId: 'yabro', testPath: ['s2'], attempt: 0} + sinon.match(formatToTestResult(dataFromDb2, {attempt: 0})), + sinon.match({browserId: 'yabro', testPath: ['s2'], attempt: 0}) ); }); @@ -105,13 +105,13 @@ describe('StaticResultsTreeBuilder', () => { assert.calledWith( StaticTestsTreeBuilder.prototype.addTestResult.firstCall, - formatToTestResult(dataFromDb1, {attempt: 0}), - {browserId: 'yabro', testPath: ['s1'], attempt: 0} + sinon.match(formatToTestResult(dataFromDb1, {attempt: 0})), + sinon.match({browserId: 'yabro', testPath: ['s1'], attempt: 0}) ); assert.calledWith( StaticTestsTreeBuilder.prototype.addTestResult.secondCall, - formatToTestResult(dataFromDb2, {attempt: 1}), - {browserId: 'yabro', testPath: ['s1'], attempt: 1} + sinon.match(formatToTestResult(dataFromDb2, {attempt: 1})), + sinon.match({browserId: 'yabro', testPath: ['s1'], attempt: 1}) ); }); }); diff --git a/test/unit/utils.js b/test/unit/utils.js index 3ba25086d..1131994db 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -130,6 +130,24 @@ function mkFormattedTest(result) { }); } +class NoRefImageError extends Error { + name = 'NoRefImageError'; +} + +class ImageDiffError extends Error { + name = 'ImageDiffError'; + constructor() { + super(); + this.stateName = ''; + this.currImg = { + path: '' + }; + this.refImg = { + path: '' + }; + } +} + module.exports = { stubConfig, stubTestCollection, @@ -141,5 +159,7 @@ module.exports = { mkImagesInfo, mkSuiteTree, mkStorage, - mkFormattedTest + mkFormattedTest, + NoRefImageError, + ImageDiffError }; From f028f037a039c3ad9001d0a768c93e750a349707 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 03:40:21 +0300 Subject: [PATCH 13/17] test: update chrome installation flow --- .../docker/browser-utils/download-chromium.js | 44 +- .../docker/browser-utils/install-chromium.sh | 4 +- .../docker/browser-utils/package-lock.json | 2633 +++-------------- test/func/docker/browser-utils/package.json | 3 +- 4 files changed, 430 insertions(+), 2254 deletions(-) diff --git a/test/func/docker/browser-utils/download-chromium.js b/test/func/docker/browser-utils/download-chromium.js index e366148c2..34cb39a36 100644 --- a/test/func/docker/browser-utils/download-chromium.js +++ b/test/func/docker/browser-utils/download-chromium.js @@ -1,6 +1,44 @@ +const fs = require('fs'); const os = require('os'); const path = require('path'); -const downloadChromium = require('hermione-headless-chrome/lib/download-chromium-by-version'); +const {pipeline} = require('stream'); +const {promisify} = require('util'); +const AdmZip = require('adm-zip'); +const got = require('got'); -downloadChromium(process.env.CHROME_VERSION, path.join(os.homedir(), 'chrome-cache'), 10) - .then(chromePath => console.log(chromePath)); +const streamPipeline = promisify(pipeline); + +const download = async (url) => { + // Create a temporary directory to store the downloaded file + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chrome-download-')); + + // Path for the downloaded zip file + const zipFilePath = path.join(tempDir, 'chrome-linux64.zip'); + + await streamPipeline( + got.stream(url), + fs.createWriteStream(zipFilePath) + ); + + return zipFilePath; +}; + +const extract = async (zipFilePath) => { + const zip = new AdmZip(zipFilePath); + + const outPath = path.dirname(zipFilePath); + + zip.extractAllTo(outPath); + + return path.join(outPath, 'chrome-linux64'); +}; + +(async () => { + // Available good versions of Chrome for testing: https://googlechromelabs.github.io/chrome-for-testing/known-good-versions.json + const CHROME_URL = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/116.0.5845.49/linux64/chrome-linux64.zip'; + + const chromeZipPath = await download(CHROME_URL); + const chromePath = await extract(chromeZipPath); + + console.log(chromePath); +})(); diff --git a/test/func/docker/browser-utils/install-chromium.sh b/test/func/docker/browser-utils/install-chromium.sh index 47eb29ad9..bfe823183 100755 --- a/test/func/docker/browser-utils/install-chromium.sh +++ b/test/func/docker/browser-utils/install-chromium.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# The download script outputs progress messages, but we need only final path, hence we filter output here -CHROME_PATH=$(node ./download-chromium.js |& grep '^/home') -CHROME_PATH=$(dirname $CHROME_PATH) +CHROME_PATH=$(node ./download-chromium.js) mkdir ~/browsers mv $CHROME_PATH ~/browsers/chrome-linux diff --git a/test/func/docker/browser-utils/package-lock.json b/test/func/docker/browser-utils/package-lock.json index 9af07e2b6..7c633889d 100644 --- a/test/func/docker/browser-utils/package-lock.json +++ b/test/func/docker/browser-utils/package-lock.json @@ -8,2439 +8,578 @@ "name": "browser-utils", "version": "0.0.0", "dependencies": { - "hermione-headless-chrome": "^1.1.0" + "adm-zip": "^0.5.10", + "got": "^11.8.6" } }, "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "version": "4.6.0", + "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "4.0.6", + "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", "dependencies": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" } }, - "node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@types/node": "*" } }, - "node_modules/ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "node_modules/@types/node": { + "version": "20.10.0", + "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", + "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" + "undici-types": "~5.26.4" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@types/node": "*" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "license": "MIT", "engines": { - "node": "*" + "node": ">=6.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=10.6.0" } }, "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "version": "7.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" + "mimic-response": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { - "restore-cursor": "^2.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", - "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dependencies": { - "mimic-response": "^1.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "node": ">=10" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cpr": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz", - "integrity": "sha512-Xch4PXQ/KC8lJ+KfJ9JI6eG/nmppLrPPWg5Q+vh65Qr9EjuJEubxh/H/Le1TmCZ7+Xv7iJuNRqapyOFZB+wsxA==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.5", - "minimist": "^1.2.0", - "mkdirp": "~0.5.1", - "rimraf": "^2.5.4" - }, - "bin": { - "cpr": "bin/cpr" + "once": "^1.4.0" } }, - "node_modules/data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "pump": "^3.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dependencies": { - "mimic-response": "^1.0.0" + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dependencies": { - "clone": "^1.0.2" + "node": ">=10.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "node_modules/degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha512-EMAC+riLSC64jKfOs1jp8J7M4ZXstUUwTdwFBEv6HOzL/Ae+eAzMKEK0nJnpof2fnw9IOjmE6u6qXFejVyk8AA==", - "dependencies": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" }, - "node_modules/download-chromium": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/download-chromium/-/download-chromium-2.2.1.tgz", - "integrity": "sha512-y3VF5hgjmQ19hYQWb348rRRxIE+ziS9oGRm2tvMz6aF6E0T7AXcrU9MVkcLyQmt0fxwPMMAi08LxnXHfqeOP8w==", + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", "dependencies": { - "cpr": "^3.0.1", - "debug": "^4.1.1", - "extract-zip": "^1.6.7", - "got": "^9.6.0", - "mkdirp": "^0.5.1", - "promisepipe": "^2.0.0", - "proxy-agent": "^3.1.0", - "proxy-from-env": "^1.0.0" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" }, - "bin": { - "download-chromium": "bin.js" + "engines": { + "node": ">=10.19.0" } }, - "node_modules/duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "json-buffer": "3.0.1" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dependencies": { - "es6-promise": "^4.0.3" + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=4" } }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=10" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" }, - "node_modules/extract-zip": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", - "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", "dependencies": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", - "yauzl": "^2.10.0" + "lowercase-keys": "^2.0.0" }, - "bin": { - "extract-zip": "cli.js" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "license": "ISC" + } + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dependencies": { - "pend": "~1.2.0" + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, - "node_modules/ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", - "dependencies": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "engines": { - "node": ">=0.8.0" + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" } }, - "node_modules/ftp/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "@types/node": { + "version": "20.10.0", + "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", + "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "requires": { + "undici-types": "~5.26.4" + } }, - "node_modules/ftp/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "@types/responselike": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "requires": { + "@types/node": "*" } }, - "node_modules/ftp/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "adm-zip": { + "version": "0.5.10", + "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" }, - "node_modules/gemini-configparser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/gemini-configparser/-/gemini-configparser-1.3.2.tgz", - "integrity": "sha512-aCbDQIPTpL2RgpsgLLx2eKB+tM/vstAnoj38yzDaZIhwALOK/xaET+4+2ofyRtF/Vi4YzPkTcAmlL9P3LOeOdQ==", - "dependencies": { - "lodash": "^4.17.4" - } + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" } }, - "node_modules/get-uri": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", - "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", - "dependencies": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "~3.0.2", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" + "clone-response": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "requires": { + "mimic-response": "^1.0.0" } }, - "node_modules/get-uri/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "decompress-response": { + "version": "6.0.0", + "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, "dependencies": { - "ms": "2.0.0" + "mimic-response": { + "version": "3.1.0", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } } }, - "node_modules/get-uri/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "get-stream": { + "version": "5.2.0", + "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" } }, - "node_modules/hermione-headless-chrome": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hermione-headless-chrome/-/hermione-headless-chrome-1.1.0.tgz", - "integrity": "sha512-0HuIoqr7+5h3hsbNdzNnPZOODdWbAwIS+tYSOBelKhYueXUZRv/2slVI8KLnF64Ji3vWC+ZYzuyDxKVw9NfcEg==", - "dependencies": { - "compare-versions": "^3.5.1", - "download-chromium": "^2.2.0", - "fs-extra": "^8.1.0", - "gemini-configparser": "^1.0.0", - "got": "^9.6.0", - "lodash": "^4.17.15", - "nested-error-stacks": "^2.1.0", - "ora": "^3.4.0" + "got": { + "version": "11.8.6", + "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, - "node_modules/http-cache-semantics": { + "http-cache-semantics": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dependencies": { - "agent-base": "4", - "debug": "3.1.0" - }, - "engines": { - "node": ">= 4.5.0" + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nested-error-stacks": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", - "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" - }, - "node_modules/netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha512-3DWDqAtIiPSkBXZyYEjwebfK56nrlQfRGt642fu8RPaL+ePu750+HCMHxjJCG3iEHq/0aeMvX6KIzlv7nuhfrA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "dependencies": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pac-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", - "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==", - "dependencies": { - "agent-base": "^4.2.0", - "debug": "^4.1.1", - "get-uri": "^2.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "pac-resolver": "^3.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "^4.0.1" - } - }, - "node_modules/pac-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", - "dependencies": { - "co": "^4.6.0", - "degenerator": "^1.0.4", - "ip": "^1.1.5", - "netmask": "^1.0.6", - "thunkify": "^2.1.2" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promisepipe": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/promisepipe/-/promisepipe-2.1.3.tgz", - "integrity": "sha512-pPJRbWEGIuPrjWAdN2lZLRuv+5wMNyig0dniGbMfadS7EK+/kxKqkjAA02iZI+S5Om0tCu6MC1JrjWYs8BlheQ==" - }, - "node_modules/proxy-agent": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz", - "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==", - "dependencies": { - "agent-base": "^4.2.0", - "debug": "4", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "lru-cache": "^5.1.1", - "pac-proxy-agent": "^3.0.1", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dependencies": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - }, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", - "dependencies": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/socks/node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==" - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha512-w9foI80XcGImrhMQ19pxunaEC5Rp2uzxZZg4XBAFRfiLOplk3F0l7wo+bO16vC2/nlQfR/mXZxcduo0MF2GWLg==" - }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==", - "engines": { - "node": "*" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - }, - "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "requires": { - "tslib": "^2.0.1" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==" - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cpr": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz", - "integrity": "sha512-Xch4PXQ/KC8lJ+KfJ9JI6eG/nmppLrPPWg5Q+vh65Qr9EjuJEubxh/H/Le1TmCZ7+Xv7iJuNRqapyOFZB+wsxA==", - "requires": { - "graceful-fs": "^4.1.5", - "minimist": "^1.2.0", - "mkdirp": "~0.5.1", - "rimraf": "^2.5.4" - } - }, - "data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "requires": { - "clone": "^1.0.2" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha512-EMAC+riLSC64jKfOs1jp8J7M4ZXstUUwTdwFBEv6HOzL/Ae+eAzMKEK0nJnpof2fnw9IOjmE6u6qXFejVyk8AA==", - "requires": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "download-chromium": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/download-chromium/-/download-chromium-2.2.1.tgz", - "integrity": "sha512-y3VF5hgjmQ19hYQWb348rRRxIE+ziS9oGRm2tvMz6aF6E0T7AXcrU9MVkcLyQmt0fxwPMMAi08LxnXHfqeOP8w==", - "requires": { - "cpr": "^3.0.1", - "debug": "^4.1.1", - "extract-zip": "^1.6.7", - "got": "^9.6.0", - "mkdirp": "^0.5.1", - "promisepipe": "^2.0.0", - "proxy-agent": "^3.1.0", - "proxy-from-env": "^1.0.0" - } - }, - "duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - } - } - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==" - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extract-zip": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", - "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", - "requires": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", - "yauzl": "^2.10.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "requires": { - "pend": "~1.2.0" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - } - } - }, - "gemini-configparser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/gemini-configparser/-/gemini-configparser-1.3.2.tgz", - "integrity": "sha512-aCbDQIPTpL2RgpsgLLx2eKB+tM/vstAnoj38yzDaZIhwALOK/xaET+4+2ofyRtF/Vi4YzPkTcAmlL9P3LOeOdQ==", - "requires": { - "lodash": "^4.17.4" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "get-uri": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", - "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", - "requires": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "~3.0.2", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "hermione-headless-chrome": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hermione-headless-chrome/-/hermione-headless-chrome-1.1.0.tgz", - "integrity": "sha512-0HuIoqr7+5h3hsbNdzNnPZOODdWbAwIS+tYSOBelKhYueXUZRv/2slVI8KLnF64Ji3vWC+ZYzuyDxKVw9NfcEg==", - "requires": { - "compare-versions": "^3.5.1", - "download-chromium": "^2.2.0", - "fs-extra": "^8.1.0", - "gemini-configparser": "^1.0.0", - "got": "^9.6.0", - "lodash": "^4.17.15", - "nested-error-stacks": "^2.1.0", - "ora": "^3.4.0" - } - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "requires": { - "agent-base": "4", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "requires": { - "graceful-fs": "^4.1.6" - } + "version": "3.0.1", + "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.5.4", + "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "requires": { - "chalk": "^2.0.1" + "json-buffer": "3.0.1" } }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "version": "2.0.0", + "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, "mimic-response": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nested-error-stacks": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", - "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" - }, - "netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha512-3DWDqAtIiPSkBXZyYEjwebfK56nrlQfRGt642fu8RPaL+ePu750+HCMHxjJCG3iEHq/0aeMvX6KIzlv7nuhfrA==" - }, "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + "version": "6.1.0", + "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - } - }, "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "pac-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", - "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==", - "requires": { - "agent-base": "^4.2.0", - "debug": "^4.1.1", - "get-uri": "^2.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "pac-resolver": "^3.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "^4.0.1" - } - }, - "pac-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", - "requires": { - "co": "^4.6.0", - "degenerator": "^1.0.4", - "ip": "^1.1.5", - "netmask": "^1.0.6", - "thunkify": "^2.1.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promisepipe": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/promisepipe/-/promisepipe-2.1.3.tgz", - "integrity": "sha512-pPJRbWEGIuPrjWAdN2lZLRuv+5wMNyig0dniGbMfadS7EK+/kxKqkjAA02iZI+S5Om0tCu6MC1JrjWYs8BlheQ==" - }, - "proxy-agent": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz", - "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==", - "requires": { - "agent-base": "^4.2.0", - "debug": "4", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "lru-cache": "^5.1.1", - "pac-proxy-agent": "^3.0.1", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^4.0.1" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.1", + "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" }, "pump": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } + "quick-lru": { + "version": "5.1.1", + "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - }, - "dependencies": { - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==" - } - } - }, - "socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", - "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "requires": { - "es6-promisify": "^5.0.0" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha512-w9foI80XcGImrhMQ19pxunaEC5Rp2uzxZZg4XBAFRfiLOplk3F0l7wo+bO16vC2/nlQfR/mXZxcduo0MF2GWLg==" - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "requires": { - "prepend-http": "^2.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "requires": { - "defaults": "^1.0.3" + "lowercase-keys": "^2.0.0" } }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + "undici-types": { + "version": "5.26.5", + "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } + "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/test/func/docker/browser-utils/package.json b/test/func/docker/browser-utils/package.json index 5f874c0a3..ff7565d4f 100644 --- a/test/func/docker/browser-utils/package.json +++ b/test/func/docker/browser-utils/package.json @@ -6,6 +6,7 @@ "install-chromium": "./install-chromium.sh" }, "dependencies": { - "hermione-headless-chrome": "1.1.0" + "adm-zip": "^0.5.10", + "got": "^11.8.6" } } From a54204241a49bccff3719daf0e522dc48cc8ca71 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 03:51:56 +0300 Subject: [PATCH 14/17] test: update browser-utils package-lock --- .../docker/browser-utils/package-lock.json | 585 ------------------ 1 file changed, 585 deletions(-) delete mode 100644 test/func/docker/browser-utils/package-lock.json diff --git a/test/func/docker/browser-utils/package-lock.json b/test/func/docker/browser-utils/package-lock.json deleted file mode 100644 index 7c633889d..000000000 --- a/test/func/docker/browser-utils/package-lock.json +++ /dev/null @@ -1,585 +0,0 @@ -{ - "name": "browser-utils", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "browser-utils", - "version": "0.0.0", - "dependencies": { - "adm-zip": "^0.5.10", - "got": "^11.8.6" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "license": "MIT" - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "license": "MIT" - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "license": "ISC" - } - }, - "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "20.10.0", - "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", - "requires": { - "undici-types": "~5.26.4" - } - }, - "@types/responselike": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "requires": { - "@types/node": "*" - } - }, - "adm-zip": { - "version": "0.5.10", - "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - } - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "requires": { - "json-buffer": "3.0.1" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "undici-types": { - "version": "5.26.5", - "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } -} From b7c9b6147477d1eb4bb53a3d5a455e7278e47c65 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 03:54:21 +0300 Subject: [PATCH 15/17] test: add browser-utils package-lock --- .../docker/browser-utils/package-lock.json | 585 ++++++++++++++++++ 1 file changed, 585 insertions(+) create mode 100644 test/func/docker/browser-utils/package-lock.json diff --git a/test/func/docker/browser-utils/package-lock.json b/test/func/docker/browser-utils/package-lock.json new file mode 100644 index 000000000..7c633889d --- /dev/null +++ b/test/func/docker/browser-utils/package-lock.json @@ -0,0 +1,585 @@ +{ + "name": "browser-utils", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "browser-utils", + "version": "0.0.0", + "dependencies": { + "adm-zip": "^0.5.10", + "got": "^11.8.6" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.10.0", + "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", + "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "license": "ISC" + } + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "20.10.0", + "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", + "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/responselike": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "clone-response": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.6", + "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} From f0afacbc41677f583c8908cfe7e47f25a0a01d6a Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 04:01:55 +0300 Subject: [PATCH 16/17] test: update browser-utils package-lock --- .../docker/browser-utils/package-lock.json | 186 ++++++++---------- 1 file changed, 77 insertions(+), 109 deletions(-) diff --git a/test/func/docker/browser-utils/package-lock.json b/test/func/docker/browser-utils/package-lock.json index 7c633889d..94cc6e375 100644 --- a/test/func/docker/browser-utils/package-lock.json +++ b/test/func/docker/browser-utils/package-lock.json @@ -14,9 +14,8 @@ }, "node_modules/@sindresorhus/is": { "version": "4.6.0", - "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -26,9 +25,8 @@ }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", - "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -38,9 +36,8 @@ }, "node_modules/@types/cacheable-request": { "version": "6.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "license": "MIT", "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", @@ -50,60 +47,53 @@ }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", - "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, "node_modules/@types/keyv": { "version": "3.1.4", - "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", - "license": "MIT", + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/responselike": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/adm-zip": { "version": "0.5.10", - "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "license": "MIT", "engines": { "node": ">=6.0" } }, "node_modules/cacheable-lookup": { "version": "5.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "license": "MIT", "engines": { "node": ">=10.6.0" } }, "node_modules/cacheable-request": { "version": "7.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "license": "MIT", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -119,9 +109,8 @@ }, "node_modules/clone-response": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "license": "MIT", "dependencies": { "mimic-response": "^1.0.0" }, @@ -131,9 +120,8 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -146,9 +134,8 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -158,27 +145,24 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/end-of-stream": { "version": "1.4.4", - "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/get-stream": { "version": "5.2.0", - "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -191,9 +175,8 @@ }, "node_modules/got": { "version": "11.8.6", - "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", @@ -216,15 +199,13 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.1", - "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "license": "BSD-2-Clause" + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "node_modules/http2-wrapper": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" @@ -235,42 +216,37 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } }, "node_modules/lowercase-keys": { "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/mimic-response": { "version": "1.0.1", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/normalize-url": { "version": "6.1.0", - "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -280,27 +256,24 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "license": "ISC", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { "wrappy": "1" } }, "node_modules/p-cancelable": { "version": "2.1.1", - "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/pump": { "version": "3.0.0", - "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -308,9 +281,8 @@ }, "node_modules/quick-lru": { "version": "5.1.1", - "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -320,15 +292,13 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, "node_modules/responselike": { "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "license": "MIT", "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -338,26 +308,24 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "license": "ISC" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" } }, "dependencies": { "@sindresorhus/is": { "version": "4.6.0", - "resolved": "https://npm.yandex-team.ru/@sindresorhus%2fis/-/is-4.6.0.tgz?rbtorrent=2a1c08eb908a193556b3cdfe71124806db621b42", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, "@szmarczak/http-timer": { "version": "4.0.6", - "resolved": "https://npm.yandex-team.ru/@szmarczak%2fhttp-timer/-/http-timer-4.0.6.tgz?rbtorrent=98f06600e20bd765eccf2b81a2adec1e2f316831", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "requires": { "defer-to-connect": "^2.0.0" @@ -365,7 +333,7 @@ }, "@types/cacheable-request": { "version": "6.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fcacheable-request/-/cacheable-request-6.0.3.tgz?rbtorrent=29ec2b8a0d70c8ecd89861b452639f37b2fe94ab", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "requires": { "@types/http-cache-semantics": "*", @@ -376,28 +344,28 @@ }, "@types/http-cache-semantics": { "version": "4.0.4", - "resolved": "https://npm.yandex-team.ru/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, "@types/keyv": { "version": "3.1.4", - "resolved": "https://npm.yandex-team.ru/@types%2fkeyv/-/keyv-3.1.4.tgz?rbtorrent=0f14c105aed4413e3449baef1870e2c09ad4cdad", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", "requires": { "@types/node": "*" } }, "@types/node": { - "version": "20.10.0", - "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-20.10.0.tgz?rbtorrent=", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", "requires": { "undici-types": "~5.26.4" } }, "@types/responselike": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/@types%2fresponselike/-/responselike-1.0.3.tgz?rbtorrent=d45eb92a70a2e3e816f766f103bb3d358cf20b7c", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "requires": { "@types/node": "*" @@ -405,17 +373,17 @@ }, "adm-zip": { "version": "0.5.10", - "resolved": "https://npm.yandex-team.ru/adm-zip/-/adm-zip-0.5.10.tgz?rbtorrent=646641a61271f565b2488767942a965dcc270be5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" }, "cacheable-lookup": { "version": "5.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz?rbtorrent=31fcec0fd24cc5f1a086b0100e6f57134e8855f2", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" }, "cacheable-request": { "version": "7.0.4", - "resolved": "https://npm.yandex-team.ru/cacheable-request/-/cacheable-request-7.0.4.tgz?rbtorrent=4f3dab0a8887eb883ef00e26904d4c81203fc995", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "requires": { "clone-response": "^1.0.2", @@ -429,7 +397,7 @@ }, "clone-response": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/clone-response/-/clone-response-1.0.3.tgz?rbtorrent=cfa91bab77f779896f6ac0e703e5e8cddb28a8a0", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "requires": { "mimic-response": "^1.0.0" @@ -437,7 +405,7 @@ }, "decompress-response": { "version": "6.0.0", - "resolved": "https://npm.yandex-team.ru/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "requires": { "mimic-response": "^3.1.0" @@ -445,19 +413,19 @@ "dependencies": { "mimic-response": { "version": "3.1.0", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" } } }, "defer-to-connect": { "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, "end-of-stream": { "version": "1.4.4", - "resolved": "https://npm.yandex-team.ru/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" @@ -465,7 +433,7 @@ }, "get-stream": { "version": "5.2.0", - "resolved": "https://npm.yandex-team.ru/get-stream/-/get-stream-5.2.0.tgz?rbtorrent=807e4f67a2aebd45f55854d6e899ce64972caf23", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "requires": { "pump": "^3.0.0" @@ -473,7 +441,7 @@ }, "got": { "version": "11.8.6", - "resolved": "https://npm.yandex-team.ru/got/-/got-11.8.6.tgz?rbtorrent=1ce8881420143799228ab2287c8b430446e52a81", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "requires": { "@sindresorhus/is": "^4.0.0", @@ -491,12 +459,12 @@ }, "http-cache-semantics": { "version": "4.1.1", - "resolved": "https://npm.yandex-team.ru/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz?rbtorrent=5560afbfd0a6b9ef486a795c446730f7599b8e36", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "http2-wrapper": { "version": "1.0.3", - "resolved": "https://npm.yandex-team.ru/http2-wrapper/-/http2-wrapper-1.0.3.tgz?rbtorrent=be4eb8e6e1e40565634d7aa6a58e96b725740174", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", "requires": { "quick-lru": "^5.1.1", @@ -505,12 +473,12 @@ }, "json-buffer": { "version": "3.0.1", - "resolved": "https://npm.yandex-team.ru/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "keyv": { "version": "4.5.4", - "resolved": "https://npm.yandex-team.ru/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "requires": { "json-buffer": "3.0.1" @@ -518,35 +486,35 @@ }, "lowercase-keys": { "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/lowercase-keys/-/lowercase-keys-2.0.0.tgz?rbtorrent=4a5b8605b468458530f8333fd71555b65efce89d", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, "mimic-response": { "version": "1.0.1", - "resolved": "https://npm.yandex-team.ru/mimic-response/-/mimic-response-1.0.1.tgz?rbtorrent=3285ae670362b295e4cc447695fcb6346b87d35e", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "normalize-url": { "version": "6.1.0", - "resolved": "https://npm.yandex-team.ru/normalize-url/-/normalize-url-6.1.0.tgz?rbtorrent=f41f4e37a4cf3fb849407c10379e14aa6ef24956", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, "once": { "version": "1.4.0", - "resolved": "https://npm.yandex-team.ru/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } }, "p-cancelable": { "version": "2.1.1", - "resolved": "https://npm.yandex-team.ru/p-cancelable/-/p-cancelable-2.1.1.tgz?rbtorrent=9200be3ddfa733b9268950c15ca856d5349d2588", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" }, "pump": { "version": "3.0.0", - "resolved": "https://npm.yandex-team.ru/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", @@ -555,17 +523,17 @@ }, "quick-lru": { "version": "5.1.1", - "resolved": "https://npm.yandex-team.ru/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, "resolve-alpn": { "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, "responselike": { "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/responselike/-/responselike-2.0.1.tgz?rbtorrent=e38fcfe826fd57d7d8f9d21cdb41e32eb4608f8e", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "requires": { "lowercase-keys": "^2.0.0" @@ -573,13 +541,13 @@ }, "undici-types": { "version": "5.26.5", - "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "wrappy": { "version": "1.0.2", - "resolved": "https://npm.yandex-team.ru/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" } } } From 54cee166734ba81c538db8f9d54617de6f07ff8c Mon Sep 17 00:00:00 2001 From: shadowusr Date: Tue, 12 Dec 2023 04:13:26 +0300 Subject: [PATCH 17/17] test: fix chrome installation flow for testing --- test/func/docker/browser-utils/install-chromium.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/func/docker/browser-utils/install-chromium.sh b/test/func/docker/browser-utils/install-chromium.sh index bfe823183..03bbabaed 100755 --- a/test/func/docker/browser-utils/install-chromium.sh +++ b/test/func/docker/browser-utils/install-chromium.sh @@ -3,3 +3,4 @@ CHROME_PATH=$(node ./download-chromium.js) mkdir ~/browsers mv $CHROME_PATH ~/browsers/chrome-linux +chmod +x ~/browsers/chrome-linux/chrome