@vercel/devlow-bench 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +82 -8
- package/dist/describe.d.ts +1 -0
- package/dist/describe.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,6 +118,10 @@ await session.softNavigationByClick("metric name", ".selector-to-click");
|
|
|
118
118
|
await session.close();
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
Run with `BROWSER_OUTPUT=1` to show the output of the browser.
|
|
122
|
+
|
|
123
|
+
Run with `HEADLESS=false` to show the actual browser window.
|
|
124
|
+
|
|
121
125
|
## Shell operations
|
|
122
126
|
|
|
123
127
|
The `devlow-bench` package provides a few helper functions to run operations in the shell.
|
|
@@ -151,6 +155,8 @@ shell.output
|
|
|
151
155
|
await shell.kill();
|
|
152
156
|
```
|
|
153
157
|
|
|
158
|
+
Run with `SHELL_OUTPUT=1` to show the output of the shell commands.
|
|
159
|
+
|
|
154
160
|
## File operations
|
|
155
161
|
|
|
156
162
|
The `devlow-bench` package provides a few helper functions to run operations on the file system.
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Page } from "playwright-chromium";
|
|
2
2
|
interface BrowserSession {
|
|
3
3
|
close(): Promise<void>;
|
|
4
|
-
hardNavigation(
|
|
4
|
+
hardNavigation(metricName: string, url: string): Promise<Page>;
|
|
5
5
|
softNavigationByClick(metricName: string, selector: string): Promise<void>;
|
|
6
6
|
reload(metricName: string): Promise<void>;
|
|
7
7
|
}
|
package/dist/browser.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chromium } from "playwright-chromium";
|
|
2
2
|
import { measureTime, reportMeasurement } from "./index.js";
|
|
3
|
+
const browserOutput = !!process.env.BROWSER_OUTPUT;
|
|
3
4
|
async function withRequestMetrics(metricName, page, fn) {
|
|
4
5
|
const activePromises = [];
|
|
5
6
|
const sizeByExtension = new Map();
|
|
@@ -25,8 +26,47 @@ async function withRequestMetrics(metricName, page, fn) {
|
|
|
25
26
|
}
|
|
26
27
|
})());
|
|
27
28
|
};
|
|
29
|
+
let errorCount = 0;
|
|
30
|
+
let warningCount = 0;
|
|
31
|
+
let logCount = 0;
|
|
32
|
+
const consoleHandler = (message) => {
|
|
33
|
+
const type = message.type();
|
|
34
|
+
if (type === "error") {
|
|
35
|
+
errorCount++;
|
|
36
|
+
}
|
|
37
|
+
else if (type === "warning") {
|
|
38
|
+
warningCount++;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
logCount++;
|
|
42
|
+
}
|
|
43
|
+
if (browserOutput) {
|
|
44
|
+
activePromises.push((async () => {
|
|
45
|
+
const args = [];
|
|
46
|
+
try {
|
|
47
|
+
const text = message.text();
|
|
48
|
+
for (const arg of message.args()) {
|
|
49
|
+
args.push(await arg.jsonValue());
|
|
50
|
+
}
|
|
51
|
+
console.log(`[${type}] ${text}`, ...args);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore
|
|
55
|
+
}
|
|
56
|
+
})());
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
let uncaughtCount = 0;
|
|
60
|
+
const exceptionHandler = (error) => {
|
|
61
|
+
uncaughtCount++;
|
|
62
|
+
if (browserOutput) {
|
|
63
|
+
console.error(`[UNCAUGHT]`, error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
28
66
|
try {
|
|
29
67
|
page.on("response", responseHandler);
|
|
68
|
+
page.on("console", consoleHandler);
|
|
69
|
+
page.on("pageerror", exceptionHandler);
|
|
30
70
|
await fn();
|
|
31
71
|
await Promise.all(activePromises);
|
|
32
72
|
let totalDownload = 0;
|
|
@@ -41,34 +81,63 @@ async function withRequestMetrics(metricName, page, fn) {
|
|
|
41
81
|
totalRequests += count;
|
|
42
82
|
}
|
|
43
83
|
reportMeasurement(`${metricName}/requests`, totalRequests, "requests");
|
|
84
|
+
reportMeasurement(`${metricName}/console/logs`, logCount, "messages");
|
|
85
|
+
reportMeasurement(`${metricName}/console/warnings`, warningCount, "messages");
|
|
86
|
+
reportMeasurement(`${metricName}/console/errors`, errorCount, "messages");
|
|
87
|
+
reportMeasurement(`${metricName}/console/uncaught`, uncaughtCount, "messages");
|
|
88
|
+
reportMeasurement(`${metricName}/console`, logCount + warningCount + errorCount + uncaughtCount, "messages");
|
|
44
89
|
}
|
|
45
90
|
finally {
|
|
46
91
|
page.off("response", responseHandler);
|
|
47
92
|
}
|
|
48
93
|
}
|
|
49
|
-
function networkIdle(page) {
|
|
50
|
-
return new Promise((resolve) => {
|
|
94
|
+
function networkIdle(page, delay = 300, rejectTimeout = 180000) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
51
96
|
const cleanup = () => {
|
|
52
97
|
page.off("request", requestHandler);
|
|
53
98
|
page.off("requestfailed", requestFinishedHandler);
|
|
54
99
|
page.off("requestfinished", requestFinishedHandler);
|
|
100
|
+
clearTimeout(fullTimeout);
|
|
101
|
+
if (timeout) {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
}
|
|
55
104
|
};
|
|
56
105
|
let activeRequests = 0;
|
|
57
106
|
let timeout = null;
|
|
58
|
-
const
|
|
107
|
+
const requests = new Set();
|
|
108
|
+
const fullTimeout = setTimeout(() => {
|
|
109
|
+
cleanup();
|
|
110
|
+
reject(new Error(`Timeout while waiting for network idle. These requests are still pending: ${Array.from(requests).join(", ")}}`));
|
|
111
|
+
}, rejectTimeout);
|
|
112
|
+
const requestFilter = async (request) => {
|
|
113
|
+
return (await request.headers().accept) !== "text/event-stream";
|
|
114
|
+
};
|
|
115
|
+
const requestHandler = async (request) => {
|
|
116
|
+
requests.add(request.url());
|
|
59
117
|
activeRequests++;
|
|
60
118
|
if (timeout) {
|
|
61
119
|
clearTimeout(timeout);
|
|
62
120
|
timeout = null;
|
|
63
121
|
}
|
|
122
|
+
// Avoid tracking some requests, but we only know this after awaiting
|
|
123
|
+
// so we need to do this weird stunt to ensure that
|
|
124
|
+
if (!(await requestFilter(request))) {
|
|
125
|
+
await requestFinishedInternal(request);
|
|
126
|
+
}
|
|
64
127
|
};
|
|
65
|
-
const requestFinishedHandler = (request) => {
|
|
128
|
+
const requestFinishedHandler = async (request) => {
|
|
129
|
+
if (await requestFilter(request)) {
|
|
130
|
+
requestFinishedInternal(request);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const requestFinishedInternal = async (request) => {
|
|
134
|
+
requests.delete(request.url());
|
|
66
135
|
activeRequests--;
|
|
67
136
|
if (activeRequests === 0) {
|
|
68
137
|
timeout = setTimeout(() => {
|
|
69
138
|
cleanup();
|
|
70
139
|
resolve();
|
|
71
|
-
},
|
|
140
|
+
}, delay);
|
|
72
141
|
}
|
|
73
142
|
};
|
|
74
143
|
page.on("request", requestHandler);
|
|
@@ -93,6 +162,7 @@ class BrowserSessionImpl {
|
|
|
93
162
|
const page = (this.page = this.page ?? (await this.context.newPage()));
|
|
94
163
|
await withRequestMetrics(metricName, page, async () => {
|
|
95
164
|
measureTime(`${metricName}/start`);
|
|
165
|
+
const idle = networkIdle(page, 3000);
|
|
96
166
|
await page.goto(url, {
|
|
97
167
|
waitUntil: "commit",
|
|
98
168
|
});
|
|
@@ -107,8 +177,9 @@ class BrowserSessionImpl {
|
|
|
107
177
|
measureTime(`${metricName}/load`, {
|
|
108
178
|
relativeTo: `${metricName}/start`,
|
|
109
179
|
});
|
|
110
|
-
await
|
|
180
|
+
await idle;
|
|
111
181
|
measureTime(`${metricName}`, {
|
|
182
|
+
offset: 3000,
|
|
112
183
|
relativeTo: `${metricName}/start`,
|
|
113
184
|
});
|
|
114
185
|
});
|
|
@@ -122,7 +193,7 @@ class BrowserSessionImpl {
|
|
|
122
193
|
await withRequestMetrics(metricName, page, async () => {
|
|
123
194
|
measureTime(`${metricName}/start`);
|
|
124
195
|
const firstResponse = new Promise((resolve) => page.once("response", () => resolve()));
|
|
125
|
-
const idle = networkIdle(page);
|
|
196
|
+
const idle = networkIdle(page, 3000);
|
|
126
197
|
await page.click(selector);
|
|
127
198
|
await firstResponse;
|
|
128
199
|
measureTime(`${metricName}/firstResponse`, {
|
|
@@ -130,6 +201,7 @@ class BrowserSessionImpl {
|
|
|
130
201
|
});
|
|
131
202
|
await idle;
|
|
132
203
|
measureTime(`${metricName}`, {
|
|
204
|
+
offset: 3000,
|
|
133
205
|
relativeTo: `${metricName}/start`,
|
|
134
206
|
});
|
|
135
207
|
});
|
|
@@ -141,6 +213,7 @@ class BrowserSessionImpl {
|
|
|
141
213
|
}
|
|
142
214
|
await withRequestMetrics(metricName, page, async () => {
|
|
143
215
|
measureTime(`${metricName}/start`);
|
|
216
|
+
const idle = networkIdle(page, 3000);
|
|
144
217
|
await page.reload({
|
|
145
218
|
waitUntil: "commit",
|
|
146
219
|
});
|
|
@@ -155,8 +228,9 @@ class BrowserSessionImpl {
|
|
|
155
228
|
measureTime(`${metricName}/load`, {
|
|
156
229
|
relativeTo: `${metricName}/start`,
|
|
157
230
|
});
|
|
158
|
-
await
|
|
231
|
+
await idle;
|
|
159
232
|
measureTime(`${metricName}`, {
|
|
233
|
+
offset: 3000,
|
|
160
234
|
relativeTo: `${metricName}/start`,
|
|
161
235
|
});
|
|
162
236
|
});
|
package/dist/describe.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare function measureTime(name: string, options?: {
|
|
|
10
10
|
relativeTo?: string | typeof PREVIOUS;
|
|
11
11
|
scenario?: string;
|
|
12
12
|
props?: Record<string, string | number | null>;
|
|
13
|
+
offset?: number;
|
|
13
14
|
}): void;
|
|
14
15
|
export declare function reportMeasurement(name: string, value: number, unit: string, options?: {
|
|
15
16
|
relativeTo?: string | typeof PREVIOUS;
|
package/dist/describe.js
CHANGED
|
@@ -57,7 +57,7 @@ export function withCurrent(current, fn) {
|
|
|
57
57
|
}
|
|
58
58
|
export const PREVIOUS = Symbol("previous measurement with that unit");
|
|
59
59
|
export function measureTime(name, options = {}) {
|
|
60
|
-
const end = Date.now();
|
|
60
|
+
const end = Date.now() - (options.offset || 0);
|
|
61
61
|
reportMeasurement(name, end, "ms", { relativeTo: PREVIOUS, ...options });
|
|
62
62
|
}
|
|
63
63
|
export function reportMeasurement(name, value, unit, options = {}) {
|