@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.
Files changed (2) hide show
  1. package/dist/browser.js +71 -41
  2. 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 = !!process.env.BROWSER_OUTPUT;
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 = /^[^\?#]+\.([a-z0-9]+)(?:[\?#]|$)/i.exec(url)?.[1] ?? "none";
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
- function networkIdle(page, delay = 300, rejectTimeout = 180000) {
95
- return new Promise((resolve, reject) => {
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
- let activeRequests = 0;
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
- 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";
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 = async (request) => {
116
- requests.add(request.url());
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 (!(await requestFilter(request))) {
125
- await requestFinishedInternal(request);
137
+ if (!requestFilter(request)) {
138
+ requestFinishedInternal(request);
126
139
  }
127
140
  };
128
- const requestFinishedHandler = async (request) => {
129
- if (await requestFilter(request)) {
141
+ const requestFinishedHandler = (request) => {
142
+ if (requestFilter(request)) {
130
143
  requestFinishedInternal(request);
131
144
  }
132
145
  };
133
- const requestFinishedInternal = async (request) => {
134
- requests.delete(request.url());
135
- activeRequests--;
136
- if (activeRequests === 0) {
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
- }, delay);
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
- const page = (this.page = this.page ?? (await this.context.newPage()));
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: 3000,
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) => page.once("response", () => 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.2.4",
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.35.0",
37
+ "playwright-chromium": "^1.39.0",
38
38
  "split2": "^4.2.0",
39
39
  "tree-kill": "^1.2.2"
40
40
  },