@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 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(url: string, metricName?: string): Promise<Page>;
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 requestHandler = (request) => {
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
- }, 300);
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 page.waitForLoadState("networkidle");
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 page.waitForLoadState("networkidle");
231
+ await idle;
159
232
  measureTime(`${metricName}`, {
233
+ offset: 3000,
160
234
  relativeTo: `${metricName}/start`,
161
235
  });
162
236
  });
@@ -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 = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/devlow-bench",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Benchmarking tool for the developer workflow",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",