test: handle error

This commit is contained in:
vagusX 2024-08-08 16:22:19 +08:00
parent aa56e9ab7c
commit 3e8e61c1c1
2 changed files with 63 additions and 34 deletions

View File

@ -21,9 +21,15 @@ interface VisualDiffConfig {
openTriggerClassName?: string; openTriggerClassName?: string;
} }
interface PageVisitConfig {
mdPath: string;
theme: string;
enableCssVar: boolean;
}
const themes = ['default', 'dark', 'compact']; const themes = ['default', 'dark', 'compact'];
async function retrieveDemoUrl(mdPath: string) { function retrieveDemoUrl(mdPath: string) {
// ~demos/button-demo-basic // ~demos/button-demo-basic
return mdPath return mdPath
.replace(/^components\//, '') .replace(/^components\//, '')
@ -31,6 +37,13 @@ async function retrieveDemoUrl(mdPath: string) {
.replace(/\//g, '-'); .replace(/\//g, '-');
} }
function retrieveCaptureImgName(config: PageVisitConfig) {
// components/affix/demo/basic.md -> affix-basic
const { mdPath, theme, enableCssVar } = config;
const demoUrl = retrieveDemoUrl(mdPath);
return `${demoUrl.replace('-demo', '')}.${theme}${enableCssVar ? '.css-var' : ''}.png`;
}
async function retrieveConfig(mdPath: string): Promise<VisualDiffConfig | void> { async function retrieveConfig(mdPath: string): Promise<VisualDiffConfig | void> {
const mdDir = path.dirname(mdPath); const mdDir = path.dirname(mdPath);
const configDir = path.join(mdDir, '..', '__tests__'); const configDir = path.join(mdDir, '..', '__tests__');
@ -64,12 +77,6 @@ async function createSiteServer() {
return server; return server;
} }
interface PageVisitConfig {
mdPath: string;
theme: string;
enableCssVar: boolean;
}
class BrowserAuto { class BrowserAuto {
private browser: Browser | null = null; private browser: Browser | null = null;
@ -86,8 +93,7 @@ class BrowserAuto {
deviceScaleFactor: 2, deviceScaleFactor: 2,
}); });
// 要截屏 6 张,需要测试一下要不要拆分为独立任务,还是按照 demoPath 一次性截屏 6 张 this.context.setDefaultTimeout(5000);
this.context.setDefaultTimeout(5000 * 6);
await fse.ensureDir(this.outputDir); await fse.ensureDir(this.outputDir);
await fse.emptyDir(this.outputDir); await fse.emptyDir(this.outputDir);
@ -97,29 +103,32 @@ class BrowserAuto {
await fse.writeFile(errorFilePath, ''); await fse.writeFile(errorFilePath, '');
} }
async appendErrorLog(errorData: any) { async appendErrorLog(errorData: {
filename: string;
error: string;
}) {
const errorFilePath = path.join(this.outputDir, 'error.jsonl'); const errorFilePath = path.join(this.outputDir, 'error.jsonl');
const errorLine = JSON.stringify({ const errorLine = JSON.stringify({
...errorData, ...errorData,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
await fs.promises.writeFile(errorFilePath, `${errorLine}\n`); console.log('Error filename:', errorData.filename);
await fs.promises.appendFile(errorFilePath, `${errorLine}\n`);
} }
// 执行截屏 // 执行截屏
async captureScreenshots(config: PageVisitConfig) { async captureScreenshots(config: PageVisitConfig, imgName: string) {
if (!this.context) return; if (!this.context) return;
const { mdPath, theme, enableCssVar } = config;
const page = await this.context.newPage(); const page = await this.context.newPage();
await this.visitDemoPage(page, mdPath, theme, enableCssVar); await this.visitDemoPage(page, config, imgName);
return page?.close(); return page?.close();
} }
private async visitDemoPage(page: Page, mdPath: string, theme: string, enableCssVar: boolean) { private async visitDemoPage(page: Page, config: PageVisitConfig, imgName: string) {
const demoUrl = await retrieveDemoUrl(mdPath); const { mdPath, theme, enableCssVar } = config;
const demoUrl = retrieveDemoUrl(mdPath);
const options = await retrieveConfig(mdPath); const options = await retrieveConfig(mdPath);
if (!options) { if (!options) {
loglevel.info('Skip for: %s', mdPath); loglevel.info('Skip for: %s', mdPath);
@ -144,10 +153,10 @@ class BrowserAuto {
const pageUrl = `http://localhost:${port}/~demos/${demoUrl.toLowerCase()}?${query.toString()}`; const pageUrl = `http://localhost:${port}/~demos/${demoUrl.toLowerCase()}?${query.toString()}`;
await page.goto(pageUrl); await page.goto(pageUrl);
// TODO: 需要禁用掉页面中的各种采集和埋点请求,避免干扰 // TODO: Need to disable various data collection and tracking requests in the page to avoid interference
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
// 禁用掉所有的动画 // disabled animation
await page.addStyleTag({ await page.addStyleTag({
content: '*{animation: none!important;}', content: '*{animation: none!important;}',
}); });
@ -176,10 +185,6 @@ class BrowserAuto {
// await page.click(`.${options.openTriggerClassName}`); // await page.click(`.${options.openTriggerClassName}`);
// } // }
// ~demos/button-demo-basic -> button-basic
const imgName = `${demoUrl.replace('-demo', '')}.${theme}${enableCssVar ? '.css-var' : ''}.png`;
// 保存截图到 ./result 目录
await page.screenshot({ await page.screenshot({
path: path.join(this.outputDir, imgName), path: path.join(this.outputDir, imgName),
animations: 'disabled', animations: 'disabled',
@ -190,7 +195,6 @@ class BrowserAuto {
}); });
} }
// 关闭浏览器
async close() { async close() {
await this.browser?.close(); await this.browser?.close();
} }
@ -225,7 +229,7 @@ function parseArgs(): {
// npm run visual-diff:capture -- --component=space --loglevel=info --server-only --shard=1/2 --max-workers=2 // npm run visual-diff:capture -- --component=space --loglevel=info --server-only --shard=1/2 --max-workers=2
(async () => { (async () => {
const args = parseArgs(); const args = parseArgs();
const { serverOnly, component, logLevel = 'error', shard } = args; const { serverOnly, component, logLevel = 'info', shard } = args;
loglevel.setLevel(logLevel); loglevel.setLevel(logLevel);
loglevel.info(`Args: ${JSON.stringify(args)}`); loglevel.info(`Args: ${JSON.stringify(args)}`);
@ -251,13 +255,15 @@ function parseArgs(): {
loglevel.info(`Shard ${current}/${total}: ${mdPaths.length}/${originLens} mds`); loglevel.info(`Shard ${current}/${total}: ${mdPaths.length}/${originLens} mds`);
} }
const task = async (visitConfig: PageVisitConfig) => { const visitTask = async (visitConfig: PageVisitConfig) => {
const imgName = retrieveCaptureImgName(visitConfig);
try { try {
await handler.captureScreenshots(visitConfig); // ~demos/button-demo-basic -> button-basic
await handler.captureScreenshots(visitConfig, imgName);
} catch (err) { } catch (err) {
const errorData = { const errorData = {
filename: visitConfig.mdPath, filename: imgName,
error: (err as Error).message, error: `Capture failed: ${(err as Error).message}`,
}; };
await handler.appendErrorLog(errorData); await handler.appendErrorLog(errorData);
loglevel.error(`Error: ${errorData.error}`); loglevel.error(`Error: ${errorData.error}`);
@ -278,11 +284,13 @@ function parseArgs(): {
await pAll( await pAll(
visitConfigs.map((visitConfig, i) => async () => { visitConfigs.map((visitConfig, i) => async () => {
loglevel.info( loglevel.info(
`处理 ${i + 1}/${visitConfigs.length}: ${visitConfig.mdPath} / ${visitConfig.theme} / ${visitConfig.enableCssVar}`, `Task ${i + 1}/${visitConfigs.length}: ${visitConfig.mdPath} / ${visitConfig.theme} / ${visitConfig.enableCssVar}`,
); );
const now = performance.now(); const now = performance.now();
await task(visitConfig); await visitTask(visitConfig);
loglevel.info(`处理 ${i + 1}/${visitConfigs.length} 完成,耗时 ${performance.now() - now}ms`); loglevel.info(
`Task ${i + 1}/${visitConfigs.length} finished, time: ${performance.now() - now}ms`,
);
}), }),
{ concurrency: 10 }, { concurrency: 10 },
); );

View File

@ -95,6 +95,23 @@ async function downloadFile(url: string, destPath: string) {
await finished(body.pipe(fs.createWriteStream(destPath))); await finished(body.pipe(fs.createWriteStream(destPath)));
} }
async function readErrorJsonl(filePath: string) {
if (!fs.existsSync(filePath)) {
return {};
}
const content = await fs.promises.readFile(filePath, 'utf-8');
const result: Record<string, string> = {};
for (const lineStr of content.split('\n').filter(Boolean)) {
try {
const line = JSON.parse(lineStr);
result[line.filename] = line.error;
} catch (err) {
// ignore
}
}
return result;
}
async function getBranchLatestRef(branchName: string) { async function getBranchLatestRef(branchName: string) {
const baseImageRefUrl = `${ossDomain}/${branchName}/visual-regression-ref.txt`; const baseImageRefUrl = `${ossDomain}/${branchName}/visual-regression-ref.txt`;
// get content from baseImageRefText // get content from baseImageRefText
@ -145,6 +162,7 @@ interface IBadCase {
* 0 - 1 * 0 - 1
*/ */
weight: number; weight: number;
reason?: string;
} }
const git = simpleGit(); const git = simpleGit();
@ -175,7 +193,7 @@ function generateLineReport(
currentRef: string, currentRef: string,
extraCaption?: boolean, extraCaption?: boolean,
) { ) {
const { filename, type, targetFilename } = badCase; const { filename, type, targetFilename, reason } = badCase;
let lineHTMLReport = ''; let lineHTMLReport = '';
if (type === 'changed') { if (type === 'changed') {
@ -216,7 +234,7 @@ function generateLineReport(
extraCaption, extraCaption,
), ),
`⛔️⛔️⛔️ Missing ⛔️⛔️⛔️`, `⛔️⛔️⛔️ Missing ⛔️⛔️⛔️`,
`🚨🚨🚨 Removed 🚨🚨🚨`, reason || `🚨🚨🚨 Removed 🚨🚨🚨`,
].join(' | '); ].join(' | ');
lineHTMLReport += ' |\n'; lineHTMLReport += ' |\n';
} else if (type === 'added') { } else if (type === 'added') {
@ -347,6 +365,8 @@ async function boot() {
const currentImgSourceDir = path.resolve(ROOT_DIR, './imageSnapshots'); const currentImgSourceDir = path.resolve(ROOT_DIR, './imageSnapshots');
const errorHashMap = await readErrorJsonl(path.resolve(currentImgSourceDir, 'error.jsonl'));
// save diff images(x3) to reportDir // save diff images(x3) to reportDir
const diffImgReportDir = path.resolve(REPORT_DIR, './images/diff'); const diffImgReportDir = path.resolve(REPORT_DIR, './images/diff');
const baseImgReportDir = path.resolve(REPORT_DIR, './images/base'); const baseImgReportDir = path.resolve(REPORT_DIR, './images/base');
@ -391,6 +411,7 @@ async function boot() {
type: 'removed', type: 'removed',
filename: compareImgName, filename: compareImgName,
weight: 1, weight: 1,
reason: errorHashMap[compareImgName],
} as IBadCase; } as IBadCase;
} }