mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[kbn/optimizer] report sizes of assets produced by optimizer (#71319)
* Revert "Report page load asset size (#66224)" This reverts commit6f57fa0b2d
. * [kbn/optimizer] report sizes of assets produced by optimizer * coalese the fast-glob versions we're using to prevent additional installs * update kbn/pm dist * Revert "update kbn/pm dist" This reverts commit68e24f0fad
. * Revert "coalese the fast-glob versions we're using to prevent additional installs" This reverts commit4201fb60b6
. * remove fast-glob, just recursivly call readdirSync() * update integration tests to use new chunk filename Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
58b4127b68
commit
a0f7dced13
21 changed files with 86 additions and 2995 deletions
1
Jenkinsfile
vendored
1
Jenkinsfile
vendored
|
@ -42,7 +42,6 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true)
|
|||
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
|
||||
'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'),
|
||||
'xpack-savedObjectsFieldMetrics': kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh'),
|
||||
// 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'),
|
||||
'xpack-securitySolutionCypress': { processNumber ->
|
||||
whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) {
|
||||
kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -220,7 +220,7 @@ it('prepares assets for distribution', async () => {
|
|||
|
||||
expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle');
|
||||
expectFileMatchesSnapshotWithCompression(
|
||||
'plugins/foo/target/public/1.plugin.js',
|
||||
'plugins/foo/target/public/foo.chunk.1.js',
|
||||
'foo async bundle'
|
||||
);
|
||||
expectFileMatchesSnapshotWithCompression('plugins/bar/target/public/bar.plugin.js', 'bar bundle');
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import Path from 'path';
|
||||
|
||||
import { materialize, mergeMap, dematerialize } from 'rxjs/operators';
|
||||
import { CiStatsReporter } from '@kbn/dev-utils';
|
||||
|
||||
|
@ -24,6 +27,32 @@ import { OptimizerUpdate$ } from './run_optimizer';
|
|||
import { OptimizerState, OptimizerConfig } from './optimizer';
|
||||
import { pipeClosure } from './common';
|
||||
|
||||
const flatten = <T>(arr: Array<T | T[]>): T[] =>
|
||||
arr.reduce((acc: T[], item) => acc.concat(item), []);
|
||||
|
||||
interface Entry {
|
||||
relPath: string;
|
||||
stats: Fs.Stats;
|
||||
}
|
||||
|
||||
const getFiles = (dir: string, parent?: string) =>
|
||||
flatten(
|
||||
Fs.readdirSync(dir).map((name): Entry | Entry[] => {
|
||||
const absPath = Path.join(dir, name);
|
||||
const relPath = parent ? Path.join(parent, name) : name;
|
||||
const stats = Fs.statSync(absPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return getFiles(absPath, relPath);
|
||||
}
|
||||
|
||||
return {
|
||||
relPath,
|
||||
stats,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export function reportOptimizerStats(reporter: CiStatsReporter, config: OptimizerConfig) {
|
||||
return pipeClosure((update$: OptimizerUpdate$) => {
|
||||
let lastState: OptimizerState | undefined;
|
||||
|
@ -36,16 +65,55 @@ export function reportOptimizerStats(reporter: CiStatsReporter, config: Optimize
|
|||
|
||||
if (n.kind === 'C' && lastState) {
|
||||
await reporter.metrics(
|
||||
config.bundles.map((bundle) => {
|
||||
// make the cache read from the cache file since it was likely updated by the worker
|
||||
bundle.cache.refresh();
|
||||
flatten(
|
||||
config.bundles.map((bundle) => {
|
||||
// make the cache read from the cache file since it was likely updated by the worker
|
||||
bundle.cache.refresh();
|
||||
|
||||
return {
|
||||
group: `@kbn/optimizer bundle module count`,
|
||||
id: bundle.id,
|
||||
value: bundle.cache.getModuleCount() || 0,
|
||||
};
|
||||
})
|
||||
const outputFiles = getFiles(bundle.outputDir).filter(
|
||||
(file) => !(file.relPath.startsWith('.') || file.relPath.endsWith('.map'))
|
||||
);
|
||||
|
||||
const entryName = `${bundle.id}.${bundle.type}.js`;
|
||||
const entry = outputFiles.find((f) => f.relPath === entryName);
|
||||
if (!entry) {
|
||||
throw new Error(
|
||||
`Unable to find bundle entry named [${entryName}] in [${bundle.outputDir}]`
|
||||
);
|
||||
}
|
||||
|
||||
const chunkPrefix = `${bundle.id}.chunk.`;
|
||||
const asyncChunks = outputFiles.filter((f) => f.relPath.startsWith(chunkPrefix));
|
||||
const miscFiles = outputFiles.filter(
|
||||
(f) => f !== entry && !asyncChunks.includes(f)
|
||||
);
|
||||
const sumSize = (files: Entry[]) =>
|
||||
files.reduce((acc: number, f) => acc + f.stats!.size, 0);
|
||||
|
||||
return [
|
||||
{
|
||||
group: `@kbn/optimizer bundle module count`,
|
||||
id: bundle.id,
|
||||
value: bundle.cache.getModuleCount() || 0,
|
||||
},
|
||||
{
|
||||
group: `page load bundle size`,
|
||||
id: bundle.id,
|
||||
value: entry.stats!.size,
|
||||
},
|
||||
{
|
||||
group: `async chunks size`,
|
||||
id: bundle.id,
|
||||
value: sumSize(asyncChunks),
|
||||
},
|
||||
{
|
||||
group: `miscellaneous assets size`,
|
||||
id: bundle.id,
|
||||
value: sumSize(miscFiles),
|
||||
},
|
||||
];
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,8 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker:
|
|||
|
||||
output: {
|
||||
path: bundle.outputDir,
|
||||
filename: `[name].${bundle.type}.js`,
|
||||
filename: `${bundle.id}.${bundle.type}.js`,
|
||||
chunkFilename: `${bundle.id}.chunk.[id].js`,
|
||||
devtoolModuleFilenameTemplate: (info) =>
|
||||
`/${bundle.type}:${bundle.id}/${Path.relative(
|
||||
bundle.sourceRoot,
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"@types/joi": "^13.4.2",
|
||||
"@types/lodash": "^4.14.155",
|
||||
"@types/parse-link-header": "^1.0.0",
|
||||
"@types/puppeteer": "^3.0.0",
|
||||
"@types/strip-ansi": "^5.2.1",
|
||||
"@types/xml2js": "^0.4.5",
|
||||
"diff": "^4.0.1"
|
||||
|
@ -31,7 +30,6 @@
|
|||
"joi": "^13.5.2",
|
||||
"lodash": "^4.17.15",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"puppeteer": "^3.3.0",
|
||||
"rxjs": "^6.5.5",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"tar-fs": "^1.16.3",
|
||||
|
|
|
@ -60,4 +60,3 @@ export { makeJunitReportPath } from './junit_report_path';
|
|||
export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix';
|
||||
|
||||
export * from './functional_test_runner';
|
||||
export * from './page_load_metrics';
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { NavigationOptions, createUrl, navigateToApps } from './navigation';
|
||||
|
||||
export async function capturePageLoadMetrics(log: ToolingLog, options: NavigationOptions) {
|
||||
const responsesByPageView = await navigateToApps(log, options);
|
||||
|
||||
const assetSizeMeasurements = new Map<string, number[]>();
|
||||
|
||||
const numberOfPagesVisited = responsesByPageView.size;
|
||||
|
||||
for (const [, frameResponses] of responsesByPageView) {
|
||||
for (const [, { url, dataLength }] of frameResponses) {
|
||||
if (url.length === 0) {
|
||||
throw new Error('navigateToApps(); failed to identify the url of the request');
|
||||
}
|
||||
if (assetSizeMeasurements.has(url)) {
|
||||
assetSizeMeasurements.set(url, [dataLength].concat(assetSizeMeasurements.get(url) || []));
|
||||
} else {
|
||||
assetSizeMeasurements.set(url, [dataLength]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(assetSizeMeasurements.entries())
|
||||
.map(([url, measurements]) => {
|
||||
const baseUrl = createUrl('/', options.appConfig.url);
|
||||
const relativeUrl = url
|
||||
// remove the baseUrl (expect the trailing slash) to make url relative
|
||||
.replace(baseUrl.slice(0, -1), '')
|
||||
// strip the build number from asset urls
|
||||
.replace(/^\/\d+\//, '/');
|
||||
return [relativeUrl, measurements] as const;
|
||||
})
|
||||
.filter(([url, measurements]) => {
|
||||
if (measurements.length !== numberOfPagesVisited) {
|
||||
// ignore urls seen only on some pages
|
||||
return false;
|
||||
}
|
||||
|
||||
if (url.startsWith('data:')) {
|
||||
// ignore data urls since they are already counted by other assets
|
||||
return false;
|
||||
}
|
||||
|
||||
if (url.startsWith('/api/') || url.startsWith('/internal/')) {
|
||||
// ignore api requests since they don't have deterministic sizes
|
||||
return false;
|
||||
}
|
||||
|
||||
const allMetricsAreEqual = measurements.every((x, i) =>
|
||||
i === 0 ? true : x === measurements[i - 1]
|
||||
);
|
||||
if (!allMetricsAreEqual) {
|
||||
throw new Error(`measurements for url [${url}] are not equal [${measurements.join(',')}]`);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map(([url, measurements]) => {
|
||||
return { group: 'page load asset size', id: url, value: measurements[0] };
|
||||
});
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Url from 'url';
|
||||
|
||||
import { run, createFlagError } from '@kbn/dev-utils';
|
||||
import { resolve, basename } from 'path';
|
||||
import { capturePageLoadMetrics } from './capture_page_load_metrics';
|
||||
|
||||
const defaultScreenshotsDir = resolve(__dirname, 'screenshots');
|
||||
|
||||
export function runPageLoadMetricsCli() {
|
||||
run(
|
||||
async ({ flags, log }) => {
|
||||
const kibanaUrl = flags['kibana-url'];
|
||||
if (!kibanaUrl || typeof kibanaUrl !== 'string') {
|
||||
throw createFlagError('Expect --kibana-url to be a string');
|
||||
}
|
||||
|
||||
const parsedUrl = Url.parse(kibanaUrl);
|
||||
|
||||
const [username, password] = parsedUrl.auth
|
||||
? parsedUrl.auth.split(':')
|
||||
: [flags.username, flags.password];
|
||||
|
||||
if (typeof username !== 'string' || typeof password !== 'string') {
|
||||
throw createFlagError(
|
||||
'Mising username and/or password, either specify in --kibana-url or pass --username and --password'
|
||||
);
|
||||
}
|
||||
|
||||
const headless = !flags.head;
|
||||
|
||||
const screenshotsDir = flags.screenshotsDir || defaultScreenshotsDir;
|
||||
|
||||
if (typeof screenshotsDir !== 'string' || screenshotsDir === basename(screenshotsDir)) {
|
||||
throw createFlagError('Expect screenshotsDir to be valid path string');
|
||||
}
|
||||
|
||||
const metrics = await capturePageLoadMetrics(log, {
|
||||
headless,
|
||||
appConfig: {
|
||||
url: kibanaUrl,
|
||||
username,
|
||||
password,
|
||||
},
|
||||
screenshotsDir,
|
||||
});
|
||||
for (const metric of metrics) {
|
||||
log.info(`${metric.id}: ${metric.value}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
description: `Loads several pages with Puppeteer to capture the size of assets`,
|
||||
flags: {
|
||||
string: ['kibana-url', 'username', 'password', 'screenshotsDir'],
|
||||
boolean: ['head'],
|
||||
default: {
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
debug: true,
|
||||
screenshotsDir: defaultScreenshotsDir,
|
||||
},
|
||||
help: `
|
||||
--kibana-url Url for Kibana we should connect to, can include login info
|
||||
--head Run puppeteer with graphical user interface
|
||||
--username Set username, defaults to 'elastic'
|
||||
--password Set password, defaults to 'changeme'
|
||||
--screenshotsDir Set screenshots directory, defaults to '${defaultScreenshotsDir}'
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export interface ResponseReceivedEvent {
|
||||
frameId: string;
|
||||
loaderId: string;
|
||||
requestId: string;
|
||||
response: Record<string, any>;
|
||||
timestamp: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface DataReceivedEvent {
|
||||
encodedDataLength: number;
|
||||
dataLength: number;
|
||||
requestId: string;
|
||||
timestamp: number;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './cli';
|
||||
export { capturePageLoadMetrics } from './capture_page_load_metrics';
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import Url from 'url';
|
||||
import puppeteer from 'puppeteer';
|
||||
import { resolve } from 'path';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { ResponseReceivedEvent, DataReceivedEvent } from './event';
|
||||
|
||||
export interface NavigationOptions {
|
||||
headless: boolean;
|
||||
appConfig: { url: string; username: string; password: string };
|
||||
screenshotsDir: string;
|
||||
}
|
||||
|
||||
export type NavigationResults = Map<string, Map<string, FrameResponse>>;
|
||||
|
||||
interface FrameResponse {
|
||||
url: string;
|
||||
dataLength: number;
|
||||
}
|
||||
|
||||
function joinPath(pathA: string, pathB: string) {
|
||||
return `${pathA.endsWith('/') ? pathA.slice(0, -1) : pathA}/${
|
||||
pathB.startsWith('/') ? pathB.slice(1) : pathB
|
||||
}`;
|
||||
}
|
||||
|
||||
export function createUrl(path: string, url: string) {
|
||||
const baseUrl = Url.parse(url);
|
||||
return Url.format({
|
||||
protocol: baseUrl.protocol,
|
||||
hostname: baseUrl.hostname,
|
||||
port: baseUrl.port,
|
||||
pathname: joinPath(baseUrl.pathname || '', path),
|
||||
});
|
||||
}
|
||||
|
||||
async function loginToKibana(
|
||||
log: ToolingLog,
|
||||
browser: puppeteer.Browser,
|
||||
options: NavigationOptions
|
||||
) {
|
||||
log.debug(`log in to the app..`);
|
||||
const page = await browser.newPage();
|
||||
const loginUrl = createUrl('/login', options.appConfig.url);
|
||||
await page.goto(loginUrl, {
|
||||
waitUntil: 'networkidle0',
|
||||
});
|
||||
await page.type('[data-test-subj="loginUsername"]', options.appConfig.username);
|
||||
await page.type('[data-test-subj="loginPassword"]', options.appConfig.password);
|
||||
await page.click('[data-test-subj="loginSubmit"]');
|
||||
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||
await page.close();
|
||||
}
|
||||
|
||||
export async function navigateToApps(log: ToolingLog, options: NavigationOptions) {
|
||||
const browser = await puppeteer.launch({ headless: options.headless, args: ['--no-sandbox'] });
|
||||
const devToolsResponses: NavigationResults = new Map();
|
||||
const apps = [
|
||||
{ path: '/app/discover', locator: '[data-test-subj="discover-sidebar"]' },
|
||||
{ path: '/app/home', locator: '[data-test-subj="homeApp"]' },
|
||||
{ path: '/app/canvas', locator: '[data-test-subj="create-workpad-button"]' },
|
||||
{ path: '/app/maps', locator: '[title="Maps"]' },
|
||||
{ path: '/app/apm', locator: '[data-test-subj="apmMainContainer"]' },
|
||||
];
|
||||
|
||||
await loginToKibana(log, browser, options);
|
||||
|
||||
await Promise.all(
|
||||
apps.map(async (app) => {
|
||||
const page = await browser.newPage();
|
||||
page.setCacheEnabled(false);
|
||||
page.setDefaultNavigationTimeout(0);
|
||||
const frameResponses = new Map<string, FrameResponse>();
|
||||
devToolsResponses.set(app.path, frameResponses);
|
||||
|
||||
const client = await page.target().createCDPSession();
|
||||
await client.send('Network.enable');
|
||||
|
||||
function getRequestData(requestId: string) {
|
||||
if (!frameResponses.has(requestId)) {
|
||||
frameResponses.set(requestId, { url: '', dataLength: 0 });
|
||||
}
|
||||
|
||||
return frameResponses.get(requestId)!;
|
||||
}
|
||||
|
||||
client.on('Network.responseReceived', (event: ResponseReceivedEvent) => {
|
||||
getRequestData(event.requestId).url = event.response.url;
|
||||
});
|
||||
|
||||
client.on('Network.dataReceived', (event: DataReceivedEvent) => {
|
||||
getRequestData(event.requestId).dataLength += event.dataLength;
|
||||
});
|
||||
|
||||
const url = createUrl(app.path, options.appConfig.url);
|
||||
log.debug(`goto ${url}`);
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
});
|
||||
|
||||
let readyAttempt = 0;
|
||||
let selectorFound = false;
|
||||
while (!selectorFound) {
|
||||
readyAttempt += 1;
|
||||
try {
|
||||
await page.waitForSelector(app.locator, { timeout: 5000 });
|
||||
selectorFound = true;
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Page '${app.path}' was not loaded properly, unable to find '${
|
||||
app.locator
|
||||
}', url: ${page.url()}`
|
||||
);
|
||||
|
||||
if (readyAttempt < 6) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const failureDir = resolve(options.screenshotsDir, 'failure');
|
||||
const screenshotPath = resolve(
|
||||
failureDir,
|
||||
`${app.path.slice(1).split('/').join('_')}_navigation.png`
|
||||
);
|
||||
Fs.mkdirSync(failureDir, { recursive: true });
|
||||
|
||||
await page.bringToFront();
|
||||
await page.screenshot({
|
||||
path: screenshotPath,
|
||||
type: 'png',
|
||||
fullPage: true,
|
||||
});
|
||||
log.debug(`Saving screenshot to ${screenshotPath}`);
|
||||
|
||||
throw new Error(`Page load timeout: ${app.path} not loaded after 30 seconds`);
|
||||
}
|
||||
}
|
||||
|
||||
await page.close();
|
||||
})
|
||||
);
|
||||
|
||||
await browser.close();
|
||||
|
||||
return devToolsResponses;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/test').runPageLoadMetricsCli();
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
source test/scripts/jenkins_test_setup_xpack.sh
|
||||
|
||||
checks-reporter-with-killswitch "Capture Kibana page load metrics" \
|
||||
node scripts/functional_tests \
|
||||
--debug --bail \
|
||||
--kibana-install-dir "$installDir" \
|
||||
--config test/page_load_metrics/config.ts;
|
|
@ -17,6 +17,9 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1
|
|||
cd "$KIBANA_DIR"
|
||||
source "test/scripts/jenkins_xpack_saved_objects_field_metrics.sh"
|
||||
|
||||
cd "$KIBANA_DIR"
|
||||
source "test/scripts/jenkins_xpack_saved_objects_field_metrics.sh"
|
||||
|
||||
echo " -> running visual regression tests from x-pack directory"
|
||||
cd "$XPACK_DIR"
|
||||
yarn percy exec -t 10000 -- -- \
|
||||
|
|
1
x-pack/.gitignore
vendored
1
x-pack/.gitignore
vendored
|
@ -3,7 +3,6 @@
|
|||
/target
|
||||
/test/functional/failure_debug
|
||||
/test/functional/screenshots
|
||||
/test/page_load_metrics/screenshots
|
||||
/test/functional/apps/reporting/reports/session
|
||||
/test/reporting/configs/failure_debug/
|
||||
/plugins/reporting/.chromium/
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { PuppeteerTestRunner } from './runner';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const kibanaCommonTestsConfig = await readConfigFile(
|
||||
require.resolve('../../../test/common/config.js')
|
||||
);
|
||||
const xpackFunctionalTestsConfig = await readConfigFile(
|
||||
require.resolve('../functional/config.js')
|
||||
);
|
||||
|
||||
return {
|
||||
...kibanaCommonTestsConfig.getAll(),
|
||||
|
||||
testRunner: PuppeteerTestRunner,
|
||||
|
||||
esArchiver: {
|
||||
directory: resolve(__dirname, 'es_archives'),
|
||||
},
|
||||
|
||||
screenshots: {
|
||||
directory: resolve(__dirname, 'screenshots'),
|
||||
},
|
||||
|
||||
esTestCluster: {
|
||||
...xpackFunctionalTestsConfig.get('esTestCluster'),
|
||||
serverArgs: [...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs')],
|
||||
},
|
||||
|
||||
kbnTestServer: {
|
||||
...xpackFunctionalTestsConfig.get('kbnTestServer'),
|
||||
},
|
||||
};
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CiStatsReporter } from '@kbn/dev-utils';
|
||||
import { capturePageLoadMetrics } from '@kbn/test';
|
||||
// @ts-ignore not TS yet
|
||||
import getUrl from '../../../src/test_utils/get_url';
|
||||
|
||||
import { FtrProviderContext } from './../functional/ftr_provider_context';
|
||||
|
||||
export async function PuppeteerTestRunner({ getService }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const config = getService('config');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
await esArchiver.load('default');
|
||||
const metrics = await capturePageLoadMetrics(log, {
|
||||
headless: true,
|
||||
appConfig: {
|
||||
url: getUrl.baseUrl(config.get('servers.kibana')),
|
||||
username: config.get('servers.kibana.username'),
|
||||
password: config.get('servers.kibana.password'),
|
||||
},
|
||||
screenshotsDir: config.get('screenshots.directory'),
|
||||
});
|
||||
const reporter = CiStatsReporter.fromEnv(log);
|
||||
|
||||
log.debug('Report page load asset size');
|
||||
await reporter.metrics(metrics);
|
||||
}
|
83
yarn.lock
83
yarn.lock
|
@ -5619,13 +5619,6 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/puppeteer@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-3.0.0.tgz#24cdcc131e319477608d893f0017e08befd70423"
|
||||
integrity sha512-59+fkfHHXHzX5rgoXIMnZyzum7ZLx/Wc3fhsOduFThpTpKbzzdBHMZsrkKGLunimB4Ds/tI5lXTRLALK8Mmnhg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
|
||||
|
@ -8700,15 +8693,6 @@ bl@^3.0.0:
|
|||
dependencies:
|
||||
readable-stream "^3.0.1"
|
||||
|
||||
bl@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
|
||||
integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
blob@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
||||
|
@ -9215,14 +9199,6 @@ buffer@^5.1.0, buffer@^5.2.0:
|
|||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-modules@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
@ -17675,7 +17651,7 @@ inflight@^1.0.4:
|
|||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4:
|
||||
inherits@2, inherits@2.0.4, inherits@~2.0.3, inherits@~2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
@ -21893,11 +21869,6 @@ mixin-object@^2.0.1:
|
|||
for-in "^0.1.3"
|
||||
is-extendable "^0.1.1"
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
|
@ -25075,22 +25046,6 @@ puppeteer@^2.0.0:
|
|||
rimraf "^2.6.1"
|
||||
ws "^6.1.0"
|
||||
|
||||
puppeteer@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7"
|
||||
integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==
|
||||
dependencies:
|
||||
debug "^4.1.0"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^4.0.0"
|
||||
mime "^2.0.3"
|
||||
progress "^2.0.1"
|
||||
proxy-from-env "^1.0.0"
|
||||
rimraf "^3.0.2"
|
||||
tar-fs "^2.0.0"
|
||||
unbzip2-stream "^1.3.3"
|
||||
ws "^7.2.3"
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
|
@ -29745,16 +29700,6 @@ tar-fs@^1.16.3:
|
|||
pump "^1.0.0"
|
||||
tar-stream "^1.1.2"
|
||||
|
||||
tar-fs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
|
||||
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^1.1.2, tar-stream@^1.5.2:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55"
|
||||
|
@ -29765,17 +29710,6 @@ tar-stream@^1.1.2, tar-stream@^1.5.2:
|
|||
readable-stream "^2.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
tar-stream@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
|
||||
integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
|
||||
dependencies:
|
||||
bl "^4.0.1"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar-stream@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
|
||||
|
@ -30061,7 +29995,7 @@ through2@~2.0.3:
|
|||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.6, through@~2.3.8:
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
@ -31257,14 +31191,6 @@ unbzip2-stream@^1.0.9:
|
|||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
|
||||
unbzip2-stream@^1.3.3:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf"
|
||||
integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
unc-path-regex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
|
@ -33215,11 +33141,6 @@ ws@^7.0.0:
|
|||
dependencies:
|
||||
async-limiter "^1.0.0"
|
||||
|
||||
ws@^7.2.3:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
|
||||
|
||||
ws@~3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue