mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-29 01:15:59 +08:00
This makes several improvements to test output: - Screenshots are no longer printed to console or saved to an html file. This is unnecessary now that they are uploaded to rdb. - When a test is retried, the timeout hook was not correctly hooked, which produced unexpected errors from multiple done calls. This has been fixed. - Screenshot error message now includes causes, which displays the pending async stacks on timeout. - Driveby fix to improper assertion in console-helpers. Bug: None Change-Id: Ia76f88ad9fbf1236a47a1f27eff41ee93d1a28a5 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5349145 Commit-Queue: Eric Leese <leese@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
// Copyright 2023 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import {createHash} from 'crypto';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
type ArtifactGroup = {
|
|
[key: string]: {
|
|
filePath: string,
|
|
},
|
|
};
|
|
|
|
export class ScreenshotError extends Error {
|
|
// The max length of the summary is 4000, but we need to leave some room for
|
|
// the rest of the HTML formatting (e.g. <pre> and </pre>).
|
|
static readonly SUMMARY_LENGTH_CUTOFF = 3900;
|
|
readonly screenshots: ArtifactGroup;
|
|
|
|
private constructor(screenshots: ArtifactGroup, message?: string, cause?: Error) {
|
|
message = [message, cause?.message, (cause?.cause as Error)?.message].filter(x => x).join('\n\n');
|
|
super(message);
|
|
this.cause = cause;
|
|
this.stack = cause?.stack ?? '';
|
|
this.screenshots = screenshots;
|
|
}
|
|
|
|
/**
|
|
* Creates a ScreenshotError when a reference golden does not exists.
|
|
*/
|
|
static fromMessage(message: string, generatedImgPath: string) {
|
|
const screenshots = {
|
|
'generated': {filePath: this.stashArtifact(generatedImgPath, 'generated')},
|
|
};
|
|
return new ScreenshotError(screenshots, message, undefined);
|
|
}
|
|
|
|
/**
|
|
* Creates a ScreenshotError when a generated screenshot is different from
|
|
* the golden.
|
|
*/
|
|
static fromError(error: Error, goldenImgPath: string, generatedImgPath: string, diffImgPath: string) {
|
|
const screenshots = {
|
|
'expected_image': {filePath: this.stashArtifact(goldenImgPath, 'expected')},
|
|
'actual_image': {filePath: this.stashArtifact(generatedImgPath, 'actual')},
|
|
'image_diff': {filePath: this.stashArtifact(diffImgPath, 'diff')},
|
|
};
|
|
return new ScreenshotError(screenshots, undefined, error);
|
|
}
|
|
|
|
/**
|
|
* Creates a ScreenshotError an unexpected error occurs. Screenshots are
|
|
* were taken for both the target and the frontend.
|
|
*/
|
|
static fromBase64Images(error: unknown, targetScreenshot?: string, frontendScreenshot?: string) {
|
|
if (!targetScreenshot || !frontendScreenshot) {
|
|
console.error('No artifacts to save.');
|
|
return error;
|
|
}
|
|
const screenshots = {
|
|
'target': {filePath: this.saveArtifact(targetScreenshot)},
|
|
'frontend': {filePath: this.saveArtifact(frontendScreenshot)},
|
|
};
|
|
return new ScreenshotError(screenshots, undefined, error as Error);
|
|
}
|
|
|
|
/**
|
|
* Costructs artifact group and summary for Milo
|
|
* at resultdb publication time.
|
|
*/
|
|
toMiloArtifacts(): [ArtifactGroup, string] {
|
|
let summary: string;
|
|
if ('expected_image' in this.screenshots) {
|
|
// no summary; autogenerated by Milo based on artifact name convention
|
|
summary = '';
|
|
} else if ('generated' in this.screenshots) {
|
|
// TODO(liviurau): embed image once Milo supports it
|
|
summary = '<pre>' + this.message.slice(0, ScreenshotError.SUMMARY_LENGTH_CUTOFF) +
|
|
'</pre><p>Screenshot generated (see below)</p>';
|
|
} else {
|
|
// TODO(liviurau): embed images once Milo supports it
|
|
const message = (this.message + '\n\n' + this.stack).slice(0, ScreenshotError.SUMMARY_LENGTH_CUTOFF);
|
|
summary = '<pre>' + message + '</pre><p>Unexpected error. See target and frontend screenshots ' +
|
|
'below.</p>';
|
|
}
|
|
return [this.screenshots, summary];
|
|
}
|
|
|
|
/**
|
|
* Copy artifacts in tmp folder so they remain available
|
|
* at resultdb publication time.
|
|
*/
|
|
private static stashArtifact(originalFile: string, tag: string): string {
|
|
const stashedFileName = tag + '-' + path.basename(originalFile);
|
|
const artifactPath = path.join(os.tmpdir(), stashedFileName);
|
|
fs.copyFileSync(originalFile, artifactPath);
|
|
return artifactPath;
|
|
}
|
|
|
|
/**
|
|
* Save base64 image in tmp folder to make it available at resultdb
|
|
* publication time.
|
|
*/
|
|
private static saveArtifact(base64Image: string): string {
|
|
base64Image = base64Image.replace(/^data:image\/png;base64,/, '');
|
|
const fileName = createHash('sha256').update(base64Image).digest('hex');
|
|
const artifactPath = path.join(os.tmpdir(), fileName);
|
|
fs.writeFileSync(artifactPath, base64Image, {encoding: 'base64'});
|
|
return artifactPath;
|
|
}
|
|
}
|