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;
}
interface PageVisitConfig {
mdPath: string;
theme: string;
enableCssVar: boolean;
}
const themes = ['default', 'dark', 'compact'];
async function retrieveDemoUrl(mdPath: string) {
function retrieveDemoUrl(mdPath: string) {
// ~demos/button-demo-basic
return mdPath
.replace(/^components\//, '')
@ -31,6 +37,13 @@ async function retrieveDemoUrl(mdPath: string) {
.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> {
const mdDir = path.dirname(mdPath);
const configDir = path.join(mdDir, '..', '__tests__');
@ -64,12 +77,6 @@ async function createSiteServer() {
return server;
}
interface PageVisitConfig {
mdPath: string;
theme: string;
enableCssVar: boolean;
}
class BrowserAuto {
private browser: Browser | null = null;
@ -86,8 +93,7 @@ class BrowserAuto {
deviceScaleFactor: 2,
});
// 要截屏 6 张,需要测试一下要不要拆分为独立任务,还是按照 demoPath 一次性截屏 6 张
this.context.setDefaultTimeout(5000 * 6);
this.context.setDefaultTimeout(5000);
await fse.ensureDir(this.outputDir);
await fse.emptyDir(this.outputDir);
@ -97,29 +103,32 @@ class BrowserAuto {
await fse.writeFile(errorFilePath, '');
}
async appendErrorLog(errorData: any) {
async appendErrorLog(errorData: {
filename: string;
error: string;
}) {
const errorFilePath = path.join(this.outputDir, 'error.jsonl');
const errorLine = JSON.stringify({
...errorData,
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;
const { mdPath, theme, enableCssVar } = config;
const page = await this.context.newPage();
await this.visitDemoPage(page, mdPath, theme, enableCssVar);
await this.visitDemoPage(page, config, imgName);
return page?.close();
}
private async visitDemoPage(page: Page, mdPath: string, theme: string, enableCssVar: boolean) {
const demoUrl = await retrieveDemoUrl(mdPath);
private async visitDemoPage(page: Page, config: PageVisitConfig, imgName: string) {
const { mdPath, theme, enableCssVar } = config;
const demoUrl = retrieveDemoUrl(mdPath);
const options = await retrieveConfig(mdPath);
if (!options) {
loglevel.info('Skip for: %s', mdPath);
@ -144,10 +153,10 @@ class BrowserAuto {
const pageUrl = `http://localhost:${port}/~demos/${demoUrl.toLowerCase()}?${query.toString()}`;
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');
// 禁用掉所有的动画
// disabled animation
await page.addStyleTag({
content: '*{animation: none!important;}',
});
@ -176,10 +185,6 @@ class BrowserAuto {
// 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({
path: path.join(this.outputDir, imgName),
animations: 'disabled',
@ -190,7 +195,6 @@ class BrowserAuto {
});
}
// 关闭浏览器
async 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
(async () => {
const args = parseArgs();
const { serverOnly, component, logLevel = 'error', shard } = args;
const { serverOnly, component, logLevel = 'info', shard } = args;
loglevel.setLevel(logLevel);
loglevel.info(`Args: ${JSON.stringify(args)}`);
@ -251,13 +255,15 @@ function parseArgs(): {
loglevel.info(`Shard ${current}/${total}: ${mdPaths.length}/${originLens} mds`);
}
const task = async (visitConfig: PageVisitConfig) => {
const visitTask = async (visitConfig: PageVisitConfig) => {
const imgName = retrieveCaptureImgName(visitConfig);
try {
await handler.captureScreenshots(visitConfig);
// ~demos/button-demo-basic -> button-basic
await handler.captureScreenshots(visitConfig, imgName);
} catch (err) {
const errorData = {
filename: visitConfig.mdPath,
error: (err as Error).message,
filename: imgName,
error: `Capture failed: ${(err as Error).message}`,
};
await handler.appendErrorLog(errorData);
loglevel.error(`Error: ${errorData.error}`);
@ -278,11 +284,13 @@ function parseArgs(): {
await pAll(
visitConfigs.map((visitConfig, i) => async () => {
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();
await task(visitConfig);
loglevel.info(`处理 ${i + 1}/${visitConfigs.length} 完成,耗时 ${performance.now() - now}ms`);
await visitTask(visitConfig);
loglevel.info(
`Task ${i + 1}/${visitConfigs.length} finished, time: ${performance.now() - now}ms`,
);
}),
{ concurrency: 10 },
);

View File

@ -95,6 +95,23 @@ async function downloadFile(url: string, destPath: string) {
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) {
const baseImageRefUrl = `${ossDomain}/${branchName}/visual-regression-ref.txt`;
// get content from baseImageRefText
@ -145,6 +162,7 @@ interface IBadCase {
* 0 - 1
*/
weight: number;
reason?: string;
}
const git = simpleGit();
@ -175,7 +193,7 @@ function generateLineReport(
currentRef: string,
extraCaption?: boolean,
) {
const { filename, type, targetFilename } = badCase;
const { filename, type, targetFilename, reason } = badCase;
let lineHTMLReport = '';
if (type === 'changed') {
@ -216,7 +234,7 @@ function generateLineReport(
extraCaption,
),
`⛔️⛔️⛔️ Missing ⛔️⛔️⛔️`,
`🚨🚨🚨 Removed 🚨🚨🚨`,
reason || `🚨🚨🚨 Removed 🚨🚨🚨`,
].join(' | ');
lineHTMLReport += ' |\n';
} else if (type === 'added') {
@ -347,6 +365,8 @@ async function boot() {
const currentImgSourceDir = path.resolve(ROOT_DIR, './imageSnapshots');
const errorHashMap = await readErrorJsonl(path.resolve(currentImgSourceDir, 'error.jsonl'));
// save diff images(x3) to reportDir
const diffImgReportDir = path.resolve(REPORT_DIR, './images/diff');
const baseImgReportDir = path.resolve(REPORT_DIR, './images/base');
@ -391,6 +411,7 @@ async function boot() {
type: 'removed',
filename: compareImgName,
weight: 1,
reason: errorHashMap[compareImgName],
} as IBadCase;
}