@cronn/playwright-utils 0.3.0 → 0.5.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/index.d.mts +162 -2
- package/dist/index.mjs +182 -2
- package/package.json +9 -9
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Normalizer } from "@cronn/playwright-file-snapshots";
|
|
2
|
-
import { APIRequestContext, APIResponse, Locator, Page, Request, Response, Route } from "@playwright/test";
|
|
2
|
+
import { APIRequestContext, APIResponse, ConsoleMessage, Locator, Page, Request, Response, Route } from "@playwright/test";
|
|
3
3
|
//#region src/environment.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Whether the current process runs in a CI environment, based on the `CI`
|
|
@@ -23,6 +23,166 @@ type FetchAPI = typeof fetch;
|
|
|
23
23
|
*/
|
|
24
24
|
declare function createFetchAdapter(requestContext: APIRequestContext): FetchAPI;
|
|
25
25
|
//#endregion
|
|
26
|
+
//#region src/capturing/console-captor.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* Filter used to select the messages collected by a {@link ConsoleCaptor}.
|
|
29
|
+
*/
|
|
30
|
+
type ConsoleMessageFilter = (message: ConsoleMessage) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The log level of a {@link ConsoleMessage}, e.g. `log`, `warning` or `error`.
|
|
33
|
+
*/
|
|
34
|
+
type ConsoleMessageLevel = ReturnType<ConsoleMessage["type"]>;
|
|
35
|
+
/**
|
|
36
|
+
* Collects the console messages of a page.
|
|
37
|
+
*
|
|
38
|
+
* Playwright reports the console output of a page through its `console` event,
|
|
39
|
+
* which has to be registered and removed again by hand. A captor bundles these
|
|
40
|
+
* steps, optionally filters the messages by level or a custom predicate and can
|
|
41
|
+
* limit the capturing to a single action.
|
|
42
|
+
*
|
|
43
|
+
* Several captors can run on the same page at the same time, each with its own
|
|
44
|
+
* filter, and every captor receives all matching messages independently.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const logs = captureConsole(page);
|
|
49
|
+
*
|
|
50
|
+
* await logs.during(async () => {
|
|
51
|
+
* await page.goto("/users");
|
|
52
|
+
* await page.getByRole("button", { name: "Only enabled" }).click();
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* expect(logs.messages.map((message) => message.text())).toEqual([
|
|
56
|
+
* "filter changed: enabled",
|
|
57
|
+
* ]);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare class ConsoleCaptor {
|
|
61
|
+
private readonly page;
|
|
62
|
+
private readonly consoleListener;
|
|
63
|
+
/**
|
|
64
|
+
* The captured messages, in the order in which the page reported them.
|
|
65
|
+
*
|
|
66
|
+
* The array is filled while the captor is running, so it can also be
|
|
67
|
+
* inspected inside the action passed to {@link during}.
|
|
68
|
+
*/
|
|
69
|
+
readonly messages: Array<ConsoleMessage>;
|
|
70
|
+
/**
|
|
71
|
+
* Create a captor for the console messages of a page.
|
|
72
|
+
*
|
|
73
|
+
* The captor does not collect anything until it is started, either by
|
|
74
|
+
* {@link startCapture} or by {@link during}.
|
|
75
|
+
*
|
|
76
|
+
* @param page - The page to capture console messages of
|
|
77
|
+
* @param filter - Optional. Decides which messages are captured, captures every message if omitted
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const captor = captureConsole(page, (message) =>
|
|
82
|
+
* message.location().url.endsWith("/analytics.js"),
|
|
83
|
+
* );
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
constructor(page: Page, filter?: ConsoleMessageFilter);
|
|
87
|
+
/**
|
|
88
|
+
* Start collecting the console messages of the page.
|
|
89
|
+
*
|
|
90
|
+
* A captor can be started and stopped repeatedly; the collected messages are
|
|
91
|
+
* kept across restarts.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* export const test = baseTest.extend<{ consoleErrors: ConsoleCaptor }>({
|
|
96
|
+
* consoleErrors: async ({ page }, use) => {
|
|
97
|
+
* const captor = captureConsole(page, "error");
|
|
98
|
+
* captor.start();
|
|
99
|
+
*
|
|
100
|
+
* await use(captor);
|
|
101
|
+
*
|
|
102
|
+
* captor.stop();
|
|
103
|
+
* expect(captor.messages.map((message) => message.text())).toEqual([]);
|
|
104
|
+
* },
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
startCapture(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Stop collecting the console messages of the page.
|
|
111
|
+
*
|
|
112
|
+
* The already captured {@link messages} are kept.
|
|
113
|
+
*/
|
|
114
|
+
stopCapture(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Remove all captured {@link messages}.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* captor.startCapture();
|
|
121
|
+
*
|
|
122
|
+
* await page.goto("/users");
|
|
123
|
+
* captor.clearMessages();
|
|
124
|
+
*
|
|
125
|
+
* await page.getByRole("button", { name: "Only enabled" }).click();
|
|
126
|
+
* expect(captor.messages).toHaveLength(1);
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
clearMessages(): void;
|
|
130
|
+
/**
|
|
131
|
+
* Collect the console messages reported while the callback runs.
|
|
132
|
+
*
|
|
133
|
+
* The capturing is stopped once the action has finished, even if it throws.
|
|
134
|
+
* A promise returned by the action is awaited before the captor stops, no
|
|
135
|
+
* matter when it is awaited by the caller.
|
|
136
|
+
*
|
|
137
|
+
* Only the returned value is awaited: asynchronous work which the action
|
|
138
|
+
* starts without returning it is not covered by the capturing.
|
|
139
|
+
*
|
|
140
|
+
* @param action - The action to capture console messages during
|
|
141
|
+
* @returns The value of the action
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const captor = captureConsole(page, "error");
|
|
146
|
+
*
|
|
147
|
+
* await captor.during(async () => {
|
|
148
|
+
* await page.getByRole("button", { name: "Create user" }).click();
|
|
149
|
+
* await expect(page.getByRole("alert")).toBeVisible();
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* expect(captor.messages).toHaveLength(1);
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
during<T>(action: () => Promise<T>): Promise<T>;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Factory method to create a {@link ConsoleCaptor} with the provided filter.
|
|
159
|
+
*
|
|
160
|
+
* The filter can be the log-level or a custom function on the message.
|
|
161
|
+
*
|
|
162
|
+
* @param page - The page to capture logs for
|
|
163
|
+
* @param levelOrFilter - The level or filter to filter out messages
|
|
164
|
+
* @returns ConsoleCaptor
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const errorCaptor = captureConsole(page, "error");
|
|
169
|
+
* const cspCaptor = captureConsole(page, message =>
|
|
170
|
+
* hasLogLevel(message, "warning", "error") && message.text().includes("Content Security Policy")
|
|
171
|
+
* );
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function captureConsole(page: Page, levelOrFilter?: ConsoleMessageLevel | Array<ConsoleMessageLevel> | ConsoleMessageFilter): ConsoleCaptor;
|
|
175
|
+
/**
|
|
176
|
+
* Tests whether the given console message has one of the specified log levels.
|
|
177
|
+
*
|
|
178
|
+
* This can be useful to define a filter with {@link captureConsole}.
|
|
179
|
+
*
|
|
180
|
+
* @param message - The console message to check
|
|
181
|
+
* @param level - The log level
|
|
182
|
+
* @returns True if the message has one of the given log levels
|
|
183
|
+
*/
|
|
184
|
+
declare function hasLogLevel(message: ConsoleMessage, ...level: Array<ConsoleMessageLevel>): boolean;
|
|
185
|
+
//#endregion
|
|
26
186
|
//#region src/api/route-interceptor/filter.d.ts
|
|
27
187
|
/**
|
|
28
188
|
* HTTP Methods used by {@link RouteFilter}
|
|
@@ -334,4 +494,4 @@ declare function maskedValueWithIndex(name: string): (index: number) => string;
|
|
|
334
494
|
*/
|
|
335
495
|
type PlaywrightTarget = Page | Locator;
|
|
336
496
|
//#endregion
|
|
337
|
-
export { type HttpMethod, type PlaywrightTarget, type ResponseHandler, type RouteFilter, RouteInterceptor, RouteInterceptorFixture, createFetchAdapter, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot };
|
|
497
|
+
export { ConsoleCaptor, type ConsoleMessageFilter, type ConsoleMessageLevel, type HttpMethod, type PlaywrightTarget, type ResponseHandler, type RouteFilter, RouteInterceptor, RouteInterceptorFixture, captureConsole, createFetchAdapter, hasLogLevel, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot };
|
package/dist/index.mjs
CHANGED
|
@@ -54,6 +54,186 @@ function mapToRequestBody(body) {
|
|
|
54
54
|
return { data: body };
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
|
+
//#region src/capturing/console-captor.ts
|
|
58
|
+
function defaultFilter(_message) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Collects the console messages of a page.
|
|
63
|
+
*
|
|
64
|
+
* Playwright reports the console output of a page through its `console` event,
|
|
65
|
+
* which has to be registered and removed again by hand. A captor bundles these
|
|
66
|
+
* steps, optionally filters the messages by level or a custom predicate and can
|
|
67
|
+
* limit the capturing to a single action.
|
|
68
|
+
*
|
|
69
|
+
* Several captors can run on the same page at the same time, each with its own
|
|
70
|
+
* filter, and every captor receives all matching messages independently.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const logs = captureConsole(page);
|
|
75
|
+
*
|
|
76
|
+
* await logs.during(async () => {
|
|
77
|
+
* await page.goto("/users");
|
|
78
|
+
* await page.getByRole("button", { name: "Only enabled" }).click();
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* expect(logs.messages.map((message) => message.text())).toEqual([
|
|
82
|
+
* "filter changed: enabled",
|
|
83
|
+
* ]);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
var ConsoleCaptor = class {
|
|
87
|
+
page;
|
|
88
|
+
consoleListener;
|
|
89
|
+
/**
|
|
90
|
+
* The captured messages, in the order in which the page reported them.
|
|
91
|
+
*
|
|
92
|
+
* The array is filled while the captor is running, so it can also be
|
|
93
|
+
* inspected inside the action passed to {@link during}.
|
|
94
|
+
*/
|
|
95
|
+
messages = [];
|
|
96
|
+
/**
|
|
97
|
+
* Create a captor for the console messages of a page.
|
|
98
|
+
*
|
|
99
|
+
* The captor does not collect anything until it is started, either by
|
|
100
|
+
* {@link startCapture} or by {@link during}.
|
|
101
|
+
*
|
|
102
|
+
* @param page - The page to capture console messages of
|
|
103
|
+
* @param filter - Optional. Decides which messages are captured, captures every message if omitted
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const captor = captureConsole(page, (message) =>
|
|
108
|
+
* message.location().url.endsWith("/analytics.js"),
|
|
109
|
+
* );
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
constructor(page, filter = defaultFilter) {
|
|
113
|
+
this.page = page;
|
|
114
|
+
this.consoleListener = (event) => {
|
|
115
|
+
if (filter(event)) this.messages.push(event);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Start collecting the console messages of the page.
|
|
120
|
+
*
|
|
121
|
+
* A captor can be started and stopped repeatedly; the collected messages are
|
|
122
|
+
* kept across restarts.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* export const test = baseTest.extend<{ consoleErrors: ConsoleCaptor }>({
|
|
127
|
+
* consoleErrors: async ({ page }, use) => {
|
|
128
|
+
* const captor = captureConsole(page, "error");
|
|
129
|
+
* captor.start();
|
|
130
|
+
*
|
|
131
|
+
* await use(captor);
|
|
132
|
+
*
|
|
133
|
+
* captor.stop();
|
|
134
|
+
* expect(captor.messages.map((message) => message.text())).toEqual([]);
|
|
135
|
+
* },
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
startCapture() {
|
|
140
|
+
this.page.on("console", this.consoleListener);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stop collecting the console messages of the page.
|
|
144
|
+
*
|
|
145
|
+
* The already captured {@link messages} are kept.
|
|
146
|
+
*/
|
|
147
|
+
stopCapture() {
|
|
148
|
+
this.page.off("console", this.consoleListener);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Remove all captured {@link messages}.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* captor.startCapture();
|
|
156
|
+
*
|
|
157
|
+
* await page.goto("/users");
|
|
158
|
+
* captor.clearMessages();
|
|
159
|
+
*
|
|
160
|
+
* await page.getByRole("button", { name: "Only enabled" }).click();
|
|
161
|
+
* expect(captor.messages).toHaveLength(1);
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
clearMessages() {
|
|
165
|
+
this.messages.length = 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Collect the console messages reported while the callback runs.
|
|
169
|
+
*
|
|
170
|
+
* The capturing is stopped once the action has finished, even if it throws.
|
|
171
|
+
* A promise returned by the action is awaited before the captor stops, no
|
|
172
|
+
* matter when it is awaited by the caller.
|
|
173
|
+
*
|
|
174
|
+
* Only the returned value is awaited: asynchronous work which the action
|
|
175
|
+
* starts without returning it is not covered by the capturing.
|
|
176
|
+
*
|
|
177
|
+
* @param action - The action to capture console messages during
|
|
178
|
+
* @returns The value of the action
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const captor = captureConsole(page, "error");
|
|
183
|
+
*
|
|
184
|
+
* await captor.during(async () => {
|
|
185
|
+
* await page.getByRole("button", { name: "Create user" }).click();
|
|
186
|
+
* await expect(page.getByRole("alert")).toBeVisible();
|
|
187
|
+
* });
|
|
188
|
+
*
|
|
189
|
+
* expect(captor.messages).toHaveLength(1);
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
async during(action) {
|
|
193
|
+
this.startCapture();
|
|
194
|
+
try {
|
|
195
|
+
return await action();
|
|
196
|
+
} finally {
|
|
197
|
+
this.stopCapture();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Factory method to create a {@link ConsoleCaptor} with the provided filter.
|
|
203
|
+
*
|
|
204
|
+
* The filter can be the log-level or a custom function on the message.
|
|
205
|
+
*
|
|
206
|
+
* @param page - The page to capture logs for
|
|
207
|
+
* @param levelOrFilter - The level or filter to filter out messages
|
|
208
|
+
* @returns ConsoleCaptor
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const errorCaptor = captureConsole(page, "error");
|
|
213
|
+
* const cspCaptor = captureConsole(page, message =>
|
|
214
|
+
* hasLogLevel(message, "warning", "error") && message.text().includes("Content Security Policy")
|
|
215
|
+
* );
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
function captureConsole(page, levelOrFilter) {
|
|
219
|
+
if (levelOrFilter === void 0) return new ConsoleCaptor(page);
|
|
220
|
+
if (typeof levelOrFilter === "function") return new ConsoleCaptor(page, levelOrFilter);
|
|
221
|
+
if (Array.isArray(levelOrFilter)) return new ConsoleCaptor(page, (message) => hasLogLevel(message, ...levelOrFilter));
|
|
222
|
+
return new ConsoleCaptor(page, (message) => hasLogLevel(message, levelOrFilter));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Tests whether the given console message has one of the specified log levels.
|
|
226
|
+
*
|
|
227
|
+
* This can be useful to define a filter with {@link captureConsole}.
|
|
228
|
+
*
|
|
229
|
+
* @param message - The console message to check
|
|
230
|
+
* @param level - The log level
|
|
231
|
+
* @returns True if the message has one of the given log levels
|
|
232
|
+
*/
|
|
233
|
+
function hasLogLevel(message, ...level) {
|
|
234
|
+
return level.includes(message.type());
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
57
237
|
//#region src/api/route-interceptor/interceptor.ts
|
|
58
238
|
function isFiltered(filter, request) {
|
|
59
239
|
if (!filter.url(new URL(request.url()))) return false;
|
|
@@ -101,7 +281,7 @@ async function withSuspendedRoute(page, filter, action) {
|
|
|
101
281
|
await route.fallback();
|
|
102
282
|
return;
|
|
103
283
|
}
|
|
104
|
-
const suspendedRoute = barrier.promise.then(() => route.
|
|
284
|
+
const suspendedRoute = barrier.promise.then(() => route.fallback());
|
|
105
285
|
suspendedRoutes.add(suspendedRoute);
|
|
106
286
|
try {
|
|
107
287
|
await suspendedRoute;
|
|
@@ -464,4 +644,4 @@ function identity(value) {
|
|
|
464
644
|
return value;
|
|
465
645
|
}
|
|
466
646
|
//#endregion
|
|
467
|
-
export { RouteInterceptor, RouteInterceptorFixture, createFetchAdapter, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot };
|
|
647
|
+
export { ConsoleCaptor, RouteInterceptor, RouteInterceptorFixture, captureConsole, createFetchAdapter, hasLogLevel, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/playwright-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Utilities for writing tests with Playwright",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"playwright",
|
|
@@ -29,30 +29,30 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"package-directory": "8.2.0"
|
|
32
|
+
"package-directory": "^8.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@arethetypeswrong/core": "0.18.5",
|
|
36
36
|
"@changesets/cli": "3.0.1",
|
|
37
|
-
"@cronn/element-snapshot": "0.
|
|
37
|
+
"@cronn/element-snapshot": "0.28.0",
|
|
38
38
|
"@cronn/playwright-file-snapshots": "2.2.1",
|
|
39
39
|
"@playwright/test": "1.62.1",
|
|
40
40
|
"@types/node": "24.13.3",
|
|
41
41
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
42
42
|
"@vitest/coverage-v8": "4.1.11",
|
|
43
|
-
"eslint": "10.9.
|
|
43
|
+
"eslint": "10.9.1",
|
|
44
44
|
"eslint-config-prettier": "10.1.8",
|
|
45
45
|
"eslint-plugin-check-file": "3.3.2",
|
|
46
46
|
"eslint-plugin-unused-imports": "4.4.1",
|
|
47
|
-
"knip": "6.
|
|
48
|
-
"oxc-minify": "0.
|
|
49
|
-
"oxfmt": "0.
|
|
47
|
+
"knip": "6.33.0",
|
|
48
|
+
"oxc-minify": "0.147.0",
|
|
49
|
+
"oxfmt": "0.65.0",
|
|
50
50
|
"publint": "0.3.24",
|
|
51
51
|
"rimraf": "6.1.3",
|
|
52
52
|
"tsdown": "0.22.14",
|
|
53
|
-
"turbo": "2.10.
|
|
53
|
+
"turbo": "2.10.12",
|
|
54
54
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
55
|
-
"typescript-eslint": "8.
|
|
55
|
+
"typescript-eslint": "8.68.0",
|
|
56
56
|
"vitepress": "2.0.0-alpha.19",
|
|
57
57
|
"vitepress-plugin-group-icons": "1.7.6",
|
|
58
58
|
"vitest": "4.1.11",
|