@vercel/devlow-bench 0.2.4 → 0.3.0
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/dist/browser.js +71 -41
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chromium } from "playwright-chromium";
|
|
2
2
|
import { measureTime, reportMeasurement } from "./index.js";
|
|
3
|
-
const browserOutput =
|
|
3
|
+
const browserOutput = Boolean(process.env.BROWSER_OUTPUT);
|
|
4
4
|
async function withRequestMetrics(metricName, page, fn) {
|
|
5
5
|
const activePromises = [];
|
|
6
6
|
const sizeByExtension = new Map();
|
|
@@ -9,7 +9,9 @@ async function withRequestMetrics(metricName, page, fn) {
|
|
|
9
9
|
activePromises.push((async () => {
|
|
10
10
|
const url = response.request().url();
|
|
11
11
|
const status = response.status();
|
|
12
|
-
const extension =
|
|
12
|
+
const extension =
|
|
13
|
+
// eslint-disable-next-line prefer-named-capture-group -- TODO: address lint
|
|
14
|
+
/^[^?#]+\.([a-z0-9]+)(?:[?#]|$)/i.exec(url)?.[1] ?? "none";
|
|
13
15
|
const currentRequests = requestsByExtension.get(extension) ?? 0;
|
|
14
16
|
requestsByExtension.set(extension, currentRequests + 1);
|
|
15
17
|
if (status >= 200 && status < 300) {
|
|
@@ -17,7 +19,9 @@ async function withRequestMetrics(metricName, page, fn) {
|
|
|
17
19
|
try {
|
|
18
20
|
body = await response.body();
|
|
19
21
|
}
|
|
20
|
-
catch {
|
|
22
|
+
catch {
|
|
23
|
+
// empty
|
|
24
|
+
}
|
|
21
25
|
if (body) {
|
|
22
26
|
const size = body.length;
|
|
23
27
|
const current = sizeByExtension.get(extension) ?? 0;
|
|
@@ -91,8 +95,15 @@ async function withRequestMetrics(metricName, page, fn) {
|
|
|
91
95
|
page.off("response", responseHandler);
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Waits until network requests have all been resolved
|
|
100
|
+
* @param page - Playwright page object
|
|
101
|
+
* @param delayMs - Amount of time in ms to wait after the last request resolves before cleaning up
|
|
102
|
+
* @param timeoutMs - Amount of time to wait before continuing. In case of timeout, this function resolves
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
105
|
+
function networkIdle(page, delayMs = 300, timeoutMs = 180000) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
96
107
|
const cleanup = () => {
|
|
97
108
|
page.off("request", requestHandler);
|
|
98
109
|
page.off("requestfailed", requestFinishedHandler);
|
|
@@ -102,42 +113,55 @@ function networkIdle(page, delay = 300, rejectTimeout = 180000) {
|
|
|
102
113
|
clearTimeout(timeout);
|
|
103
114
|
}
|
|
104
115
|
};
|
|
105
|
-
|
|
116
|
+
const requests = new Map();
|
|
117
|
+
const start = Date.now();
|
|
118
|
+
let lastRequest;
|
|
106
119
|
let timeout = null;
|
|
107
|
-
const requests = new Set();
|
|
108
120
|
const fullTimeout = setTimeout(() => {
|
|
109
121
|
cleanup();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
// eslint-disable-next-line no-console -- logging
|
|
123
|
+
console.error(`Timeout while waiting for network idle. These requests are still pending: ${Array.from(requests).join(", ")}} time is ${lastRequest - start}`);
|
|
124
|
+
resolve(Date.now() - lastRequest);
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
const requestFilter = (request) => {
|
|
127
|
+
return request.headers().accept !== "text/event-stream";
|
|
114
128
|
};
|
|
115
|
-
const requestHandler =
|
|
116
|
-
requests.
|
|
117
|
-
activeRequests++;
|
|
129
|
+
const requestHandler = (request) => {
|
|
130
|
+
requests.set(request.url(), (requests.get(request.url()) ?? 0) + 1);
|
|
118
131
|
if (timeout) {
|
|
119
132
|
clearTimeout(timeout);
|
|
120
133
|
timeout = null;
|
|
121
134
|
}
|
|
122
135
|
// Avoid tracking some requests, but we only know this after awaiting
|
|
123
136
|
// so we need to do this weird stunt to ensure that
|
|
124
|
-
if (!
|
|
125
|
-
|
|
137
|
+
if (!requestFilter(request)) {
|
|
138
|
+
requestFinishedInternal(request);
|
|
126
139
|
}
|
|
127
140
|
};
|
|
128
|
-
const requestFinishedHandler =
|
|
129
|
-
if (
|
|
141
|
+
const requestFinishedHandler = (request) => {
|
|
142
|
+
if (requestFilter(request)) {
|
|
130
143
|
requestFinishedInternal(request);
|
|
131
144
|
}
|
|
132
145
|
};
|
|
133
|
-
const requestFinishedInternal =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
146
|
+
const requestFinishedInternal = (request) => {
|
|
147
|
+
lastRequest = Date.now();
|
|
148
|
+
const currentCount = requests.get(request.url());
|
|
149
|
+
if (currentCount === undefined) {
|
|
150
|
+
// eslint-disable-next-line no-console -- basic logging
|
|
151
|
+
console.error(`Unexpected untracked but completed request ${request.url()}`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (currentCount === 1) {
|
|
155
|
+
requests.delete(request.url());
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
requests.set(request.url(), currentCount - 1);
|
|
159
|
+
}
|
|
160
|
+
if (requests.size === 0) {
|
|
137
161
|
timeout = setTimeout(() => {
|
|
138
162
|
cleanup();
|
|
139
|
-
resolve();
|
|
140
|
-
},
|
|
163
|
+
resolve(Date.now() - lastRequest);
|
|
164
|
+
}, delayMs);
|
|
141
165
|
}
|
|
142
166
|
};
|
|
143
167
|
page.on("request", requestHandler);
|
|
@@ -159,27 +183,28 @@ class BrowserSessionImpl {
|
|
|
159
183
|
await this.browser.close();
|
|
160
184
|
}
|
|
161
185
|
async hardNavigation(metricName, url) {
|
|
162
|
-
|
|
186
|
+
this.page = this.page ?? (await this.context.newPage());
|
|
187
|
+
const page = this.page;
|
|
163
188
|
await withRequestMetrics(metricName, page, async () => {
|
|
164
|
-
measureTime(`${metricName}/start`);
|
|
189
|
+
await measureTime(`${metricName}/start`);
|
|
165
190
|
const idle = networkIdle(page, 3000);
|
|
166
191
|
await page.goto(url, {
|
|
167
192
|
waitUntil: "commit",
|
|
168
193
|
});
|
|
169
|
-
measureTime(`${metricName}/html`, {
|
|
194
|
+
await measureTime(`${metricName}/html`, {
|
|
170
195
|
relativeTo: `${metricName}/start`,
|
|
171
196
|
});
|
|
172
197
|
await page.waitForLoadState("domcontentloaded");
|
|
173
|
-
measureTime(`${metricName}/dom`, {
|
|
198
|
+
await measureTime(`${metricName}/dom`, {
|
|
174
199
|
relativeTo: `${metricName}/start`,
|
|
175
200
|
});
|
|
176
201
|
await page.waitForLoadState("load");
|
|
177
|
-
measureTime(`${metricName}/load`, {
|
|
202
|
+
await measureTime(`${metricName}/load`, {
|
|
178
203
|
relativeTo: `${metricName}/start`,
|
|
179
204
|
});
|
|
180
|
-
await idle;
|
|
181
|
-
measureTime(`${metricName}`, {
|
|
182
|
-
offset
|
|
205
|
+
const offset = await idle;
|
|
206
|
+
await measureTime(`${metricName}`, {
|
|
207
|
+
offset,
|
|
183
208
|
relativeTo: `${metricName}/start`,
|
|
184
209
|
});
|
|
185
210
|
});
|
|
@@ -191,16 +216,20 @@ class BrowserSessionImpl {
|
|
|
191
216
|
throw new Error("softNavigationByClick() must be called after hardNavigation()");
|
|
192
217
|
}
|
|
193
218
|
await withRequestMetrics(metricName, page, async () => {
|
|
194
|
-
measureTime(`${metricName}/start`);
|
|
195
|
-
const firstResponse = new Promise((resolve) =>
|
|
219
|
+
await measureTime(`${metricName}/start`);
|
|
220
|
+
const firstResponse = new Promise((resolve) => {
|
|
221
|
+
page.once("response", () => {
|
|
222
|
+
resolve();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
196
225
|
const idle = networkIdle(page, 3000);
|
|
197
226
|
await page.click(selector);
|
|
198
227
|
await firstResponse;
|
|
199
|
-
measureTime(`${metricName}/firstResponse`, {
|
|
228
|
+
await measureTime(`${metricName}/firstResponse`, {
|
|
200
229
|
relativeTo: `${metricName}/start`,
|
|
201
230
|
});
|
|
202
231
|
await idle;
|
|
203
|
-
measureTime(`${metricName}`, {
|
|
232
|
+
await measureTime(`${metricName}`, {
|
|
204
233
|
offset: 3000,
|
|
205
234
|
relativeTo: `${metricName}/start`,
|
|
206
235
|
});
|
|
@@ -212,24 +241,24 @@ class BrowserSessionImpl {
|
|
|
212
241
|
throw new Error("reload() must be called after hardNavigation()");
|
|
213
242
|
}
|
|
214
243
|
await withRequestMetrics(metricName, page, async () => {
|
|
215
|
-
measureTime(`${metricName}/start`);
|
|
244
|
+
await measureTime(`${metricName}/start`);
|
|
216
245
|
const idle = networkIdle(page, 3000);
|
|
217
246
|
await page.reload({
|
|
218
247
|
waitUntil: "commit",
|
|
219
248
|
});
|
|
220
|
-
measureTime(`${metricName}/html`, {
|
|
249
|
+
await measureTime(`${metricName}/html`, {
|
|
221
250
|
relativeTo: `${metricName}/start`,
|
|
222
251
|
});
|
|
223
252
|
await page.waitForLoadState("domcontentloaded");
|
|
224
|
-
measureTime(`${metricName}/dom`, {
|
|
253
|
+
await measureTime(`${metricName}/dom`, {
|
|
225
254
|
relativeTo: `${metricName}/start`,
|
|
226
255
|
});
|
|
227
256
|
await page.waitForLoadState("load");
|
|
228
|
-
measureTime(`${metricName}/load`, {
|
|
257
|
+
await measureTime(`${metricName}/load`, {
|
|
229
258
|
relativeTo: `${metricName}/start`,
|
|
230
259
|
});
|
|
231
260
|
await idle;
|
|
232
|
-
measureTime(`${metricName}`, {
|
|
261
|
+
await measureTime(`${metricName}`, {
|
|
233
262
|
offset: 3000,
|
|
234
263
|
relativeTo: `${metricName}/start`,
|
|
235
264
|
});
|
|
@@ -239,6 +268,7 @@ class BrowserSessionImpl {
|
|
|
239
268
|
export async function newBrowserSession(options) {
|
|
240
269
|
const browser = await chromium.launch({
|
|
241
270
|
headless: options.headless ?? process.env.HEADLESS !== "false",
|
|
271
|
+
devtools: true,
|
|
242
272
|
timeout: 60000,
|
|
243
273
|
});
|
|
244
274
|
const context = await browser.newContext({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/devlow-bench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Benchmarking tool for the developer workflow",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"inquirer": "^9.2.7",
|
|
35
35
|
"minimist": "^1.2.8",
|
|
36
36
|
"pidusage-tree": "^2.0.5",
|
|
37
|
-
"playwright-chromium": "^1.
|
|
37
|
+
"playwright-chromium": "^1.39.0",
|
|
38
38
|
"split2": "^4.2.0",
|
|
39
39
|
"tree-kill": "^1.2.2"
|
|
40
40
|
},
|