puppeteer_tests: Translate logged traces and errors with source maps.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2021-03-24 16:26:42 -07:00 committed by Tim Abbott
parent da9783fd10
commit e62b0d6b5f
4 changed files with 100 additions and 24 deletions

View File

@ -2,8 +2,12 @@ import {strict as assert} from "assert";
import "css.escape"; import "css.escape";
import path from "path"; import path from "path";
import ErrorStackParser from "error-stack-parser";
import fetch from "node-fetch";
import type {Browser, ConsoleMessage, ConsoleMessageLocation, ElementHandle, Page} from "puppeteer"; import type {Browser, ConsoleMessage, ConsoleMessageLocation, ElementHandle, Page} from "puppeteer";
import {launch} from "puppeteer"; import {launch} from "puppeteer";
import StackFrame from "stackframe";
import StackTraceGPS from "stacktrace-gps";
import {test_credentials} from "../../var/puppeteer/test_credentials"; import {test_credentials} from "../../var/puppeteer/test_credentials";
@ -17,6 +21,8 @@ class CommonUtils {
screenshot_id = 0; screenshot_id = 0;
is_firefox = process.env.PUPPETEER_PRODUCT === "firefox"; is_firefox = process.env.PUPPETEER_PRODUCT === "firefox";
realm_url = "http://zulip.zulipdev.com:9981/"; realm_url = "http://zulip.zulipdev.com:9981/";
gps = new StackTraceGPS({ajax: async (url) => (await fetch(url)).text()});
pm_recipient = { pm_recipient = {
async set(page: Page, recipient: string): Promise<void> { async set(page: Page, recipient: string): Promise<void> {
// Without using the delay option here there seems to be // Without using the delay option here there seems to be
@ -493,51 +499,102 @@ class CommonUtils {
const browser = await this.ensure_browser(); const browser = await this.ensure_browser();
const page = await this.get_page(); const page = await this.get_page();
// Used to keep console messages in order after async source mapping
let console_ready = Promise.resolve();
page.on("console", (message: ConsoleMessage) => { page.on("console", (message: ConsoleMessage) => {
function context({url, lineNumber, columnNumber}: ConsoleMessageLocation): string { const context = async ({
url,
lineNumber,
columnNumber,
}: ConsoleMessageLocation): Promise<string> => {
if (lineNumber === undefined || columnNumber === undefined) { if (lineNumber === undefined || columnNumber === undefined) {
return `${url}`; return `${url}`;
} }
return `${url}:${lineNumber + 1}:${columnNumber + 1}`;
}
console.log(`${context(message.location())}: ${message.type()}: ${message.text()}`); let frame = new StackFrame({
fileName: url,
lineNumber: lineNumber + 1,
columnNumber: columnNumber + 1,
});
try {
frame = await this.gps.getMappedLocation(frame);
} catch {
// Ignore source mapping errors
}
return `${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`;
};
const console_ready1 = console_ready;
console_ready = (async () => {
let output = `${await context(
message.location(),
)}: ${message.type()}: ${message.text()}`;
if (message.type() === "trace") { if (message.type() === "trace") {
for (const frame of message.stackTrace()) { for (const frame of message.stackTrace()) {
console.log(` at ${context(frame)}`); output += `\n at ${await context(frame)}`;
} }
} }
await console_ready1;
console.log(output);
})();
}); });
let page_errored = false; let page_errored = false;
page.on("pageerror", async (error: Error) => { page.on("pageerror", (error: Error) => {
console.error("Page error:", error);
page_errored = true; page_errored = true;
// Puppeteer gives us the stack as the message for some reason.
const error1 = new Error("dummy");
error1.stack = error.message;
const console_ready1 = console_ready;
console_ready = (async () => {
const frames = await Promise.all(
ErrorStackParser.parse(error1).map(async (frame1) => {
let frame = (frame1 as unknown) as StackFrame;
try {
frame = await this.gps.getMappedLocation(frame);
} catch {
// Ignore source mapping errors
}
return `\n at ${frame.functionName} (${frame.fileName}:${frame.lineNumber}:${frame.columnNumber})`;
}),
);
await console_ready1;
console.error("Page error:", error.message.split("\n", 1)[0] + frames.join(""));
})();
const console_ready2 = console_ready;
console_ready = (async () => {
try { try {
// Take a screenshot, and increment the screenshot_id. // Take a screenshot, and increment the screenshot_id.
await this.screenshot(page, `failure-${this.screenshot_id}`); await this.screenshot(page, `failure-${this.screenshot_id}`);
this.screenshot_id += 1; this.screenshot_id += 1;
} finally { } finally {
await console_ready2;
console.log("Closing page to stop the test..."); console.log("Closing page to stop the test...");
await page.close(); await page.close();
} }
})();
}); });
try { try {
await test_function(page); await test_function(page);
} catch (error: unknown) {
console.log(error);
if (page_errored) {
throw new Error("Page threw an error");
}
} catch (error: unknown) {
if (!page_errored) { if (!page_errored) {
// Take a screenshot, and increment the screenshot_id. // Take a screenshot, and increment the screenshot_id.
await this.screenshot(page, `failure-${this.screenshot_id}`); await this.screenshot(page, `failure-${this.screenshot_id}`);
this.screenshot_id += 1; this.screenshot_id += 1;
} }
await browser.close(); throw error;
process.exit(1);
} finally { } finally {
await console_ready;
await browser.close(); await browser.close();
} }
} }

View File

@ -84,6 +84,7 @@
"@types/jquery": "^3.3.31", "@types/jquery": "^3.3.31",
"@types/mini-css-extract-plugin": "^1.0.0", "@types/mini-css-extract-plugin": "^1.0.0",
"@types/node": "^14.0.11", "@types/node": "^14.0.11",
"@types/node-fetch": "^2.5.8",
"@types/optimize-css-assets-webpack-plugin": "^5.0.1", "@types/optimize-css-assets-webpack-plugin": "^5.0.1",
"@types/terser-webpack-plugin": "^4.1.0", "@types/terser-webpack-plugin": "^4.1.0",
"@types/webpack": "^4.4.32", "@types/webpack": "^4.4.32",
@ -99,6 +100,7 @@
"js-yaml": "^4.0.0", "js-yaml": "^4.0.0",
"jsdom": "^16.1.0", "jsdom": "^16.1.0",
"mockdate": "^3.0.2", "mockdate": "^3.0.2",
"node-fetch": "^2.6.1",
"nyc": "^15.0.0", "nyc": "^15.0.0",
"openapi-examples-validator": "^4.0.0", "openapi-examples-validator": "^4.0.0",
"prettier": "^2.0.5", "prettier": "^2.0.5",

View File

@ -45,4 +45,4 @@ API_FEATURE_LEVEL = 44
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = "134.0" PROVISION_VERSION = "134.1"

View File

@ -1264,6 +1264,14 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
"@types/node-fetch@^2.5.8":
version "2.5.8"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*", "@types/node@^14.0.11": "@types/node@*", "@types/node@^14.0.11":
version "14.14.31" version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
@ -3229,7 +3237,7 @@ colormap@^2.3.1:
dependencies: dependencies:
lerp "^1.0.3" lerp "^1.0.3"
combined-stream@^1.0.6, combined-stream@~1.0.6: combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -5347,6 +5355,15 @@ form-data@^2.3.2:
combined-stream "^1.0.6" combined-stream "^1.0.6"
mime-types "^2.1.12" mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2: form-data@~2.3.2:
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"