-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
testPuppeteerPage.mjs
125 lines (104 loc) · 3.65 KB
/
testPuppeteerPage.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// @ts-check
import { ensureDir } from "std/fs/mod.ts";
import { fromFileUrl } from "std/path/mod.ts";
import { assertEquals } from "std/testing/asserts.ts";
const projectDirUrl = new URL("../", import.meta.url);
const coverageDirUrl = new URL(".coverage/", projectDirUrl);
/**
* Creates a new Puppeteer page for testing served project files with script
* coverage enabled.
* @param {import("puppeteer").Browser} browser Puppeteer browser.
* @param {URL} projectFilesOriginUrl Project files origin URL.
* @param {(page: import("puppeteer").Page) => void | Promise<void>} callback
* Receives the Puppeteer page, ready for testing.
*/
export default async function testPuppeteerPage(
browser,
projectFilesOriginUrl,
callback,
) {
// Unable to do an `instanceof` check.
if (typeof browser !== "object" || !browser) {
throw new TypeError(
"Argument 1 `browser` must be a Puppeteer `Browser` instance.",
);
}
if (!(projectFilesOriginUrl instanceof URL)) {
throw new TypeError(
"Argument 3 `projectFilesOriginUrl` must be a `URL` instance.",
);
}
if (typeof callback !== "function") {
throw new TypeError("Argument 5 `callback` must be a function.");
}
const page = await browser.newPage();
try {
/** @type {Array<Error>} */
const pageErrors = [];
page.on("pageerror", (error) => {
pageErrors.push(error);
});
page.on("console", (message) => {
console.group(`Puppeteer page console ${message.type()}:`);
console.log(message.text());
console.groupEnd();
});
await page.coverage.startJSCoverage();
try {
await callback(page);
} finally {
// @ts-ignore This private API is the only way to get the raw V8 script
// coverage result, see:
// https://github.com/puppeteer/puppeteer/issues/2136#issuecomment-657080751
const pageClient = page._client;
/** @type {{ result: Array<ScriptCoverage>}} */
const { result } = await pageClient.send("Profiler.takePreciseCoverage");
await page.coverage.stopJSCoverage();
await ensureDir(fromFileUrl(coverageDirUrl));
/** @type {Array<Promise<void>>} */
const writes = [];
for (const scriptCoverage of result) {
if (scriptCoverage.url.startsWith(projectFilesOriginUrl.href)) {
writes.push(
Deno.writeTextFile(
new URL(`${crypto.randomUUID()}.json`, coverageDirUrl),
JSON.stringify({
...scriptCoverage,
url: projectDirUrl.href +
scriptCoverage.url.slice(projectFilesOriginUrl.href.length),
}),
),
);
}
}
await Promise.all(writes);
}
assertEquals(pageErrors, []);
} finally {
await page.close();
}
}
/**
* Coverage data for a source range.
* @typedef {object} CoverageRange
* @prop {number} startOffset Source offset for the range start.
* @prop {number} endOffset Source offset for the range end.
* @prop {number} count Collected execution count for the source range.
*/
/**
* Coverage data for a JavaScript function.
* @typedef {object} FunctionCoverage
* @prop {string} functionName JavaScript function name.
* @prop {Array<CoverageRange>} ranges Source ranges within the function with
* coverage data.
* @prop {boolean} isBlockCoverage Does coverage data for this function have
* block granularity.
*/
/**
* Coverage data for a JavaScript script.
* @typedef {object} ScriptCoverage
* @prop {string} scriptId JavaScript script ID.
* @prop {string} url JavaScript script name or URL.
* @prop {Array<FunctionCoverage>} functions Functions within the script that
* have coverage data.
*/