This commit is contained in:
Norbert de Langen 2023-06-21 22:42:05 +02:00
parent 16aa7229be
commit ee122a471e
No known key found for this signature in database
GPG Key ID: FD0E78AF9A837762
8 changed files with 192 additions and 78 deletions

View File

@ -1,54 +1,99 @@
/* eslint-disable no-await-in-loop */
import type { Page, FrameLocator } from 'playwright';
import type { Page, FrameLocator, Browser } from 'playwright';
import { chromium } from 'playwright';
import type { Config } from './types';
import { now } from './utils';
const now = () => new Date().getTime();
const TIMEOUT = 20000;
export const browse = async (url: string, config: Config) => {
if (!config.managerLoaded && !config.previewLoadedText) return undefined;
interface Result {
managerHeaderVisible?: number;
managerIndexVisible?: number;
storyVisible?: number;
docsVisible?: number;
}
export const browse = async (url: string) => {
const start = now();
let managerLoaded;
let previewLoaded;
const result: Result = {};
const browser = await chromium.launch(/* { headless: false } */);
const page = await browser.newPage();
page.on('console', (msg: any) => {
const type = msg.type();
console.log(type, msg.text());
});
await page.goto(url);
if (config.managerLoaded) {
await page.waitForSelector(config.managerLoaded, { state: 'attached' });
managerLoaded = now() - start;
console.log('manager', new Date(), config.managerLoaded, managerLoaded);
}
if (config.previewLoadedText) {
let previewFrame: FrameLocator | Page;
previewFrame = page;
if (config.previewFrameLocator) {
previewFrame = await page.frameLocator(config.previewFrameLocator);
}
let actualText;
while (now() - start < TIMEOUT && (!actualText || !actualText.length)) {
const preview = await previewFrame.getByText(config.previewLoadedText);
actualText = await preview.innerText();
}
console.log({ actualText });
if (!actualText?.includes(config.previewLoadedText)) {
throw new Error('previewLoadedText not found');
}
previewLoaded = now() - start;
console.log('preview', new Date(), config.previewLoadedText, previewLoaded);
}
Object.assign(result, await benchStory(browser, url, start));
Object.assign(result, await benchDocs(browser, url, start));
await browser.close();
return { managerLoaded, previewLoaded };
return result;
};
async function benchDocs(browser: Browser, url: string, start: number) {
const page = await browser.newPage();
const result: Result = {};
await page.goto(`${url}?path=/docs/example-button--docs`);
const tasks = [
async () => {
let previewFrame: FrameLocator | Page = page;
previewFrame = await page.frameLocator('#storybook-preview-iframe');
let actualText;
while (now() - start < TIMEOUT && (!actualText || !actualText.length)) {
const preview = await previewFrame.getByText('Primary UI component for user interaction');
actualText = await preview.innerText();
}
if (!actualText?.includes('Primary UI component for user interaction')) {
throw new Error('docs not visible in time');
}
result.docsVisible = now() - start;
},
];
await Promise.all(tasks.map((t) => t()));
await page.close();
return result;
}
async function benchStory(browser: Browser, url: string, start: number) {
const page = await browser.newPage();
const result: Result = {};
await page.goto(`${url}?path=/story/example-button--primary`);
const tasks = [
//
async () => {
await page.waitForSelector('.sidebar-header', { state: 'attached' });
result.managerHeaderVisible = now() - start;
},
async () => {
await page.waitForSelector('#example-button--primary', { state: 'attached' });
result.managerIndexVisible = now() - start;
},
async () => {
let previewFrame: FrameLocator | Page = page;
previewFrame = await page.frameLocator('#storybook-preview-iframe');
let actualText;
while (now() - start < TIMEOUT && (!actualText || !actualText.length)) {
const preview = await previewFrame.getByText('Button');
actualText = await preview.innerText();
}
if (!actualText?.includes('Button')) {
throw new Error('preview not visible in time');
}
result.storyVisible = now() - start;
},
];
await Promise.all(tasks.map((t) => t()));
await page.close();
return result;
}

View File

@ -1,7 +0,0 @@
import type { Config } from './types';
export const storybookConfig: Config = {
managerLoaded: '.sidebar-header',
previewFrameLocator: '#storybook-preview-iframe',
previewLoadedText: 'Button',
};

View File

@ -1,4 +1,3 @@
export * from './types';
export * from './config';
export * from './utils';
export { browse } from './browse';

View File

@ -86,9 +86,11 @@
"@types/escodegen": "^0.0.6",
"@types/express": "^4.17.11",
"@types/fs-extra": "^11.0.1",
"@types/http-server": "^0.12.1",
"@types/lodash": "^4",
"@types/node": "^16.0.0",
"@types/node-fetch": "^2.5.7",
"@types/pretty-hrtime": "^1.0.0",
"@types/prompts": "2.0.11",
"@types/react": "^16.14.34",
"@types/react-dom": "^16.9.17",
@ -154,7 +156,9 @@
"playwright": "^1.35.0",
"playwright-core": "^1.35.0",
"prettier": "^2.8.0",
"pretty-bytes": "^6.1.0",
"pretty-hrtime": "^1.0.0",
"pretty-ms": "^8.0.0",
"process": "^0.11.10",
"prompts": "^2.4.0",
"react": "16.14.0",

View File

@ -3,6 +3,9 @@ import type { Task } from '../task';
import { PORT, dev } from './dev';
import { serve } from './serve';
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const dynamicImport = new Function('specifier', 'return import(specifier)');
export const bench: Task = {
description: 'Run benchmarks against a sandbox in dev mode',
dependsOn: ['build'],
@ -11,35 +14,60 @@ export const bench: Task = {
},
async run(details, options) {
const { browse, saveBench, storybookConfig } = await import('../bench');
const url = `http://localhost:${PORT}?path=/story/example-button--primary`;
const controllers: AbortController[] = [];
try {
const { browse, saveBench, loadBench } = await import('../bench');
const { default: prettyBytes } = await dynamicImport('pretty-bytes');
const { default: prettyTime } = await dynamicImport('pretty-ms');
const devController = await dev.run(details, options);
if (!devController) {
throw new Error('dev: controller is null');
}
const url = `http://localhost:${PORT}`;
const devBrowseResult = await browse(url, storybookConfig);
devController.abort();
const serveController = await serve.run(details, options);
if (!serveController) {
throw new Error('serve: controller is null');
}
const buildBrowseResult = await browse(url, storybookConfig);
serveController.abort();
await saveBench(
{
devManagerLoaded: devBrowseResult.managerLoaded,
devPreviewLoaded: devBrowseResult.previewLoaded,
buildManagerLoaded: buildBrowseResult.managerLoaded,
buildPreviewLoaded: buildBrowseResult.previewLoaded,
},
{
rootDir: details.sandboxDir,
const devController = await dev.run(details, { ...options, debug: false });
if (!devController) {
throw new Error('dev: controller is null');
}
);
controllers.push(devController);
const devBrowseResult = await browse(url);
devController.abort();
const serveController = await serve.run(details, { ...options, debug: false });
if (!serveController) {
throw new Error('serve: controller is null');
}
controllers.push(serveController);
const buildBrowseResult = await browse(url);
serveController.abort();
await saveBench(
{
devManagerHeaderVisible: devBrowseResult.managerHeaderVisible,
devManagerIndexVisible: devBrowseResult.managerIndexVisible,
devStoryVisible: devBrowseResult.storyVisible,
devDocsVisible: devBrowseResult.docsVisible,
buildManagerHeaderVisible: buildBrowseResult.managerHeaderVisible,
buildManagerIndexVisible: buildBrowseResult.managerIndexVisible,
buildPreviewVisible: buildBrowseResult.storyVisible,
buildDocsVisible: buildBrowseResult.docsVisible,
},
{
rootDir: details.sandboxDir,
}
);
const data = await loadBench({ rootDir: details.sandboxDir });
Object.entries(data).forEach(([key, value]) => {
if (key.includes('Size')) {
console.log(`${key}: ${prettyBytes(value)}`);
} else {
console.log(`${key}: ${prettyTime(value)}`);
}
});
} catch (e) {
controllers.forEach((c) => c.abort());
throw e;
}
},
};

View File

@ -6,6 +6,7 @@ import { exec } from '../utils/exec';
export const PORT = process.env.STORYBOOK_SERVE_PORT
? parseInt(process.env.STORYBOOK_SERVE_PORT, 10)
: 8001;
export const serve: Task = {
description: 'Serve the build storybook for a sandbox',
service: true,
@ -16,12 +17,14 @@ export const serve: Task = {
async run({ builtSandboxDir, codeDir }, { debug, dryRun }) {
const controller = new AbortController();
exec(
`yarn http-server ${builtSandboxDir} --port ${PORT}`,
`yarn http-server ${builtSandboxDir} --port ${PORT} -s`,
{ cwd: codeDir },
{ dryRun, debug, signal: controller.signal as AbortSignal }
).catch((err) => {
// If aborted, we want to make sure the rejection is handled.
if (!err.killed) throw err;
if (!err.killed) {
throw err;
}
});
const { default: waitOn } = await import('wait-on');
await waitOn({ resources: [`http://localhost:${PORT}`], interval: 16 });

View File

@ -1 +0,0 @@
declare module 'pretty-hrtime';

View File

@ -2951,9 +2951,11 @@ __metadata:
"@types/escodegen": ^0.0.6
"@types/express": ^4.17.11
"@types/fs-extra": ^11.0.1
"@types/http-server": ^0.12.1
"@types/lodash": ^4
"@types/node": ^16.0.0
"@types/node-fetch": ^2.5.7
"@types/pretty-hrtime": ^1.0.0
"@types/prompts": 2.0.11
"@types/react": ^16.14.34
"@types/react-dom": ^16.9.17
@ -3020,7 +3022,9 @@ __metadata:
playwright: ^1.35.0
playwright-core: ^1.35.0
prettier: ^2.8.0
pretty-bytes: ^6.1.0
pretty-hrtime: ^1.0.0
pretty-ms: ^8.0.0
process: ^0.11.10
prompts: ^2.4.0
react: 16.14.0
@ -3530,6 +3534,15 @@ __metadata:
languageName: node
linkType: hard
"@types/http-server@npm:^0.12.1":
version: 0.12.1
resolution: "@types/http-server@npm:0.12.1"
dependencies:
"@types/connect": "*"
checksum: 9b2397640b961589b23c98c5b1ed9f507fe07413f5723b3b4f447740d70ed702a356ec32dfa7e77fc477505f65758e423ee8745b879efb1fdddc23a840c29534
languageName: node
linkType: hard
"@types/is-empty@npm:^1.0.0":
version: 1.2.1
resolution: "@types/is-empty@npm:1.2.1"
@ -3711,6 +3724,13 @@ __metadata:
languageName: node
linkType: hard
"@types/pretty-hrtime@npm:^1.0.0":
version: 1.0.1
resolution: "@types/pretty-hrtime@npm:1.0.1"
checksum: e990110a3626e987319092c5149d5ea244785b83fbbd8e62605714ec1fa4317a3524ae0b6381cdc2ca92619d9a451b3fe9ff4085c42826f5398e3380d3031bff
languageName: node
linkType: hard
"@types/prompts@npm:2.0.11":
version: 2.0.11
resolution: "@types/prompts@npm:2.0.11"
@ -12787,6 +12807,13 @@ __metadata:
languageName: node
linkType: hard
"parse-ms@npm:^3.0.0":
version: 3.0.0
resolution: "parse-ms@npm:3.0.0"
checksum: 056b4a32a9d3749f3f4cfffefb45c45540491deaa8e1d8ad43c2ddde7ba04edd076bd1b298f521238bb5fb084a9b2c4a2ebb78aefa651afbc4c2b0af4232fc54
languageName: node
linkType: hard
"parse-passwd@npm:^1.0.0":
version: 1.0.0
resolution: "parse-passwd@npm:1.0.0"
@ -13159,6 +13186,13 @@ __metadata:
languageName: node
linkType: hard
"pretty-bytes@npm:^6.1.0":
version: 6.1.0
resolution: "pretty-bytes@npm:6.1.0"
checksum: 717ed82f8d4bbf038b623062d360bf71fe9107c3f2c43de7d0fd3e9610780306e38059d8d8356bc2c151d2929fada934c051f7d51be2587ebe8f426268b43112
languageName: node
linkType: hard
"pretty-format@npm:^27.0.2":
version: 27.5.1
resolution: "pretty-format@npm:27.5.1"
@ -13188,6 +13222,15 @@ __metadata:
languageName: node
linkType: hard
"pretty-ms@npm:^8.0.0":
version: 8.0.0
resolution: "pretty-ms@npm:8.0.0"
dependencies:
parse-ms: ^3.0.0
checksum: e960d633ecca45445cf5c6dffc0f5e4bef6744c92449ab0e8c6c704800675ab71e181c5e02ece5265e02137a33e313d3f3e355fbf8ea30b4b5b23de423329f8d
languageName: node
linkType: hard
"prettyjson@npm:^1.2.1":
version: 1.2.5
resolution: "prettyjson@npm:1.2.5"