[9.0] [Space time] extending Scout with perfTracker fixture (#212397) (#215016)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Space time] extending Scout with perfTracker fixture
(#212397)](https://github.com/elastic/kibana/pull/212397)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Dzmitry
Lemechko","email":"dzmitry.lemechko@elastic.co"},"sourceCommit":{"committedDate":"2025-03-14T17:35:31Z","message":"[Space
time] extending Scout with perfTracker fixture (#212397)\n\n##
Summary\n\nOn-Week project \"Client-side performance insights with
Playwright &\nLighthouse\"\n\nrevisiting old PR #66224\n\nThis PR
extends Scout with `perfTracker` fixture designed to analyze\nJavaScript
bundle performance and page-level performance metrics in\nKibana by
leveraging Chrome DevTools Protocol (CDP).\n\nIt intercepts network
requests, filters static bundles, and computes\nbundle size statistics
per page load. Additionally, it collects CDP\nPerformance Domain
Metrics, allowing in-depth analysis of rendering and\nscript execution
times.\n\n\n[README](c013c39b7e/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md)\nfile
with more details and examples was added\n\nOutput example for
`/app/discover` (shorten, just to share the idea):\n\n```\n{\n \"url\":
\"http://localhost:5620/app/discover#/\",\n \"bundleCount\": 87,\n
\"totalSize\": 3133420,\n \"pluginCount\": 9,\n \"plugins\": [\n {\n
\"name\": \"aiops\",\n \"bundlesCount\": 2,\n \"totalSize\": 5982,\n
\"bundles\": [\n {\n \"name\": \"aiops.chunk.1.js\",\n
\"transferredSize\": 2013\n },\n ...\n ]\n },\n {\n \"name\":
\"discover\",\n \"bundlesCount\": 17,\n \"totalSize\": 631605,\n
\"bundles\": [\n {\n \"name\": \"discover.chunk.1.js\",\n
\"transferredSize\": 41915\n },\n ...\n ]\n },\n {\n \"name\":
\"eventAnnotation\",\n \"bundlesCount\": 1,\n \"totalSize\": 8652,\n
\"bundles\": [\n {\n \"name\": \"eventAnnotation.chunk.1.js\",\n
\"transferredSize\": 8652\n }\n ]\n },\n {\n \"name\":
\"expressionXY\",\n \"bundlesCount\": 5,\n \"totalSize\": 203127,\n
\"bundles\": [\n {\n \"name\": \"expressionXY.chunk.2.js\",\n
\"transferredSize\": 5328\n },\n ...\n ]\n },\n ]\n}\n```\n\nYou can
create a Scout UI test and start bundle tracker whenever you\nwant to
compute collected stats when all loading is done. Designed as\ntest, it
allows you to have individual validations for plugins, total\nbundle
size, individual plugin size, etc.\n\n```\n // Ensure all JS bundles are
loaded\n await perfTracker.waitForJsLoad(cdp);\n\n // Collect and
validate stats\n const stats =
perfTracker.collectJsBundleStats(currentUrl);\n expect(\n
stats.totalSize,\n `Total bundles size loaded on page should not exceed
3.0 MB`\n ).toBeLessThan(3 * 1024 * 1024);\n expect(stats.bundleCount,
{\n message: `Total bundle chunks count loaded on page should not exceed
100`,\n }).toBeLessThan(100);\n expect(\n stats.plugins.map((p) =>
p.name),\n { message: 'Unexpected plugins were loaded on page' }\n
).toStrictEqual([\n 'aiops',\n 'discover',\n 'eventAnnotation',\n
'expressionXY',\n 'kbn-ui-shared-deps-npm',\n 'lens',\n 'maps',\n
'unifiedHistogram',\n 'unifiedSearch',\n ]);\n // Validate individual
plugin bundle sizes\n expect(stats.plugins.find((p) => p.name ===
'discover')?.totalSize, {\n message: `Total 'discover' bundles size
should not exceed 625 KB`,\n }).toBeLessThan(625 * 1024);\n```\n\nFull
test
example:\n7b18e85541/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b5f158bc42285c37911abca17c371f4459106fa7","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","wg:performance","backport:version","test:scout","v9.1.0","v8.19.0"],"title":"[Space
time] extending Scout with perfTracker
fixture","number":212397,"url":"https://github.com/elastic/kibana/pull/212397","mergeCommit":{"message":"[Space
time] extending Scout with perfTracker fixture (#212397)\n\n##
Summary\n\nOn-Week project \"Client-side performance insights with
Playwright &\nLighthouse\"\n\nrevisiting old PR #66224\n\nThis PR
extends Scout with `perfTracker` fixture designed to analyze\nJavaScript
bundle performance and page-level performance metrics in\nKibana by
leveraging Chrome DevTools Protocol (CDP).\n\nIt intercepts network
requests, filters static bundles, and computes\nbundle size statistics
per page load. Additionally, it collects CDP\nPerformance Domain
Metrics, allowing in-depth analysis of rendering and\nscript execution
times.\n\n\n[README](c013c39b7e/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md)\nfile
with more details and examples was added\n\nOutput example for
`/app/discover` (shorten, just to share the idea):\n\n```\n{\n \"url\":
\"http://localhost:5620/app/discover#/\",\n \"bundleCount\": 87,\n
\"totalSize\": 3133420,\n \"pluginCount\": 9,\n \"plugins\": [\n {\n
\"name\": \"aiops\",\n \"bundlesCount\": 2,\n \"totalSize\": 5982,\n
\"bundles\": [\n {\n \"name\": \"aiops.chunk.1.js\",\n
\"transferredSize\": 2013\n },\n ...\n ]\n },\n {\n \"name\":
\"discover\",\n \"bundlesCount\": 17,\n \"totalSize\": 631605,\n
\"bundles\": [\n {\n \"name\": \"discover.chunk.1.js\",\n
\"transferredSize\": 41915\n },\n ...\n ]\n },\n {\n \"name\":
\"eventAnnotation\",\n \"bundlesCount\": 1,\n \"totalSize\": 8652,\n
\"bundles\": [\n {\n \"name\": \"eventAnnotation.chunk.1.js\",\n
\"transferredSize\": 8652\n }\n ]\n },\n {\n \"name\":
\"expressionXY\",\n \"bundlesCount\": 5,\n \"totalSize\": 203127,\n
\"bundles\": [\n {\n \"name\": \"expressionXY.chunk.2.js\",\n
\"transferredSize\": 5328\n },\n ...\n ]\n },\n ]\n}\n```\n\nYou can
create a Scout UI test and start bundle tracker whenever you\nwant to
compute collected stats when all loading is done. Designed as\ntest, it
allows you to have individual validations for plugins, total\nbundle
size, individual plugin size, etc.\n\n```\n // Ensure all JS bundles are
loaded\n await perfTracker.waitForJsLoad(cdp);\n\n // Collect and
validate stats\n const stats =
perfTracker.collectJsBundleStats(currentUrl);\n expect(\n
stats.totalSize,\n `Total bundles size loaded on page should not exceed
3.0 MB`\n ).toBeLessThan(3 * 1024 * 1024);\n expect(stats.bundleCount,
{\n message: `Total bundle chunks count loaded on page should not exceed
100`,\n }).toBeLessThan(100);\n expect(\n stats.plugins.map((p) =>
p.name),\n { message: 'Unexpected plugins were loaded on page' }\n
).toStrictEqual([\n 'aiops',\n 'discover',\n 'eventAnnotation',\n
'expressionXY',\n 'kbn-ui-shared-deps-npm',\n 'lens',\n 'maps',\n
'unifiedHistogram',\n 'unifiedSearch',\n ]);\n // Validate individual
plugin bundle sizes\n expect(stats.plugins.find((p) => p.name ===
'discover')?.totalSize, {\n message: `Total 'discover' bundles size
should not exceed 625 KB`,\n }).toBeLessThan(625 * 1024);\n```\n\nFull
test
example:\n7b18e85541/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b5f158bc42285c37911abca17c371f4459106fa7"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/212397","number":212397,"mergeCommit":{"message":"[Space
time] extending Scout with perfTracker fixture (#212397)\n\n##
Summary\n\nOn-Week project \"Client-side performance insights with
Playwright &\nLighthouse\"\n\nrevisiting old PR #66224\n\nThis PR
extends Scout with `perfTracker` fixture designed to analyze\nJavaScript
bundle performance and page-level performance metrics in\nKibana by
leveraging Chrome DevTools Protocol (CDP).\n\nIt intercepts network
requests, filters static bundles, and computes\nbundle size statistics
per page load. Additionally, it collects CDP\nPerformance Domain
Metrics, allowing in-depth analysis of rendering and\nscript execution
times.\n\n\n[README](c013c39b7e/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md)\nfile
with more details and examples was added\n\nOutput example for
`/app/discover` (shorten, just to share the idea):\n\n```\n{\n \"url\":
\"http://localhost:5620/app/discover#/\",\n \"bundleCount\": 87,\n
\"totalSize\": 3133420,\n \"pluginCount\": 9,\n \"plugins\": [\n {\n
\"name\": \"aiops\",\n \"bundlesCount\": 2,\n \"totalSize\": 5982,\n
\"bundles\": [\n {\n \"name\": \"aiops.chunk.1.js\",\n
\"transferredSize\": 2013\n },\n ...\n ]\n },\n {\n \"name\":
\"discover\",\n \"bundlesCount\": 17,\n \"totalSize\": 631605,\n
\"bundles\": [\n {\n \"name\": \"discover.chunk.1.js\",\n
\"transferredSize\": 41915\n },\n ...\n ]\n },\n {\n \"name\":
\"eventAnnotation\",\n \"bundlesCount\": 1,\n \"totalSize\": 8652,\n
\"bundles\": [\n {\n \"name\": \"eventAnnotation.chunk.1.js\",\n
\"transferredSize\": 8652\n }\n ]\n },\n {\n \"name\":
\"expressionXY\",\n \"bundlesCount\": 5,\n \"totalSize\": 203127,\n
\"bundles\": [\n {\n \"name\": \"expressionXY.chunk.2.js\",\n
\"transferredSize\": 5328\n },\n ...\n ]\n },\n ]\n}\n```\n\nYou can
create a Scout UI test and start bundle tracker whenever you\nwant to
compute collected stats when all loading is done. Designed as\ntest, it
allows you to have individual validations for plugins, total\nbundle
size, individual plugin size, etc.\n\n```\n // Ensure all JS bundles are
loaded\n await perfTracker.waitForJsLoad(cdp);\n\n // Collect and
validate stats\n const stats =
perfTracker.collectJsBundleStats(currentUrl);\n expect(\n
stats.totalSize,\n `Total bundles size loaded on page should not exceed
3.0 MB`\n ).toBeLessThan(3 * 1024 * 1024);\n expect(stats.bundleCount,
{\n message: `Total bundle chunks count loaded on page should not exceed
100`,\n }).toBeLessThan(100);\n expect(\n stats.plugins.map((p) =>
p.name),\n { message: 'Unexpected plugins were loaded on page' }\n
).toStrictEqual([\n 'aiops',\n 'discover',\n 'eventAnnotation',\n
'expressionXY',\n 'kbn-ui-shared-deps-npm',\n 'lens',\n 'maps',\n
'unifiedHistogram',\n 'unifiedSearch',\n ]);\n // Validate individual
plugin bundle sizes\n expect(stats.plugins.find((p) => p.name ===
'discover')?.totalSize, {\n message: `Total 'discover' bundles size
should not exceed 625 KB`,\n }).toBeLessThan(625 * 1024);\n```\n\nFull
test
example:\n7b18e85541/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b5f158bc42285c37911abca17c371f4459106fa7"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Dzmitry Lemechko 2025-03-19 11:08:26 +01:00 committed by GitHub
parent c04009575a
commit 270867bac6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1287 additions and 85 deletions

View file

@ -1788,6 +1788,7 @@
"json5": "^2.2.3",
"jsondiffpatch": "0.4.1",
"license-checker": "^25.0.1",
"lighthouse": "^12.4.0",
"listr2": "^8.2.5",
"lmdb": "^2.9.2",
"marge": "^1.0.1",

View file

@ -158,6 +158,7 @@
"@types/supertest",
"@types/react-test-renderer",
"babel-plugin-istanbul",
"lighthouse",
"nyc",
"oboe",
"pixelmatch",

View file

@ -12,6 +12,7 @@ export {
expect,
test,
spaceTest,
lighthouseTest,
globalSetupHook,
tags,
createPlaywrightConfig,
@ -38,6 +39,6 @@ export type {
} from './src/types';
// re-export from Playwright
export type { Locator } from 'playwright/test';
export type { Locator, CDPSession } from 'playwright/test';
export { measurePerformance, measurePerformanceAsync } from './src/common';

View file

@ -11,6 +11,7 @@ import path from 'path';
import Fs from 'fs';
import { ToolingLog } from '@kbn/tooling-log';
import { saveScoutTestConfigOnDisk } from './save_scout_test_config';
import { ServerlessProjectType } from '@kbn/es';
const MOCKED_SCOUT_SERVERS_ROOT = '/mock/repo/root/scout/servers';
@ -34,6 +35,7 @@ const testServersConfig = {
password: 'changeme',
},
serverless: true,
projectType: 'oblt' as ServerlessProjectType,
isCloud: true,
license: 'trial',
cloudUsersFilePath: '/path/to/users',

View file

@ -49,6 +49,8 @@ describe('createPlaywrightConfig', () => {
expect(config.use).toEqual({
serversConfigDir: SCOUT_SERVERS_ROOT,
[VALID_CONFIG_MARKER]: true,
actionTimeout: 10000,
navigationTimeout: 20000,
screenshot: 'only-on-failure',
testIdAttribute: 'data-test-subj',
trace: 'on-first-retry',

View file

@ -72,6 +72,8 @@ export function createPlaywrightConfig(options: ScoutPlaywrightOptions): Playwri
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
actionTimeout: 10000, // Shorten timeout for actions like `click()`
navigationTimeout: 20000, // Shorter timeout for page navigations
// 'configName' is not defined by default to enforce using '--project' flag when running the tests
testIdAttribute: 'data-test-subj',
serversConfigDir: SCOUT_SERVERS_ROOT,

View file

@ -15,6 +15,7 @@ import {
esArchiverFixture,
uiSettingsFixture,
synthtraceFixture,
lighthouseFixture,
} from './worker';
import type {
EsArchiverFixture,
@ -31,9 +32,12 @@ import {
browserAuthFixture,
pageObjectsFixture,
validateTagsFixture,
persistentContext,
perfTrackerFixture,
} from './test';
import type { BrowserAuthFixture, ScoutPage, PageObjects } from './test';
import type { BrowserAuthFixture, ScoutPage, PageObjects, PerfTrackerFixture } from './test';
export type { ScoutPage, PageObjects } from './test';
export type { LighthouseAuditOptions } from './worker';
export const scoutFixtures = mergeTests(
// worker scope fixtures
@ -47,13 +51,16 @@ export const scoutFixtures = mergeTests(
browserAuthFixture,
scoutPageFixture,
pageObjectsFixture,
validateTagsFixture
validateTagsFixture,
// performance fixtures
perfTrackerFixture
);
export interface ScoutTestFixtures {
browserAuth: BrowserAuthFixture;
page: ScoutPage;
pageObjects: PageObjects;
perfTracker: PerfTrackerFixture;
}
export interface ScoutWorkerFixtures extends ApiFixtures {
@ -68,3 +75,5 @@ export interface ScoutWorkerFixtures extends ApiFixtures {
infraSynthtraceEsClient: SynthtraceFixture['infraSynthtraceEsClient'];
otelSynthtraceEsClient: SynthtraceFixture['otelSynthtraceEsClient'];
}
export const lighthouseFixtures = mergeTests(scoutFixtures, persistentContext, lighthouseFixture);

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import * as os from 'os';
import getPort from 'get-port';
import { BrowserContext, chromium } from 'playwright/test';
import { coreWorkerFixtures } from '../../worker';
/**
* Launches browser with persistent context across multiple tests / browser windows in the same test.
* E.g. Lighthouse launches a new browser window and the authentication state
* is not persisted between windows by default, so we can't do page audit without persistent context.
*/
export const persistentContext = coreWorkerFixtures.extend<
{
context: BrowserContext;
},
{ debuggingPort: number }
>({
debuggingPort: [
async ({ log }, use) => {
const port = await getPort({ port: [9222, 9223, 9224] });
log.serviceLoaded(`remote debugging port [${port}]`);
use(port);
},
{ scope: 'worker' },
],
context: [
async ({ log, debuggingPort }, use) => {
const userDataDir = os.tmpdir();
const context = await chromium.launchPersistentContext(userDataDir, {
args: [`--remote-debugging-port=${debuggingPort}`],
});
log.serviceLoaded(`persistentContext on port [${debuggingPort}]`);
await use(context);
await context.close();
},
{ scope: 'test' },
],
});

View file

@ -14,3 +14,6 @@ export type { ScoutPage } from './scout_page';
export { validateTagsFixture } from './validate_tags';
export { pageObjectsFixture, pageObjectsParallelFixture } from './page_objects';
export type { PageObjects } from './page_objects';
export { persistentContext } from './context';
export { perfTrackerFixture } from './performance';
export type { PerfTrackerFixture } from './performance';

View file

@ -8,7 +8,7 @@
*/
import { PageObjects, createCorePageObjects } from '../../../page_objects';
import { ScoutSpaceParallelFixture } from '../../worker';
import { ScoutSpaceParallelFixture, ScoutTestConfig } from '../../worker';
import { scoutPageParallelFixture } from '../scout_page';
/**
@ -23,10 +23,10 @@ export const pageObjectsParallelFixture = scoutPageParallelFixture.extend<
{
pageObjects: PageObjects;
},
{ scoutSpace: ScoutSpaceParallelFixture }
{ scoutSpace: ScoutSpaceParallelFixture; config: ScoutTestConfig }
>({
pageObjects: async ({ page, log }, use) => {
const corePageObjects = createCorePageObjects(page);
pageObjects: async ({ page, config, log }, use) => {
const corePageObjects = createCorePageObjects({ page, config, log });
log.serviceLoaded(`pageObjects`);
await use(corePageObjects);
},

View file

@ -8,6 +8,7 @@
*/
import { PageObjects, createCorePageObjects } from '../../../page_objects';
import { ScoutTestConfig } from '../../worker';
import { scoutPageFixture } from '../scout_page';
/**
@ -18,11 +19,14 @@ import { scoutPageFixture } from '../scout_page';
*
* Note: Page Objects are lazily instantiated on first access.
*/
export const pageObjectsFixture = scoutPageFixture.extend<{
pageObjects: PageObjects;
}>({
pageObjects: async ({ page, log }, use) => {
const corePageObjects = createCorePageObjects(page);
export const pageObjectsFixture = scoutPageFixture.extend<
{
pageObjects: PageObjects;
},
{ config: ScoutTestConfig }
>({
pageObjects: async ({ page, log, config }, use) => {
const corePageObjects = createCorePageObjects({ page, config, log });
log.serviceLoaded('pageObjects');
await use(corePageObjects);
},

View file

@ -0,0 +1,126 @@
## Performance Tracker Fixture
### Overview
`perfTrackerFixture` is a Playwright fixture designed to analyze JavaScript bundle performance and page-level performance metrics in Kibana by leveraging Chrome DevTools Protocol (CDP). It intercepts network requests, filters static bundles, and computes bundle size statistics per page load. Additionally, it collects CDP Performance Domain Metrics, allowing in-depth analysis of rendering and script execution times.
### Key Features
- Uses CDP session to monitor network requests in Playwright tests.
- Exposes `waitForJsLoad` to ensure all incoming bundle requests are fully resolved before proceeding.
- Exposes `captureBundleResponses` to start tracking network requests and collect JavaScript bundle responses.
- Exposes `collectJsBundleStats` to aggregate all captured responses by plugin, making validation easier in tests.
- Automatically attaches collected JS bundle stats as a JSON artifact in test reports, making them available in the `Playwright HTML report`.
- Captures `CDP Performance Domain Metrics`, including:
- JavaScript Heap Usage (jsHeapUsedSize, jsHeapTotalSize)
- CPU Execution Time (cpuTime)
- Script Execution Time (scriptTime)
- Layout & Rendering Performance (layoutTime, layoutCount, styleRecalcCount)
- Frames Per Second (FPS) (fps)
- DOM Complexity Metrics (nodesCount, documentsCount)
### Usage: capturing JS bundles on page
```ts
test.describe(
'Discover App - Performance Metrics & Bundle Analysis',
{ tag: [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE] },
() => {
let cdp: CDPSession;
test.beforeEach(async ({ browserAuth, page, context, perfTracker }) => {
await browserAuth.loginAsAdmin();
cdp = await context.newCDPSession(page);
await cdp.send('Network.enable');
// load the starting page, e.g. '/app/home'
await perfTracker.waitForJsLoad(cdp); // Ensure JS bundles are fully loaded
});
test('collects and validates JS Bundles loaded on page', async ({
page,
pageObjects,
perfTracker,
}) => {
perfTracker.captureBundleResponses(cdp); // Start tracking
// Navigate to Discover app
await pageObjects.collapsibleNav.clickItem('Discover');
const currentUrl = page.url();
expect(currentUrl).toContain('app/discover#/');
// Ensure all JS bundles are loaded
await perfTracker.waitForJsLoad(cdp);
// Collect and validate stats
const stats = perfTracker.collectJsBundleStats(currentUrl);
expect(
stats.totalSize,
`Total bundles size loaded on page should not exceed 3.0 MB`
).toBeLessThan(3 * 1024 * 1024);
expect(stats.bundleCount, {
message: `Total bundle chunks count loaded on page should not exceed 100`,
}).toBeLessThan(100);
expect(
stats.plugins.map((p) => p.name),
{ message: 'Unexpected plugins were loaded on page' }
).toStrictEqual([
'aiops',
'discover',
'eventAnnotation',
'expressionXY',
'kbn-ui-shared-deps-npm',
'lens',
'maps',
'unifiedHistogram',
'unifiedSearch',
]);
// Validate individual plugin bundle sizes
expect(stats.plugins.find((p) => p.name === 'discover')?.totalSize, {
message: `Total 'discover' bundles size should not exceed 625 KB`,
}).toBeLessThan(625 * 1024);
});
```
### Uage: collecting CDP Performance metrics
```ts
test.describe(
'Discover App - Performance Metrics & Bundle Analysis',
{ tag: [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE] },
() => {
let cdp: CDPSession;
test.beforeEach(async ({ browserAuth, page, context, perfTracker }) => {
await browserAuth.loginAsAdmin();
cdp = await context.newCDPSession(page);
// load the starting page, e.g. '/app/home' and wait for loading to finish
});
test('measures Performance Metrics before and after Discover load', async ({
page,
pageObjects,
perfTracker,
}) => {
const beforeMetrics = await perfTracker.capturePagePerformanceMetrics(cdp);
// Navigate to Discover app
await pageObjects.collapsibleNav.clickItem('Discover');
await page.waitForLoadingIndicatorHidden();
const currentUrl = page.url();
expect(currentUrl).toContain('app/discover#/');
await pageObjects.discover.waitForHistogramRendered();
const afterMetrics = await perfTracker.capturePagePerformanceMetrics(cdp);
const perfStats = perfTracker.collectPagePerformanceStats(
currentUrl,
beforeMetrics,
afterMetrics
);
expect(perfStats.cpuTime.diff).toBeLessThan(1.5); // CPU time (seconds) usage during page navigation
expect(perfStats.scriptTime.diff).toBeLessThan(0.4); // Additional time (seconds) spent executing JS scripts
expect(perfStats.layoutTime.diff).toBeLessThan(0.06); // Total layout computation time (seconds)
});
```

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { coreWorkerFixtures } from '../../worker';
import { PerformanceTracker } from './performance_tracker';
export const perfTrackerFixture = coreWorkerFixtures.extend<{ perfTracker: PerformanceTracker }>({
perfTracker: [
async ({ log }, use, testInfo) => {
log.serviceLoaded('perfTracker');
const tracker = new PerformanceTracker(testInfo);
await use(tracker);
},
{ scope: 'test' },
],
});
export type PerfTrackerFixture = ReturnType<typeof perfTrackerFixture>;

View file

@ -0,0 +1,184 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { CDPSession, TestInfo } from '@playwright/test';
import { BundleInfo, PageInfo, PerformanceMetrics, PluginInfo } from './types';
export class PerformanceTracker {
private bundleResponses = new Map<string, BundleInfo>();
constructor(private testInfo: TestInfo) {}
private getRequestData(requestId: string): BundleInfo {
if (!this.bundleResponses.has(requestId)) {
this.bundleResponses.set(requestId, {
url: '',
name: '',
plugin: '',
transferredSize: 0,
headersSize: 0,
});
}
return this.bundleResponses.get(requestId)!;
}
captureBundleResponses(cdp: CDPSession) {
cdp.on('Network.responseReceived', (event) => {
if (event.response.url.endsWith('.js') && event.response.url.includes('bundles')) {
const requestData = this.getRequestData(event.requestId);
requestData.url = event.response.url;
requestData.name = event.response.url.split('/').pop()!;
requestData.plugin = requestData.name.split('.')[0];
requestData.headersSize = event.response.headersText?.length ?? 0;
}
});
cdp.on('Network.loadingFinished', (event) => {
if (this.bundleResponses.has(event.requestId)) {
this.bundleResponses.get(event.requestId)!.transferredSize = event.encodedDataLength;
}
});
}
async waitForJsLoad(cdp: CDPSession, timeout: number = 2000): Promise<void> {
return new Promise<void>((resolve) => {
let lastRequestTime = Date.now();
let activeRequests = 0;
// Track new JS requests
cdp.on('Network.requestWillBeSent', (event) => {
if (event.request.url.endsWith('.js') && event.request.url.includes('bundles')) {
activeRequests++;
lastRequestTime = Date.now();
}
});
// Track when JS requests are completed
cdp.on('Network.loadingFinished', () => {
activeRequests = Math.max(0, activeRequests - 1);
});
// Check every 500ms if no new requests arrived
const interval = setInterval(() => {
if (Date.now() - lastRequestTime > timeout && activeRequests === 0) {
clearInterval(interval);
resolve();
}
}, 500);
});
}
computeBundleStats(bundleResponses: Map<string, BundleInfo>): PageInfo {
const bundles = Array.from(bundleResponses.values());
const pluginAggregates = new Map<string, PluginInfo>();
let totalSize = 0;
for (const { plugin, transferredSize, name } of bundles) {
totalSize += transferredSize;
if (!pluginAggregates.has(plugin)) {
pluginAggregates.set(plugin, { count: 0, totalSize: 0, bundles: [] });
}
const pluginInfo = pluginAggregates.get(plugin)!;
pluginInfo.count += 1;
pluginInfo.totalSize += transferredSize;
pluginInfo.bundles.push({ name, transferredSize });
}
// Sort plugins alphabetically and bundle names inside them
const plugins = Array.from(pluginAggregates.entries())
.map(([pluginName, pluginInfo]) => ({
name: pluginName,
bundlesCount: pluginInfo.count,
totalSize: pluginInfo.totalSize,
bundles: pluginInfo.bundles.sort((a, b) => a.name.localeCompare(b.name)),
}))
.sort((a, b) => a.name.localeCompare(b.name));
return {
bundleCount: bundles.length,
totalSize,
pluginCount: pluginAggregates.size,
plugins,
};
}
collectJsBundleStats(url: string) {
const stats = this.computeBundleStats(this.bundleResponses);
this.testInfo.attach('page-bundles-report', {
body: JSON.stringify({ url, ...stats }, null, 2),
contentType: 'application/json',
});
return stats;
}
// CDP Performance Domain Metrics
async capturePagePerformanceMetrics(cdp: CDPSession) {
await cdp.send('Performance.enable');
const { metrics } = await cdp.send('Performance.getMetrics');
return {
jsHeapUsedSize: metrics.find((m) => m.name === 'JSHeapUsedSize')?.value,
jsHeapTotalSize: metrics.find((m) => m.name === 'JSHeapTotalSize')?.value,
cpuTime: metrics.find((m) => m.name === 'TaskDuration')?.value,
scriptTime: metrics.find((m) => m.name === 'ScriptDuration')?.value,
layoutTime: metrics.find((m) => m.name === 'LayoutDuration')?.value,
fps: metrics.find((m) => m.name === 'FramesPerSecond')?.value,
nodesCount: metrics.find((m) => m.name === 'Nodes')?.value,
documentsCount: metrics.find((m) => m.name === 'Documents')?.value,
layoutCount: metrics.find((m) => m.name === 'LayoutCount')?.value,
styleRecalcCount: metrics.find((m) => m.name === 'RecalcStyleCount')?.value,
};
}
private comparePerformanceMetrics(before: PerformanceMetrics, after: PerformanceMetrics) {
const metrics: Record<
string,
{ before: number; after: number; diff: number; percentage: string }
> = {};
for (const key of Object.keys(after)) {
const metricKey = key as keyof PerformanceMetrics;
if (after[metricKey] !== undefined && before[metricKey] !== undefined) {
const diff = after[metricKey]! - before[metricKey]!;
const percentage =
before[metricKey] !== 0 ? ((diff / before[metricKey]!) * 100).toFixed(2) + '%' : 'N/A';
metrics[metricKey] = {
before: before[metricKey]!,
after: after[metricKey]!,
diff,
percentage,
};
}
}
return metrics;
}
collectPagePerformanceStats = (
url: string,
before: PerformanceMetrics,
after: PerformanceMetrics
) => {
const stats = this.comparePerformanceMetrics(before, after);
this.testInfo.attach('perf-metrics-report', {
body: JSON.stringify({ url, stats }, null, 2),
contentType: 'application/json',
});
return stats;
};
}

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export interface BundleInfo {
url: string;
name: string;
plugin: string;
transferredSize: number;
headersSize: number;
}
export interface PluginInfo {
count: number;
totalSize: number;
bundles: Array<{ name: string; transferredSize: number }>;
}
export interface PageInfo {
bundleCount: number;
totalSize: number;
pluginCount: number;
plugins: Array<{
name: string;
bundlesCount: number;
totalSize: number;
bundles: Array<{ name: string; transferredSize: number }>;
}>;
}
export interface PerformanceMetrics {
jsHeapUsedSize?: number;
jsHeapTotalSize?: number;
cpuTime?: number;
scriptTime?: number;
layoutTime?: number;
fps?: number;
nodesCount?: number;
documentsCount?: number;
layoutCount?: number;
styleRecalcCount?: number;
}

View file

@ -7,10 +7,10 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Page } from '@playwright/test';
import { Page, test as base } from '@playwright/test';
import { subj } from '@kbn/test-subj-selector';
import { PathOptions } from '../../../../common/services/kibana_url';
import { KibanaUrl, ScoutLogger, coreWorkerFixtures } from '../../worker';
import { KibanaUrl, ScoutLogger } from '../../worker';
import { ScoutPage } from '.';
/**
@ -120,7 +120,7 @@ export function extendPlaywrightPage({
* await page.gotoApp('discover);
* ```
*/
export const scoutPageFixture = coreWorkerFixtures.extend<
export const scoutPageFixture = base.extend<
{ page: ScoutPage; log: ScoutLogger },
{ kbnUrl: KibanaUrl }
>({

View file

@ -10,7 +10,7 @@
import { test as base } from '@playwright/test';
import { tags } from '../../../tags';
const supportedTags = tags.DEPLOYMENT_AGNOSTIC;
const supportedTags = [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE];
export const validateTagsFixture = base.extend<{ validateTags: void }>({
validateTags: [

View file

@ -31,3 +31,6 @@ export type { ApiFixtures, ApiParallelWorkerFixtures } from './apis';
export { synthtraceFixture } from './synthtrace';
export type { SynthtraceFixture } from './synthtrace';
export { lighthouseFixture } from './lighthouse';
export type { LighthouseFixture, LighthouseAuditOptions } from './lighthouse';

View file

@ -0,0 +1,44 @@
## Lighthouse Fixture
### Overview
`lighthouseFixture` integrates Lighthouse with Playwright, allowing automated performance and accessibility audits for Kibana and other web applications. It leverages persistent browser sessions to ensure authenticated audits and automatically attaches reports as artifacts in Playwright test reports.
### How it works
- Dynamically imports Lighthouse (ES module)
- Uses `persistentContext` fixture to launch a new browser context on the specified debugging port, preserving authentication state across sessions.
- Loads the provided Kibana URL inside this persistent context and runs the Lighthouse audit, using the same debug port and ensuring the session remains active (otherwise Lighthouse will be redirected to login page)
- Automatically attaches Lighthouse report as html artifact in test reports, making them available in the `Playwright HTML report`.
### Usage: running report on Kibana page
```ts
import { lighthouseTest, tags } from '@kbn/scout';
lighthouseTest.describe(
'Discover App - Lighthouse Performance Audit',
{ tag: [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE] },
() => {
lighthouseTest.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
// loading the data
});
lighthouseTest.afterAll(async ({ kbnClient, uiSettings }) => {
// unloading the data
});
lighthouseTest(
'runs audit on Discover Page',
async ({ browserAuth, lighthouse, page, pageObjects }) => {
await browserAuth.loginAsAdmin();
await pageObjects.discover.goto();
await pageObjects.discover.waitForHistogramRendered();
const currentUrl = page.url();
// Run the Lighthouse audit on the current page and attach the report
await lighthouse.runAudit(currentUrl);
}
);
}
```

View file

@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { RunnerResult } from 'lighthouse';
import { coreWorkerFixtures } from '../core_fixtures';
type OutputMode = 'html' | 'json' | 'csv';
export interface LighthouseAuditOptions {
maxWaitForLoad?: number;
screenEmulation?: {
width: number;
height: number;
};
}
export interface LighthouseFixture {
runAudit: (url: string, options?: LighthouseAuditOptions) => Promise<RunnerResult>;
}
/**
* Lighthouse fixture https://developer.chrome.com/docs/lighthouse/overview/
* It allows to run Lighthouse audits on a given URL
*/
export const lighthouseFixture = coreWorkerFixtures.extend<
{ lighthouse: LighthouseFixture },
{ debuggingPort: number }
>({
lighthouse: [
async ({ log, debuggingPort }, use, testInfo) => {
// Import Lighthouse dynamically (ES module)
const lighthouse = (await import('lighthouse')).default;
if (!debuggingPort) {
throw new Error(
`Remote debugging port is not set: Check 'use.launchOptions.args' in Playwright configuration`
);
}
const DEFAULT_AUDIT_OPTIONS: Partial<import('lighthouse').Flags> = {
maxWaitForLoad: 30000,
output: ['html'],
formFactor: 'desktop',
screenEmulation: {
width: 1920,
height: 1080,
mobile: false,
deviceScaleFactor: 1,
disabled: false,
},
};
const runAudit = async (url: string, auditOptions?: LighthouseAuditOptions) => {
const options: import('lighthouse').Flags = {
port: debuggingPort,
maxWaitForLoad: auditOptions?.maxWaitForLoad ?? DEFAULT_AUDIT_OPTIONS.maxWaitForLoad,
output: DEFAULT_AUDIT_OPTIONS.output as OutputMode[],
formFactor: DEFAULT_AUDIT_OPTIONS.formFactor as 'desktop' | 'mobile',
screenEmulation: {
width:
auditOptions?.screenEmulation?.width ?? DEFAULT_AUDIT_OPTIONS.screenEmulation!.width,
height:
auditOptions?.screenEmulation?.height ??
DEFAULT_AUDIT_OPTIONS.screenEmulation!.height,
mobile: DEFAULT_AUDIT_OPTIONS.screenEmulation!.mobile,
deviceScaleFactor: DEFAULT_AUDIT_OPTIONS.screenEmulation!.deviceScaleFactor,
disabled: DEFAULT_AUDIT_OPTIONS.screenEmulation!.disabled,
},
};
const auditResult = await lighthouse(url, options);
if (!auditResult?.lhr?.categories?.performance?.score) {
throw new Error('Lighthouse audit failed: No performance score found');
}
const perfScore = auditResult.lhr.categories.performance.score;
log.info(`✅ Lighthouse audit completed with performance score: ${perfScore}`);
testInfo.attach('lighthouse-report', {
body: auditResult.report?.[0] ?? 'No report generated',
contentType: 'text/html',
});
return auditResult;
};
use({ runAudit });
},
{ scope: 'test' },
],
});

View file

@ -7,11 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { scoutFixtures, scoutParallelFixtures, globalSetup } from './fixtures';
import { scoutFixtures, scoutParallelFixtures, lighthouseFixtures, globalSetup } from './fixtures';
// Scout core fixtures: worker & test scope
export const test = scoutFixtures;
export const lighthouseTest = lighthouseFixtures;
// Scout core 'space aware' fixtures: worker & test scope
export const spaceTest = scoutParallelFixtures;

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Locator } from 'playwright/test';
import { ScoutPage } from '..';
import { ScoutTestConfig } from '../../types';
export class CollapsibleNav {
private toggleNavButton: Locator;
constructor(private readonly page: ScoutPage, private readonly config: ScoutTestConfig) {
this.toggleNavButton = this.page.testSubj.locator(
this.config.serverless ? 'euiCollapsibleNavButton' : 'toggleNavButton'
);
}
async expandNav() {
if (await this.toggleNavButton.isVisible()) {
const isExpanded = await this.toggleNavButton.getAttribute('aria-expanded');
if (isExpanded === 'false') {
await this.toggleNavButton.click();
}
}
}
async clickItem(itemName: 'Discover' | 'Dashboards' | 'Maps' | 'Machine Learning') {
await this.expandNav();
return this.config.serverless
? this.page.testSubj.click(`*nav-item-id-${itemName.toLocaleLowerCase()}`)
: this.page.click(`[title="${itemName}"]`);
}
}

View file

@ -25,6 +25,10 @@ export class DashboardApp {
await this.page.gotoApp('dashboards');
}
async waitForListingTableToLoad() {
return this.page.testSubj.waitForSelector('table-is-ready', { state: 'visible' });
}
async openNewDashboard() {
await this.page.testSubj.click('newItemButton');
await this.page.testSubj.waitForSelector('emptyDashboardWidget', { state: 'visible' });
@ -92,4 +96,24 @@ export class DashboardApp {
state: 'hidden',
});
}
async waitForPanelsToLoad(
expectedCount: number,
options: { timeout: number; selector: string } = {
timeout: 20000,
selector: '[data-test-subj="embeddablePanel"][data-render-complete="true"]',
}
) {
const startTime = Date.now();
while (Date.now() - startTime < options.timeout) {
const count = await this.page.locator(options.selector).count();
if (count === expectedCount) return;
// Short polling interval
// eslint-disable-next-line playwright/no-wait-for-timeout
await this.page.waitForTimeout(100);
}
throw new Error(`Timeout waiting for ${expectedCount} elements matching ${options.selector}`);
}
}

View file

@ -8,6 +8,9 @@
*/
import { ScoutPage } from '..';
import { ScoutLogger } from '../../common';
import { ScoutTestConfig } from '../../types';
import { CollapsibleNav } from './collapsible_nav';
import { DashboardApp } from './dashboard_app';
import { DatePicker } from './date_picker';
import { DiscoverApp } from './discover_app';
@ -16,6 +19,12 @@ import { MapsPage } from './maps_page';
import { RenderablePage } from './renderable_page';
import { createLazyPageObject } from './utils';
export interface PageObjectsFixtures {
page: ScoutPage;
config: ScoutTestConfig;
log: ScoutLogger;
}
export interface PageObjects {
datePicker: DatePicker;
discover: DiscoverApp;
@ -23,6 +32,7 @@ export interface PageObjects {
filterBar: FilterBar;
maps: MapsPage;
renderable: RenderablePage;
collapsibleNav: CollapsibleNav;
}
/**
@ -31,14 +41,15 @@ export interface PageObjects {
* @param page - `ScoutPage` instance used for initializing page objects.
* @returns An object containing lazy-loaded core page objects.
*/
export function createCorePageObjects(page: ScoutPage): PageObjects {
export function createCorePageObjects(fixtures: PageObjectsFixtures): PageObjects {
return {
datePicker: createLazyPageObject(DatePicker, page),
dashboard: createLazyPageObject(DashboardApp, page),
discover: createLazyPageObject(DiscoverApp, page),
filterBar: createLazyPageObject(FilterBar, page),
maps: createLazyPageObject(MapsPage, page),
renderable: createLazyPageObject(RenderablePage, page),
datePicker: createLazyPageObject(DatePicker, fixtures.page),
dashboard: createLazyPageObject(DashboardApp, fixtures.page),
discover: createLazyPageObject(DiscoverApp, fixtures.page),
filterBar: createLazyPageObject(FilterBar, fixtures.page),
maps: createLazyPageObject(MapsPage, fixtures.page),
renderable: createLazyPageObject(RenderablePage, fixtures.page),
collapsibleNav: createLazyPageObject(CollapsibleNav, fixtures.page, fixtures.config),
// Add new page objects here
};
}

View file

@ -10,11 +10,13 @@
const SERVERLESS_ONLY = ['@svlSecurity', '@svlOblt', '@svlSearch'];
const ESS_ONLY = ['@ess'];
const DEPLOYMENT_AGNOSTIC = SERVERLESS_ONLY.concat(ESS_ONLY);
const PERFORMANCE = ['@perf'];
export const tags = {
ESS_ONLY,
SERVERLESS_ONLY,
DEPLOYMENT_AGNOSTIC,
PERFORMANCE,
};
export const tagsByMode = {

View file

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { test, tags, expect, CDPSession } from '@kbn/scout';
import { testData } from '../fixtures';
test.describe(
'Discover App - Performance Metrics & Bundle Analysis',
{ tag: [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE] },
() => {
let cdp: CDPSession;
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS);
await uiSettings.set({
defaultIndex: testData.DATA_VIEW_ID.LOGSTASH,
'timepicker:timeDefaults': `{ "from": "${testData.LOGSTASH_DEFAULT_START_TIME}", "to": "${testData.LOGSTASH_DEFAULT_END_TIME}"}`,
});
});
test.beforeEach(async ({ browserAuth, page, context, perfTracker }) => {
await browserAuth.loginAsAdmin();
cdp = await context.newCDPSession(page);
await cdp.send('Network.enable');
await page.gotoApp('home');
await page.waitForLoadingIndicatorHidden();
await perfTracker.waitForJsLoad(cdp); // Ensure JS bundles are fully loaded
});
test.afterAll(async ({ kbnClient, uiSettings }) => {
await uiSettings.unset('defaultIndex', 'timepicker:timeDefaults');
await kbnClient.savedObjects.cleanStandardList();
});
test('collects and validates JS Bundles loaded on page', async ({
page,
pageObjects,
perfTracker,
}) => {
perfTracker.captureBundleResponses(cdp); // Start tracking
// Navigate to Discover app
await pageObjects.collapsibleNav.clickItem('Discover');
const currentUrl = page.url();
expect(currentUrl).toContain('app/discover#/');
// Ensure all JS bundles are loaded
await perfTracker.waitForJsLoad(cdp);
// Collect and validate stats
const stats = perfTracker.collectJsBundleStats(currentUrl);
expect(
stats.totalSize,
`Total bundles size loaded on page should not exceed 3.1 MB`
).toBeLessThan(3.1 * 1024 * 1024);
expect(
stats.bundleCount,
`Total bundle chunks count loaded on page should not exceed 100`
).toBeLessThan(100);
expect(
stats.plugins.map((p) => p.name),
'Unexpected plugins were loaded on page'
).toStrictEqual([
'aiops',
'discover',
'eventAnnotation',
'expressionXY',
'kbn-ui-shared-deps-npm',
'lens',
'maps',
'unifiedHistogram',
'unifiedSearch',
]);
// Validate individual plugin bundle sizes
expect(
stats.plugins.find((p) => p.name === 'discover')?.totalSize,
`Total 'discover' bundles size should not exceed 650 KB`
).toBeLessThan(650 * 1024);
expect(
stats.plugins.find((p) => p.name === 'unifiedHistogram')?.totalSize,
`Total 'unifiedHistogram' bundles size should not exceed 150 KB`
).toBeLessThan(150 * 1024);
expect(
stats.plugins.find((p) => p.name === 'unifiedSearch')?.totalSize,
`Total 'unifiedSearch' bundles size should not exceed 450 KB`
).toBeLessThan(450 * 1024);
});
test('measures Performance Metrics before and after Discover load', async ({
page,
pageObjects,
perfTracker,
}) => {
const beforeMetrics = await perfTracker.capturePagePerformanceMetrics(cdp);
// Navigate to Discover app
await pageObjects.collapsibleNav.clickItem('Discover');
await page.waitForLoadingIndicatorHidden();
const currentUrl = page.url();
expect(currentUrl).toContain('app/discover#/');
await pageObjects.discover.waitForHistogramRendered();
const afterMetrics = await perfTracker.capturePagePerformanceMetrics(cdp);
const perfStats = perfTracker.collectPagePerformanceStats(
currentUrl,
beforeMetrics,
afterMetrics
);
expect(
perfStats.cpuTime.diff,
'CPU time (seconds) usage during page navigation should not exceed 1.5 seconds'
).toBeLessThan(1.5);
expect(
perfStats.scriptTime.diff,
'Additional time spent executing JS scripts should not exceed 0.5 second'
).toBeLessThan(0.5);
expect(
perfStats.layoutTime.diff,
'Total layout computation time should not exceed 0.1 second'
).toBeLessThan(0.06);
});
}
);

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { lighthouseTest, tags } from '@kbn/scout';
import { testData } from '../fixtures';
lighthouseTest.describe(
'Discover App - Lighthouse Performance Audit',
{ tag: [...tags.DEPLOYMENT_AGNOSTIC, ...tags.PERFORMANCE] },
() => {
lighthouseTest.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS);
await uiSettings.set({
defaultIndex: testData.DATA_VIEW_ID.LOGSTASH,
'timepicker:timeDefaults': `{ "from": "${testData.LOGSTASH_DEFAULT_START_TIME}", "to": "${testData.LOGSTASH_DEFAULT_END_TIME}"}`,
});
});
lighthouseTest.afterAll(async ({ kbnClient, uiSettings }) => {
await uiSettings.unset('defaultIndex', 'timepicker:timeDefaults');
await kbnClient.savedObjects.cleanStandardList();
});
lighthouseTest(
'runs audit on Discover Page',
async ({ browserAuth, lighthouse, page, pageObjects }) => {
await browserAuth.loginAsAdmin();
await pageObjects.discover.goto();
await pageObjects.discover.waitForHistogramRendered();
const currentUrl = page.url();
// Run the Lighthouse audit on the current page and attach the report
await lighthouse.runAudit(currentUrl);
}
);
}
);

478
yarn.lock
View file

@ -71,7 +71,7 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@apidevtools/json-schema-ref-parser@11.7.2", "@apidevtools/json-schema-ref-parser@^11.5.5":
"@apidevtools/json-schema-ref-parser@11.7.2":
version "11.7.2"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031"
integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==
@ -80,6 +80,15 @@
"@types/json-schema" "^7.0.15"
js-yaml "^4.1.0"
"@apidevtools/json-schema-ref-parser@^11.5.5":
version "11.9.3"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz#0e0c9061fc41cf03737d499a4e6a8299fdd2bfa7"
integrity sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.15"
js-yaml "^4.1.0"
"@apidevtools/json-schema-ref-parser@^9.0.6":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
@ -2938,6 +2947,16 @@
"@formatjs/intl-localematcher" "0.5.4"
tslib "^2.4.0"
"@formatjs/ecma402-abstract@2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.3.tgz#fbc7555c9e4fdd104cd5e23129fa3735be3ad0ba"
integrity sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg==
dependencies:
"@formatjs/fast-memoize" "2.2.6"
"@formatjs/intl-localematcher" "0.6.0"
decimal.js "10"
tslib "2"
"@formatjs/fast-memoize@2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz#33bd616d2e486c3e8ef4e68c99648c196887802b"
@ -2945,6 +2964,22 @@
dependencies:
tslib "^2.4.0"
"@formatjs/fast-memoize@2.2.6":
version "2.2.6"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz#fac0a84207a1396be1f1aa4ee2805b179e9343d1"
integrity sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==
dependencies:
tslib "2"
"@formatjs/icu-messageformat-parser@2.11.1", "@formatjs/icu-messageformat-parser@^2.7.6":
version "2.11.1"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.1.tgz#59d69124b9cf3186800a576c0228947d10594347"
integrity sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA==
dependencies:
"@formatjs/ecma402-abstract" "2.3.3"
"@formatjs/icu-skeleton-parser" "1.8.13"
tslib "2"
"@formatjs/icu-messageformat-parser@2.7.6":
version "2.7.6"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz#3d69806de056d2919d53dad895a5ff4851e4e9ff"
@ -2954,7 +2989,7 @@
"@formatjs/icu-skeleton-parser" "1.8.0"
tslib "^2.4.0"
"@formatjs/icu-messageformat-parser@2.7.8", "@formatjs/icu-messageformat-parser@^2.7.6":
"@formatjs/icu-messageformat-parser@2.7.8":
version "2.7.8"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz#f6d7643001e9bb5930d812f1f9a9856f30fa0343"
integrity sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==
@ -2971,6 +3006,14 @@
"@formatjs/ecma402-abstract" "1.18.2"
tslib "^2.4.0"
"@formatjs/icu-skeleton-parser@1.8.13":
version "1.8.13"
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.13.tgz#5e8b1e1bb467c937735fecb4cb4b345932151a44"
integrity sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg==
dependencies:
"@formatjs/ecma402-abstract" "2.3.3"
tslib "2"
"@formatjs/icu-skeleton-parser@1.8.2":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz#2252c949ae84ee66930e726130ea66731a123c9f"
@ -3004,6 +3047,13 @@
dependencies:
tslib "^2.4.0"
"@formatjs/intl-localematcher@0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.0.tgz#33cf0d33279572c990e02ab75a93122569878082"
integrity sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA==
dependencies:
tslib "2"
"@formatjs/intl-pluralrules@^5.2.12":
version "5.2.12"
resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.12.tgz#0433202e985c7853b8737e7127253e18474eced1"
@ -9159,6 +9209,13 @@
node-addon-api "^3.2.1"
node-gyp-build "^4.3.0"
"@paulirish/trace_engine@0.0.44":
version "0.0.44"
resolved "https://registry.yarnpkg.com/@paulirish/trace_engine/-/trace_engine-0.0.44.tgz#27f2188856c4800e02a68e6f51b6ade9c460cfb8"
integrity sha512-QjDv5qVaUXd5WZzE2ktKvqtGA17v4HFtj6MROCGkK57AZr9n0ZKgcx7dEFho+5EHZ6V6h1upW2eqheo8C4Y4dA==
dependencies:
third-party-web latest
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@ -9285,6 +9342,19 @@
unbzip2-stream "^1.4.3"
yargs "^17.7.2"
"@puppeteer/browsers@2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.8.0.tgz#9d592933cbefc66c37823770844b8cbac52607dd"
integrity sha512-yTwt2KWRmCQAfhvbCRjebaSX8pV1//I0Y3g+A7f/eS7gf0l4eRJoUCvcYdVtboeU4CTOZQuqYbZNS8aBYb8ROQ==
dependencies:
debug "^4.4.0"
extract-zip "^2.0.1"
progress "^2.0.3"
proxy-agent "^6.5.0"
semver "^7.7.1"
tar-fs "^3.0.8"
yargs "^17.7.2"
"@readme/json-schema-ref-parser@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@readme/json-schema-ref-parser/-/json-schema-ref-parser-1.2.0.tgz#8552cde8f8ecf455398c59aa6e2cf5ed2d0f3d31"
@ -9421,6 +9491,56 @@
agentkeepalive "^4.1.3"
lodash "^4.17.21"
"@sentry-internal/tracing@7.120.3":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.120.3.tgz#a54e67c39d23576a72b3f349c1a3fae13e27f2f1"
integrity sha512-Ausx+Jw1pAMbIBHStoQ6ZqDZR60PsCByvHdw/jdH9AqPrNE9xlBSf9EwcycvmrzwyKspSLaB52grlje2cRIUMg==
dependencies:
"@sentry/core" "7.120.3"
"@sentry/types" "7.120.3"
"@sentry/utils" "7.120.3"
"@sentry/core@7.120.3":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.120.3.tgz#88ae2f8c242afce59e32bdee7f866d8788e86c03"
integrity sha512-vyy11fCGpkGK3qI5DSXOjgIboBZTriw0YDx/0KyX5CjIjDDNgp5AGgpgFkfZyiYiaU2Ww3iFuKo4wHmBusz1uA==
dependencies:
"@sentry/types" "7.120.3"
"@sentry/utils" "7.120.3"
"@sentry/integrations@7.120.3":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.120.3.tgz#ea6812b77dea7d0090a5cf85383f154b3bd5b073"
integrity sha512-6i/lYp0BubHPDTg91/uxHvNui427df9r17SsIEXa2eKDwQ9gW2qRx5IWgvnxs2GV/GfSbwcx4swUB3RfEWrXrQ==
dependencies:
"@sentry/core" "7.120.3"
"@sentry/types" "7.120.3"
"@sentry/utils" "7.120.3"
localforage "^1.8.1"
"@sentry/node@^7.0.0":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.120.3.tgz#59a54e1bfccffd28e7d502a5eefea615f07e13f5"
integrity sha512-t+QtekZedEfiZjbkRAk1QWJPnJlFBH/ti96tQhEq7wmlk3VszDXraZvLWZA0P2vXyglKzbWRGkT31aD3/kX+5Q==
dependencies:
"@sentry-internal/tracing" "7.120.3"
"@sentry/core" "7.120.3"
"@sentry/integrations" "7.120.3"
"@sentry/types" "7.120.3"
"@sentry/utils" "7.120.3"
"@sentry/types@7.120.3":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.120.3.tgz#25f69ae27f0c8430f1863ad2a9ee9cab7fccf232"
integrity sha512-C4z+3kGWNFJ303FC+FxAd4KkHvxpNFYAFN8iMIgBwJdpIl25KZ8Q/VdGn0MLLUEHNLvjob0+wvwlcRBBNLXOow==
"@sentry/utils@7.120.3":
version "7.120.3"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.120.3.tgz#0cc891c315d3894eb80c2e7298efd7437e939a5d"
integrity sha512-UDAOQJtJDxZHQ5Nm1olycBIsz2wdGX8SdzyGVHmD8EOQYAeDZQyIlQYohDe9nazdIOQLZCIc3fU0G9gqVLkaGQ==
dependencies:
"@sentry/types" "7.120.3"
"@sideway/address@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5"
@ -14243,15 +14363,20 @@ axe-core@^4.10.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59"
integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==
axe-core@^4.10.2:
version "4.10.2"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
axe-core@^4.2.0, axe-core@^4.6.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0"
integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==
axios@^1.0.0, axios@^1.6.0, axios@^1.6.2, axios@^1.7.4, axios@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979"
integrity sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==
version "1.8.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.3.tgz#9ebccd71c98651d547162a018a1a95a4b4ed4de8"
integrity sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
@ -14545,27 +14670,33 @@ bare-events@^2.0.0, bare-events@^2.2.0:
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.1.tgz#7b6d421f26a7a755e20bf580b727c84b807964c1"
integrity sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==
bare-fs@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.1.tgz#c1985d8d3e07a178956b072d3af67cb8c1fa9391"
integrity sha512-+CjmZANQDFZWy4PGbVdmALIwmt33aJg8qTkVjClU6X4WmZkTPBDxRHiBn7fpqEWEfF3AC2io++erpViAIQbSjg==
bare-fs@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.0.1.tgz#85844f34da819c76754d545323a8b23ed3617c76"
integrity sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==
dependencies:
bare-events "^2.0.0"
bare-os "^2.0.0"
bare-path "^2.0.0"
streamx "^2.13.0"
bare-path "^3.0.0"
bare-stream "^2.0.0"
bare-os@^2.0.0, bare-os@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.2.0.tgz#24364692984d0bd507621754781b31d7872736b2"
integrity sha512-hD0rOPfYWOMpVirTACt4/nK8mC55La12K5fY1ij8HAdfQakD62M+H4o4tpfKzVGLgRDTuk3vjA4GqGXXCeFbag==
bare-os@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.6.0.tgz#1465dd7e1bebe0dec230097a23ad00f7db51f957"
integrity sha512-BUrFS5TqSBdA0LwHop4OjPJwisqxGy6JsWVqV6qaFoe965qqtaKfDzHY5T2YA1gUL0ZeeQeA+4BBc1FJTcHiPw==
bare-path@^2.0.0, bare-path@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.0.tgz#830f17fd39842813ca77d211ebbabe238a88cb4c"
integrity sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==
bare-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.0.0.tgz#b59d18130ba52a6af9276db3e96a2e3d3ea52178"
integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==
dependencies:
bare-os "^2.1.0"
bare-os "^3.0.1"
bare-stream@^2.0.0:
version "2.6.5"
resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.5.tgz#bba8e879674c4c27f7e27805df005c15d7a2ca07"
integrity sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==
dependencies:
streamx "^2.21.0"
base64-js@1.3.1:
version "1.3.1"
@ -15483,6 +15614,16 @@ chroma-js@2.4.2, chroma-js@^2.1.0, chroma-js@^2.4.2:
resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0"
integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==
chrome-launcher@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-1.1.2.tgz#52eff6b3fd7f24b65192b2624a108dadbcca4b9d"
integrity sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==
dependencies:
"@types/node" "*"
escape-string-regexp "^4.0.0"
is-wsl "^2.2.0"
lighthouse-logger "^2.0.1"
chrome-trace-event@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@ -15511,6 +15652,14 @@ chromium-bidi@1.1.0:
mitt "3.0.1"
zod "3.24.1"
chromium-bidi@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-2.1.2.tgz#b0710279f993128d4e0b41c892209ea093217d97"
integrity sha512-vtRWBK2uImo5/W2oG6/cDkkHSm+2t6VHgnj+Rcwhb0pP74OoUb4GipyRX/T/y39gYQPhioP0DPShn+A7P6CHNw==
dependencies:
mitt "^3.0.1"
zod "^3.24.1"
ci-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
@ -16098,6 +16247,18 @@ concaveman@*:
robust-predicates "^2.0.4"
tinyqueue "^2.0.3"
configstore@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
dependencies:
dot-prop "^5.2.0"
graceful-fs "^4.1.2"
make-dir "^3.0.0"
unique-string "^2.0.0"
write-file-atomic "^3.0.0"
xdg-basedir "^4.0.0"
connect-history-api-fallback@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
@ -16425,6 +16586,16 @@ crypto-js@^4.2.0:
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
crypto-random-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
csp_evaluator@1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/csp_evaluator/-/csp_evaluator-1.1.5.tgz#33788d695b7b539b17d5b6eba494431ce931faff"
integrity sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==
css-box-model@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
@ -17153,7 +17324,7 @@ debounce@^1.2.1:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0:
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -17216,10 +17387,10 @@ decamelize@^6.0.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-6.0.0.tgz#8cad4d916fde5c41a264a43d0ecc56fe3d31749e"
integrity sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==
decimal.js@^10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7"
integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==
decimal.js@10, decimal.js@^10.4.1:
version "10.5.0"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22"
integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==
decko@^1.2.0:
version "1.2.0"
@ -17677,6 +17848,16 @@ devtools-protocol@0.0.1380148:
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1380148.tgz#7dcdad06515135b244ff05878ca8019e041c1c55"
integrity sha512-1CJABgqLxbYxVI+uJY/UDUHJtJ0KZTSjNYJYKqd9FRoXT33WDakDHNxRapMEgzeJ/C3rcs01+avshMnPmKQbvA==
devtools-protocol@0.0.1413902:
version "0.0.1413902"
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1413902.tgz#a0f00fe9eb25ab337a8f9656a29e0a1a69f42401"
integrity sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==
devtools-protocol@0.0.1423531:
version "0.0.1423531"
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1423531.tgz#43ba906340fb8ffbda566711ead31f139b2a150a"
integrity sha512-z6cOcajZWxk80zvFnkTGa7tj3oqF+C5SnOF1KSMeAr5/WW/nLNHlEpKr7voSzMz8IaUoq5rjdI0Mqv5k/BUkhg==
dezalgo@^1.0.0, dezalgo@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
@ -17891,6 +18072,13 @@ dot-case@^3.0.4:
no-case "^3.0.4"
tslib "^2.0.3"
dot-prop@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
dependencies:
is-obj "^2.0.0"
dotenv-expand@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
@ -19240,7 +19428,7 @@ fast-equals@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.0.tgz#bef2c423af3939f2c54310df54c57e64cd2adefc"
integrity sha512-u6RBd8cSiLLxAiC04wVsLV6GBFDOXcTCgWkd3wEoFXgidPSoAJENqC9m7Jb2vewSvjBIfXV6icKeh3GTKfIaXA==
fast-fifo@^1.1.0, fast-fifo@^1.2.0:
fast-fifo@^1.2.0, fast-fifo@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
@ -21183,6 +21371,11 @@ http-https@^1.0.0:
resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b"
integrity sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=
http-link-header@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.3.tgz#b367b7a0ad1cf14027953f31aa1df40bb433da2a"
integrity sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==
http-parser-js@>=0.5.1:
version "0.5.3"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
@ -21381,6 +21574,11 @@ ignore@^7.0.3:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.3.tgz#397ef9315dfe0595671eefe8b633fec6943ab733"
integrity sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==
image-ssim@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/image-ssim/-/image-ssim-0.2.0.tgz#83b42c7a2e6e4b85505477fe6917f5dbc56420e5"
integrity sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@ -21584,6 +21782,16 @@ intl-messageformat@10.5.12:
"@formatjs/icu-messageformat-parser" "2.7.6"
tslib "^2.4.0"
intl-messageformat@^10.5.3:
version "10.7.15"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.15.tgz#5cdc62139ef39ece1b083db32dae4d1c9fa5b627"
integrity sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg==
dependencies:
"@formatjs/ecma402-abstract" "2.3.3"
"@formatjs/fast-memoize" "2.2.6"
"@formatjs/icu-messageformat-parser" "2.11.1"
tslib "2"
invariant@^2.1.0, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -21983,6 +22191,11 @@ is-obj@^1.0.1:
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
is-obj@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
is-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
@ -23006,6 +23219,11 @@ jpeg-exif@^1.1.4:
resolved "https://registry.yarnpkg.com/jpeg-exif/-/jpeg-exif-1.1.4.tgz#781a65b6cd74f62cb1c493511020f8d3577a1c2b"
integrity sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==
jpeg-js@^0.4.1, jpeg-js@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
jquery@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de"
@ -23021,6 +23239,11 @@ js-levenshtein@^1.1.6:
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
js-library-detector@^6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/js-library-detector/-/js-library-detector-6.7.0.tgz#5075c71fcf835b71133bca13363b91509a39235a"
integrity sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==
js-search@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/js-search/-/js-search-1.4.3.tgz#23a86d7e064ca53a473930edc48615b6b1c1954a"
@ -23659,6 +23882,13 @@ license-checker@^25.0.1:
spdx-satisfies "^4.0.0"
treeify "^1.1.0"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
dependencies:
immediate "~3.0.5"
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
@ -23666,6 +23896,53 @@ lie@~3.3.0:
dependencies:
immediate "~3.0.5"
lighthouse-logger@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz#48895f639b61cca89346bb6f47f7403a3895fa02"
integrity sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==
dependencies:
debug "^2.6.9"
marky "^1.2.2"
lighthouse-stack-packs@1.12.2:
version "1.12.2"
resolved "https://registry.yarnpkg.com/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.2.tgz#dbe0ccdbc381784ef176f4f8c2367ac5b077d6ca"
integrity sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw==
lighthouse@^12.4.0:
version "12.4.0"
resolved "https://registry.yarnpkg.com/lighthouse/-/lighthouse-12.4.0.tgz#0ca978e6b3ef2c815c132866eb83fbb21845980a"
integrity sha512-1p/YKQpMqfYVSKVOB43RG3xbnxkSUOG0zqVm/bxJHAaAHKrEACgFi8HZxD9CCTFrt+d/Q/x9gjDyeUDarm1SIg==
dependencies:
"@paulirish/trace_engine" "0.0.44"
"@sentry/node" "^7.0.0"
axe-core "^4.10.2"
chrome-launcher "^1.1.2"
configstore "^5.0.1"
csp_evaluator "1.1.5"
devtools-protocol "0.0.1423531"
enquirer "^2.3.6"
http-link-header "^1.1.1"
intl-messageformat "^10.5.3"
jpeg-js "^0.4.4"
js-library-detector "^6.7.0"
lighthouse-logger "^2.0.1"
lighthouse-stack-packs "1.12.2"
lodash-es "^4.17.21"
lookup-closest-locale "6.2.0"
metaviewport-parser "0.3.0"
open "^8.4.0"
parse-cache-control "1.0.1"
puppeteer-core "^24.3.0"
robots-parser "^3.0.1"
semver "^5.3.0"
speedline-core "^1.4.3"
third-party-web "^0.26.5"
tldts-icann "^6.1.16"
ws "^7.0.0"
yargs "^17.3.1"
yargs-parser "^21.0.0"
lilconfig@^2.0.3, lilconfig@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
@ -23775,6 +24052,13 @@ loader-utils@^2.0.0, loader-utils@^2.0.4:
emojis-list "^3.0.0"
json5 "^2.1.2"
localforage@^1.8.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
dependencies:
lie "3.1.1"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@ -24018,6 +24302,11 @@ longest-streak@^2.0.0, longest-streak@^2.0.1:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
lookup-closest-locale@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz#57f665e604fd26f77142d48152015402b607bcf3"
integrity sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -24258,6 +24547,11 @@ marked@^4.3.0:
resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
marky@^1.2.2:
version "1.2.5"
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0"
integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==
mathml-tag-names@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@ -24584,6 +24878,11 @@ meta-png@1.0.6:
resolved "https://registry.yarnpkg.com/meta-png/-/meta-png-1.0.6.tgz#34d78a403cc1c809978d3e9f89485a2700daafce"
integrity sha512-eQtEi5E9axqwqA/sDK1dyhX9kYHCUe2m+45aQ3JHrozjGPs+/ab+hdhPp7A3GUNW+ZAbavrsg5xQ4r5jkGDX+A==
metaviewport-parser@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/metaviewport-parser/-/metaviewport-parser-0.3.0.tgz#6af1e99b5eaf250c049e0af1e84143a39750dea6"
integrity sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ==
methods@^1.1.2, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -24874,7 +25173,7 @@ mississippi@^3.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
mitt@3.0.1:
mitt@3.0.1, mitt@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
@ -26465,6 +26764,11 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.6:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
parse-cache-control@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e"
integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
@ -27744,6 +28048,18 @@ puppeteer-core@24.1.1:
typed-query-selector "^2.12.0"
ws "^8.18.0"
puppeteer-core@^24.3.0:
version "24.4.0"
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.4.0.tgz#a301c58344fe939b487704593681ea9f913fe6f8"
integrity sha512-eFw66gCnWo0X8Hyf9KxxJtms7a61NJVMiSaWfItsFPzFBsjsWdmcNlBdsA1WVwln6neoHhsG+uTVesKmTREn/g==
dependencies:
"@puppeteer/browsers" "2.8.0"
chromium-bidi "2.1.2"
debug "^4.4.0"
devtools-protocol "0.0.1413902"
typed-query-selector "^2.12.0"
ws "^8.18.1"
puppeteer@24.1.1:
version "24.1.1"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.1.1.tgz#dadcfbe05b25a54aee7061325631145db568890b"
@ -27797,11 +28113,6 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
queue-tick@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
quick-format-unescaped@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz#6d6b66b8207aa2b35eef12be1421bb24c428f652"
@ -29331,6 +29642,11 @@ rison-node@1.0.2:
resolved "https://registry.yarnpkg.com/rison-node/-/rison-node-1.0.2.tgz#b7b5f37f39f5ae2a51a973a33c9aa17239a33e4b"
integrity sha1-t7Xzfzn1ripRqXOjPJqhcjmjPks=
robots-parser@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/robots-parser/-/robots-parser-3.0.1.tgz#3d8a3cdfa8ac240cbb062a4bd16fcc0e0fb9ed23"
integrity sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==
robust-predicates@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-2.0.4.tgz#0a2367a93abd99676d075981707f29cfb402248b"
@ -29788,7 +30104,7 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@ -30581,6 +30897,15 @@ specificity@^0.4.1:
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
speedline-core@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/speedline-core/-/speedline-core-1.4.3.tgz#4d6e7276e2063c2d36a375cb25a523ac73475319"
integrity sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==
dependencies:
"@types/node" "*"
image-ssim "^0.2.0"
jpeg-js "^0.4.1"
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
@ -30806,13 +31131,13 @@ stream-slicer@0.0.6:
resolved "https://registry.yarnpkg.com/stream-slicer/-/stream-slicer-0.0.6.tgz#f86b2ac5c2440b7a0a87b71f33665c0788046138"
integrity sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg=
streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.0, streamx@^2.13.2, streamx@^2.14.0, streamx@^2.15.0:
version "2.16.1"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.16.1.tgz#2b311bd34832f08aa6bb4d6a80297c9caef89614"
integrity sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==
streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0, streamx@^2.15.0, streamx@^2.21.0:
version "2.22.0"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7"
integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==
dependencies:
fast-fifo "^1.1.0"
queue-tick "^1.0.1"
fast-fifo "^1.3.2"
text-decoder "^1.1.0"
optionalDependencies:
bare-events "^2.2.0"
@ -31476,16 +31801,16 @@ tar-fs@^2.0.0:
pump "^3.0.0"
tar-stream "^2.1.4"
tar-fs@^3.0.4, tar-fs@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217"
integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==
tar-fs@^3.0.4, tar-fs@^3.0.6, tar-fs@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.8.tgz#8f62012537d5ff89252d01e48690dc4ebed33ab7"
integrity sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==
dependencies:
pump "^3.0.0"
tar-stream "^3.1.5"
optionalDependencies:
bare-fs "^2.1.1"
bare-path "^2.1.0"
bare-fs "^4.0.1"
bare-path "^3.0.0"
tar-stream@^2.1.4:
version "2.2.0"
@ -31654,6 +31979,13 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
text-decoder@^1.1.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65"
integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==
dependencies:
b4a "^1.6.4"
text-diff@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/text-diff/-/text-diff-1.0.1.tgz#6c105905435e337857375c9d2f6ca63e453ff565"
@ -31679,6 +32011,11 @@ thingies@^1.20.0:
resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1"
integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==
third-party-web@^0.26.5, third-party-web@latest:
version "0.26.5"
resolved "https://registry.yarnpkg.com/third-party-web/-/third-party-web-0.26.5.tgz#c442b2a16db66a6064e05e0f060c9ed780f31709"
integrity sha512-tDuKQJUTfjvi9Fcrs1s6YAQAB9mzhTSbBZMfNgtWNmJlHuoFeXO6dzBFdGeCWRvYL50jQGK0jPsBZYxqZQJ2SA==
thread-stream@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33"
@ -31807,10 +32144,17 @@ tinyqueue@^2.0.3:
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
tldts-core@^6.1.46:
version "6.1.46"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.46.tgz#062d64981ee83f934f875c178a97e42bcd13bef7"
integrity sha512-zA3ai/j4aFcmbqTvTONkSBuWs0Q4X4tJxa0gV9sp6kDbq5dAhQDSg0WUkReEm0fBAKAGNj+wPKCCsR8MYOYmwA==
tldts-core@^6.1.46, tldts-core@^6.1.78:
version "6.1.78"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.78.tgz#47b477d9742870daa01dbd5ff9a598a48379728c"
integrity sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==
tldts-icann@^6.1.16:
version "6.1.78"
resolved "https://registry.yarnpkg.com/tldts-icann/-/tldts-icann-6.1.78.tgz#82909d0a1d9a278956aecaa6d49b3789bfd2130f"
integrity sha512-IaM0vesvhlEGsEOG+UQfaW6AuQPB9MHI8sz9K8T8sUjj1rKxBp8SclNqUG8a3WQGv87VOoa+ICZrGXh/tnc/VA==
dependencies:
tldts-core "^6.1.78"
tldts@^6.1.32:
version "6.1.46"
@ -32113,6 +32457,11 @@ tsd@^0.31.1:
path-exists "^4.0.0"
read-pkg-up "^7.0.0"
tslib@2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tslib@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
@ -32133,11 +32482,6 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tslib@~2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
@ -32526,6 +32870,13 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
unique-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
dependencies:
crypto-random-string "^2.0.0"
unist-builder@2.0.3, unist-builder@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436"
@ -34051,15 +34402,15 @@ write-file-atomic@^4.0.2:
imurmurhash "^0.1.4"
signal-exit "^3.0.7"
ws@^7.3.1, ws@^7.4.2:
ws@^7.0.0, ws@^7.3.1, ws@^7.4.2:
version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@^8.16.0, ws@^8.18.0, ws@^8.2.3, ws@^8.9.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
ws@^8.16.0, ws@^8.18.0, ws@^8.18.1, ws@^8.2.3, ws@^8.9.0:
version "8.18.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
x-default-browser@^0.4.0:
version "0.4.0"
@ -34068,6 +34419,11 @@ x-default-browser@^0.4.0:
optionalDependencies:
default-browser-id "^1.0.4"
xdg-basedir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xml-crypto@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-6.0.1.tgz#81d224cf9f2cac9f15190bbb4ef93f53e1f8a8e8"
@ -34389,7 +34745,7 @@ zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.4, zod-to-json-schema@^3.22
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz#f08c6725091aadabffa820ba8d50c7ab527f227a"
integrity sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==
zod@3.24.1:
zod@3.24.1, zod@^3.24.1:
version "3.24.1"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee"
integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==