@web/test-runner-commands 0.0.0-canary-20230403143511
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 +7 -0
- package/browser/commands.d.ts +297 -0
- package/browser/commands.mjs +223 -0
- package/dist/a11ySnapshotPlugin.d.ts +6 -0
- package/dist/a11ySnapshotPlugin.d.ts.map +1 -0
- package/dist/a11ySnapshotPlugin.js +44 -0
- package/dist/a11ySnapshotPlugin.js.map +1 -0
- package/dist/emulateMediaPlugin.d.ts +9 -0
- package/dist/emulateMediaPlugin.d.ts.map +1 -0
- package/dist/emulateMediaPlugin.js +55 -0
- package/dist/emulateMediaPlugin.js.map +1 -0
- package/dist/filePlugin.d.ts +16 -0
- package/dist/filePlugin.d.ts.map +1 -0
- package/dist/filePlugin.js +95 -0
- package/dist/filePlugin.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/selectOptionPlugin.d.ts +17 -0
- package/dist/selectOptionPlugin.d.ts.map +1 -0
- package/dist/selectOptionPlugin.js +97 -0
- package/dist/selectOptionPlugin.js.map +1 -0
- package/dist/sendKeysPlugin.d.ts +17 -0
- package/dist/sendKeysPlugin.d.ts.map +1 -0
- package/dist/sendKeysPlugin.js +104 -0
- package/dist/sendKeysPlugin.js.map +1 -0
- package/dist/sendMousePlugin.d.ts +24 -0
- package/dist/sendMousePlugin.d.ts.map +1 -0
- package/dist/sendMousePlugin.js +133 -0
- package/dist/sendMousePlugin.js.map +1 -0
- package/dist/setUserAgentPlugin.d.ts +3 -0
- package/dist/setUserAgentPlugin.d.ts.map +1 -0
- package/dist/setUserAgentPlugin.js +23 -0
- package/dist/setUserAgentPlugin.js.map +1 -0
- package/dist/setViewportPlugin.d.ts +9 -0
- package/dist/setViewportPlugin.d.ts.map +1 -0
- package/dist/setViewportPlugin.js +48 -0
- package/dist/setViewportPlugin.js.map +1 -0
- package/dist/snapshotPlugin.d.ts +10 -0
- package/dist/snapshotPlugin.d.ts.map +1 -0
- package/dist/snapshotPlugin.js +177 -0
- package/dist/snapshotPlugin.js.map +1 -0
- package/index.d.ts +2 -0
- package/package.json +60 -0
- package/plugins.d.ts +2 -0
- package/plugins.js +2 -0
- package/plugins.mjs +24 -0
- package/src/a11ySnapshotPlugin.ts +53 -0
- package/src/emulateMediaPlugin.ts +75 -0
- package/src/filePlugin.ts +108 -0
- package/src/index.ts +8 -0
- package/src/selectOptionPlugin.ts +122 -0
- package/src/sendKeysPlugin.ts +124 -0
- package/src/sendMousePlugin.ts +191 -0
- package/src/setUserAgentPlugin.ts +25 -0
- package/src/setViewportPlugin.ts +59 -0
- package/src/snapshotPlugin.ts +205 -0
package/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Test Runner Commands
|
|
2
|
+
|
|
3
|
+
Commands for executing code server-side during your tests in the browser. To control the browser page, access the file system, or execute NodeJS libraries.
|
|
4
|
+
|
|
5
|
+
Built-in commands can change the viewport, emulate media queries or set the user agent. You can create custom commands to implement your own functionalities.
|
|
6
|
+
|
|
7
|
+
See [our website](https://modern-web.dev/docs/test-runner/commands/) for full documentation.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Media,
|
|
3
|
+
Viewport,
|
|
4
|
+
SendKeysPayload,
|
|
5
|
+
A11ySnapshotPayload,
|
|
6
|
+
WriteFilePayload,
|
|
7
|
+
ReadFilePayload,
|
|
8
|
+
RemoveFilePayload,
|
|
9
|
+
SnapshotPluginConfig,
|
|
10
|
+
SaveSnapshotPayload,
|
|
11
|
+
SendMousePayload,
|
|
12
|
+
} from '../dist/index';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Executes a command on the server. If this is a custom command, you need to implement a plugin
|
|
16
|
+
* to execute this command.
|
|
17
|
+
*/
|
|
18
|
+
export function executeServerCommand<R, P>(command: string, payload?: P): Promise<R>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sets the viewport of the browser.
|
|
22
|
+
*/
|
|
23
|
+
export function setViewport(viewport: Viewport): Promise<void>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Emulates browser media, such as screen/print or color scheme, to be used in
|
|
27
|
+
* CSS media queries.
|
|
28
|
+
*
|
|
29
|
+
* The `forcedColors` property is Playwright-only API that does not work in WebKit.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* await emulateMedia({
|
|
34
|
+
* colorScheme: 'dark',
|
|
35
|
+
* reducedMotion: 'reduce',
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* await emulateMedia({
|
|
42
|
+
* media: 'print',
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* await emulateMedia({
|
|
49
|
+
* forcedColors: 'active',
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function emulateMedia(media: Media): Promise<void>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sets the user agent of the browser. This is a puppeteer-only API.
|
|
57
|
+
*/
|
|
58
|
+
export function setUserAgent(userAgent: string): Promise<void>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sends a string of keys for the browser to press (all at once, as with single keys
|
|
62
|
+
* or shortcuts; e.g. `{press: 'Tab'}` or `{press: 'Shift+a'}` or
|
|
63
|
+
* `{press: 'Option+ArrowUp}`) or type (in sequence, e.g. `{type: 'Your name'}`) natively.
|
|
64
|
+
*
|
|
65
|
+
* For specific documentation of the strings to leverage here, see the Playwright documentation,
|
|
66
|
+
* here:
|
|
67
|
+
*
|
|
68
|
+
* - `press`: https://playwright.dev/docs/api/class-keyboard#keyboardpresskey-options
|
|
69
|
+
* - `type`: https://playwright.dev/docs/api/class-keyboard#keyboardtypetext-options
|
|
70
|
+
*
|
|
71
|
+
* Or, the Puppeter documentation, here:
|
|
72
|
+
*
|
|
73
|
+
* - `press`: https://pptr.dev/#?product=Puppeteer&show=api-keyboardpresskey-options
|
|
74
|
+
* - `type`: https://pptr.dev/#?product=Puppeteer&show=api-keyboardtypetext-options
|
|
75
|
+
*
|
|
76
|
+
* Or, the Webdriver documentation, here:
|
|
77
|
+
*
|
|
78
|
+
* https://webdriver.io/docs/api/browser/keys/
|
|
79
|
+
*
|
|
80
|
+
* @param payload An object including a `press` or `type` property an the associated string
|
|
81
|
+
* for the browser runner to apply via that input method.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* await sendKeys({
|
|
86
|
+
* press: 'Tab',
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* await sendKeys({
|
|
93
|
+
* type: 'Your address',
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
**/
|
|
98
|
+
export function sendKeys(payload: SendKeysPayload): Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Sends an action for the mouse to move it to a specific position or click a mouse button (left, middle, or right).
|
|
102
|
+
*
|
|
103
|
+
* WARNING: When moving the mouse or holding down a mouse button, the mouse stays in this state as long as
|
|
104
|
+
* you do not explicitly move it to another position or release the button. For this reason, it is recommended
|
|
105
|
+
* to reset the mouse state with the `resetMouse` command after each test case manipulating the mouse to avoid
|
|
106
|
+
* unexpected side effects.
|
|
107
|
+
*
|
|
108
|
+
* @param payload An object representing a mouse action specified by the `type` property (move, click, down, up)
|
|
109
|
+
* and including some properties to configure this action.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* await sendMouse({
|
|
114
|
+
* type: 'move',
|
|
115
|
+
* position: [100, 100]
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* await sendMouse({
|
|
122
|
+
* type: 'click',
|
|
123
|
+
* position: [100, 100],
|
|
124
|
+
* button: 'right'
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* await sendMouse({
|
|
131
|
+
* type: 'down'
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
**/
|
|
136
|
+
export function sendMouse(payload: SendMousePayload): Promise<void>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resets the mouse position to (0, 0) and releases mouse buttons.
|
|
140
|
+
*
|
|
141
|
+
* Use this command to reset the mouse state after mouse manipulations by the `sendMouse` command.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```
|
|
145
|
+
* it('does something with the mouse', () => {
|
|
146
|
+
* await sendMouse({ type: 'move', position: [150, 150] });
|
|
147
|
+
* await sendMouse({ type: 'down', button: 'middle' });
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* afterEach(() => {
|
|
151
|
+
* await resetMouse();
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function resetMouse(): Promise<void>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Request a snapshot of the Accessibility Tree of the entire page or starting from
|
|
159
|
+
* the element that is obtained via the `selector` property of the `payload` argument.
|
|
160
|
+
*
|
|
161
|
+
* Learn more about the tree that is returned from Playwright here:
|
|
162
|
+
* - https://playwright.dev/docs/api/class-accessibility/
|
|
163
|
+
*
|
|
164
|
+
* Learn more about the tree that is returned from Puppeteer here:
|
|
165
|
+
* - https://pptr.dev/#?product=Puppeteer&show=api-class-accessibility
|
|
166
|
+
*
|
|
167
|
+
* @param payload An object including a `selector` property pointing to the root of the
|
|
168
|
+
* a11y tree you'd like returned.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* await a11ySnapshot();
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* await a11ySnapshot({
|
|
178
|
+
* selector: 'main'
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function a11ySnapshot(payload: A11ySnapshotPayload): Promise<void>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Walk the provided accessibility tree that starts on `node` and test each
|
|
186
|
+
* node in the tree until one is found that meast the `test` provided.
|
|
187
|
+
*
|
|
188
|
+
* Learn more about the tree that is returned from Playwright here:
|
|
189
|
+
* - https://playwright.dev/docs/api/class-accessibility/
|
|
190
|
+
*
|
|
191
|
+
* Learn more about the tree that is returned from Puppeteer here:
|
|
192
|
+
* - https://pptr.dev/#?product=Puppeteer&show=api-class-accessibility
|
|
193
|
+
*
|
|
194
|
+
* @param node
|
|
195
|
+
* @param test
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* // return whether a node in the `snapshot` has `name: 'Label Text Value Text'`
|
|
199
|
+
* ```ts
|
|
200
|
+
* findAccessibilityNode<{ name: string }>(
|
|
201
|
+
* snapshot,
|
|
202
|
+
* (node) => node.name === 'Label Text Value Text'
|
|
203
|
+
* )
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function findAccessibilityNode<TNode>(
|
|
207
|
+
node: TNode & { children: TNode[] },
|
|
208
|
+
test: (node: TNode) => boolean,
|
|
209
|
+
): TNode | null;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Writes a file to disk.
|
|
213
|
+
*
|
|
214
|
+
* @param payload.filePath the path of the file to save. This is a path relative to the test file
|
|
215
|
+
* being executed. It cannot be an absolute path.
|
|
216
|
+
* @param payload.content the file content to save as a string.
|
|
217
|
+
* @param payload.encoding optional encoding to use when saving the file.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* await writeFile({ path: 'hello-world.txt', content: 'Hello world!' });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export function writeFile(payload: WriteFilePayload): Promise<void>;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Reads a file from disk.
|
|
228
|
+
*
|
|
229
|
+
* @param payload.filePath the path of the file to read. This is a path relative to the test file
|
|
230
|
+
* being executed. It cannot be an absolute path.
|
|
231
|
+
* @param payload.encoding optional encoding to use when reading the file.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const content = await readFile({ path: 'hello-world.txt' });
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export function readFile(payload: ReadFilePayload): Promise<string | undefined>;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Removes a file from disk.
|
|
242
|
+
*
|
|
243
|
+
* @param payload.filePath the path of the file to remove. This is a path relative to the test file
|
|
244
|
+
* being executed. It cannot be an absolute path.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* await removeFile({ path: 'hello-world.txt' });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function removeFile(payload: RemoveFilePayload): Promise<void>;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Gets configuration for snapshot testing.
|
|
255
|
+
*
|
|
256
|
+
* @param payload.updateSnapshots whether to updated snapshots that are not the same
|
|
257
|
+
*/
|
|
258
|
+
export function getSnapshotConfig(): Promise<SnapshotPluginConfig>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Gets the snapshots stored for this test file.
|
|
262
|
+
*/
|
|
263
|
+
export function getSnapshots(): Promise<Record<string, string>>;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Saves a snapshot for this test file.
|
|
267
|
+
*
|
|
268
|
+
* @param payload.name the name of the snapshot
|
|
269
|
+
*/
|
|
270
|
+
export function getSnapshot(options: { name: string }): Promise<string | undefined>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Saves a snapshot for this test file.
|
|
274
|
+
*
|
|
275
|
+
* @param payload.name the name of the snapshot
|
|
276
|
+
* @param payload.content the content of the snapshot as a string
|
|
277
|
+
*/
|
|
278
|
+
export function saveSnapshot(options: SaveSnapshotPayload): Promise<void>;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Removes stored snapshots for this test file.
|
|
282
|
+
*
|
|
283
|
+
* @param payload.name the name of the snapshot
|
|
284
|
+
*
|
|
285
|
+
*/
|
|
286
|
+
export function removeSnapshot(options: { name: string }): Promise<void>;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* If no snapshot exists on disk or if update-snapshots is set to true server-side,
|
|
290
|
+
* the passed snapshot is saved. Otherwise the snapshot is compared with what's stored
|
|
291
|
+
* on disk. If the snapshots are not equal, an error is thrown.
|
|
292
|
+
*
|
|
293
|
+
* @param options
|
|
294
|
+
*/
|
|
295
|
+
export function compareSnapshot(options: { name: string; content: string }): Promise<void>;
|
|
296
|
+
|
|
297
|
+
export { Media, Viewport, SendKeysPayload, WriteFilePayload, ReadFilePayload, RemoveFilePayload };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* eslint-env browser, es2020 */
|
|
2
|
+
const PARAM_SESSION_ID = 'wtr-session-id';
|
|
3
|
+
|
|
4
|
+
const sessionId = new URL(window.location.href).searchParams.get(PARAM_SESSION_ID);
|
|
5
|
+
|
|
6
|
+
function isObject(payload) {
|
|
7
|
+
return payload != null && typeof payload === 'object';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function executeServerCommand(command, payload, pluginName) {
|
|
11
|
+
if (typeof sessionId !== 'string') {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Unable to execute server commands in a browser not controlled by the test runner. ' +
|
|
14
|
+
'Use the debug option from the watch menu to debug in a controlled browser.',
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let sendMessageWaitForResponse;
|
|
19
|
+
try {
|
|
20
|
+
const webSocketModule = await import('/__web-dev-server__web-socket.js');
|
|
21
|
+
({ sendMessageWaitForResponse } = webSocketModule);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Could not setup web socket connection. Are you executing this test through Web Test Runner?',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const response = await sendMessageWaitForResponse({
|
|
30
|
+
type: 'wtr-command',
|
|
31
|
+
sessionId,
|
|
32
|
+
command,
|
|
33
|
+
payload,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!response.executed) {
|
|
37
|
+
let msg;
|
|
38
|
+
if (pluginName) {
|
|
39
|
+
msg = `Unknown command ${command}. Add the ${pluginName} to your config.`;
|
|
40
|
+
} else {
|
|
41
|
+
msg = `Unknown command ${command}. Did you install a plugin to handle this command?`;
|
|
42
|
+
}
|
|
43
|
+
throw new Error(msg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response.result;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Error while executing command ${command}${
|
|
50
|
+
payload ? ` with payload ${JSON.stringify(payload)}` : ''
|
|
51
|
+
}: ${error.message}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function setViewport(viewport) {
|
|
57
|
+
return executeServerCommand('set-viewport', viewport);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function emulateMedia(media) {
|
|
61
|
+
return executeServerCommand('emulate-media', media);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function setUserAgent(options) {
|
|
65
|
+
return executeServerCommand('set-user-agent', options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sendKeys(options) {
|
|
69
|
+
return executeServerCommand('send-keys', options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function selectOption(options) {
|
|
73
|
+
return executeServerCommand('select-option', options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function sendMouse(options) {
|
|
77
|
+
return executeServerCommand('send-mouse', options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resetMouse(options) {
|
|
81
|
+
return executeServerCommand('reset-mouse', options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function a11ySnapshot(options) {
|
|
85
|
+
return executeServerCommand('a11y-snapshot', options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function writeFile(options) {
|
|
89
|
+
return executeServerCommand('write-file', options, 'filePlugin from @web/test-runner-commands');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function readFile(options) {
|
|
93
|
+
return executeServerCommand('read-file', options, 'filePlugin from @web/test-runner-commands');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function removeFile(options) {
|
|
97
|
+
return executeServerCommand('remove-file', options, 'filePlugin from @web/test-runner-commands');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function findAccessibilityNode(node, test) {
|
|
101
|
+
if (test(node)) return node;
|
|
102
|
+
for (const child of node.children || []) {
|
|
103
|
+
const foundNode = findAccessibilityNode(child, test);
|
|
104
|
+
if (foundNode) {
|
|
105
|
+
return foundNode;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let snapshotConfig;
|
|
112
|
+
let cachedSnapshots;
|
|
113
|
+
|
|
114
|
+
export async function getSnapshotConfig() {
|
|
115
|
+
if (!snapshotConfig) {
|
|
116
|
+
snapshotConfig = await executeServerCommand(
|
|
117
|
+
'get-snapshot-config',
|
|
118
|
+
undefined,
|
|
119
|
+
'snapshotPlugin from @web/test-runner-commands',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return snapshotConfig;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* This regexp is used to capture the snapshots contents.
|
|
128
|
+
*
|
|
129
|
+
* snapshots\[[^\]]+] = (\n)? - snapshot definition. Sometimes the initial content backtick is placed in the next line
|
|
130
|
+
* (?<content>`[^`]+`) - capture the snapshot content, which is included between backticks "`"
|
|
131
|
+
* /gm - global and multiline
|
|
132
|
+
* @type {RegExp}
|
|
133
|
+
*/
|
|
134
|
+
const ESCAPE_REGEX = /snapshots\[[^\]]+] = (\n)?(?<content>`[^`]*`)/gm;
|
|
135
|
+
|
|
136
|
+
const escapeContent = content => {
|
|
137
|
+
[...content.matchAll(ESCAPE_REGEX)].forEach(({ groups: { content: itemContent } }) => {
|
|
138
|
+
content = content.replaceAll(itemContent, encodeURIComponent(itemContent));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return content;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export async function getSnapshots({ cache = true } = {}) {
|
|
145
|
+
if (cache && cachedSnapshots) {
|
|
146
|
+
return cachedSnapshots;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = await executeServerCommand(
|
|
150
|
+
'get-snapshots',
|
|
151
|
+
undefined,
|
|
152
|
+
'snapshotPlugin from @web/test-runner-commands',
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (typeof result?.content !== 'string') {
|
|
156
|
+
throw new Error('Expected a result as string');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const content = `${escapeContent(result.content)}/* ${Math.random()} */`;
|
|
160
|
+
const module = await import(`data:text/javascript;charset=utf-8,${content}`);
|
|
161
|
+
|
|
162
|
+
if (!module || !isObject(module.snapshots)) {
|
|
163
|
+
throw new Error('Expected snapshot result to be a module that exports an object.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
cachedSnapshots = module.snapshots;
|
|
167
|
+
|
|
168
|
+
return cachedSnapshots;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function getSnapshot(options) {
|
|
172
|
+
if (!isObject(options)) throw new Error('You must provide a payload object');
|
|
173
|
+
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
|
|
174
|
+
|
|
175
|
+
const snapshots = await getSnapshots(options);
|
|
176
|
+
|
|
177
|
+
return snapshots[options.name];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function saveSnapshot(options) {
|
|
181
|
+
if (!isObject(options)) throw new Error('You must provide a payload object');
|
|
182
|
+
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
|
|
183
|
+
if (options.content !== undefined && typeof options.content !== 'string')
|
|
184
|
+
throw new Error('You must provide a snapshot content');
|
|
185
|
+
|
|
186
|
+
// ensure snapshots for this file are loaded
|
|
187
|
+
const snapshots = await getSnapshots();
|
|
188
|
+
|
|
189
|
+
// store snapshot in-memory
|
|
190
|
+
snapshots[options.name] = options.content;
|
|
191
|
+
|
|
192
|
+
return executeServerCommand(
|
|
193
|
+
'save-snapshot',
|
|
194
|
+
options,
|
|
195
|
+
'snapshotPlugin from @web/test-runner-commands',
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function removeSnapshot(options) {
|
|
200
|
+
if (!isObject(options)) throw new Error('You must provide a payload object');
|
|
201
|
+
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
|
|
202
|
+
|
|
203
|
+
return saveSnapshot({ ...options, content: undefined });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function compareSnapshot({ name, content }) {
|
|
207
|
+
const currentSnapshot = await getSnapshot({ name });
|
|
208
|
+
if (currentSnapshot) {
|
|
209
|
+
const config = await getSnapshotConfig();
|
|
210
|
+
if (!config.updateSnapshots) {
|
|
211
|
+
if (currentSnapshot !== content) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Snapshots for ${name} are not equal. \n\n` +
|
|
214
|
+
`Stored:\n${currentSnapshot}\n\n` +
|
|
215
|
+
`New:\n${content}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
} else if (currentSnapshot === content) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
await saveSnapshot({ name, content });
|
|
223
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11ySnapshotPlugin.d.ts","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,oBAAY,mBAAmB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,wBAAgB,kBAAkB,IAAI,gBAAgB,CAAC,mBAAmB,CAAC,CA8C1E"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.a11ySnapshotPlugin = void 0;
|
|
4
|
+
function a11ySnapshotPlugin() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'a11y-snapshot-command',
|
|
7
|
+
async executeCommand({ command, payload, session }) {
|
|
8
|
+
if (command === 'a11y-snapshot') {
|
|
9
|
+
// handle specific behavior for playwright
|
|
10
|
+
if (session.browser.type === 'playwright') {
|
|
11
|
+
const page = session.browser.getPage(session.id);
|
|
12
|
+
const options = {};
|
|
13
|
+
if (payload && payload.selector) {
|
|
14
|
+
const root = await page.$(payload.selector);
|
|
15
|
+
if (root) {
|
|
16
|
+
options.root = root;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const snapshot = await page.accessibility.snapshot(options);
|
|
20
|
+
return snapshot;
|
|
21
|
+
}
|
|
22
|
+
// handle specific behavior for puppeteer
|
|
23
|
+
if (session.browser.type === 'puppeteer') {
|
|
24
|
+
const page = session.browser.getPage(session.id);
|
|
25
|
+
const options = {};
|
|
26
|
+
if (payload && payload.selector) {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
const root = await page.$(payload.selector);
|
|
29
|
+
if (root) {
|
|
30
|
+
options.root = root;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
const snapshot = await page.accessibility.snapshot(options);
|
|
35
|
+
return snapshot;
|
|
36
|
+
}
|
|
37
|
+
// you might not be able to support all browser launchers
|
|
38
|
+
throw new Error(`Acessibility snapshot is not supported for browser type ${session.browser.type}.`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
exports.a11ySnapshotPlugin = a11ySnapshotPlugin;
|
|
44
|
+
//# sourceMappingURL=a11ySnapshotPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11ySnapshotPlugin.js","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":";;;AAMA,SAAgB,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YAChD,IAAI,OAAO,KAAK,eAAe,EAAE;gBAC/B,0CAA0C;gBAC1C,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE;oBACzC,MAAM,IAAI,GAAI,OAAO,CAAC,OAA8B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACzE,MAAM,OAAO,GAET,EAAE,CAAC;oBACP,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE;wBAC/B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;wBAC5C,IAAI,IAAI,EAAE;4BACR,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;yBACrB;qBACF;oBACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBAC5D,OAAO,QAAQ,CAAC;iBACjB;gBAED,yCAAyC;gBACzC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE;oBACxC,MAAM,IAAI,GAAI,OAAO,CAAC,OAA0B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACrE,MAAM,OAAO,GAET,EAAE,CAAC;oBACP,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE;wBAC/B,aAAa;wBACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;wBAC5C,IAAI,IAAI,EAAE;4BACR,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;yBACrB;qBACF;oBACD,aAAa;oBACb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBAC5D,OAAO,QAAQ,CAAC;iBACjB;gBAED,yDAAyD;gBACzD,MAAM,IAAI,KAAK,CACb,2DAA2D,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CACnF,CAAC;aACH;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AA9CD,gDA8CC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TestRunnerPlugin } from '@web/test-runner-core';
|
|
2
|
+
export interface Media {
|
|
3
|
+
media?: 'screen' | 'print';
|
|
4
|
+
colorScheme?: 'dark' | 'light' | 'no-preference';
|
|
5
|
+
reducedMotion?: 'reduce' | 'no-preference';
|
|
6
|
+
forcedColors?: 'active' | 'none';
|
|
7
|
+
}
|
|
8
|
+
export declare function emulateMediaPlugin(): TestRunnerPlugin;
|
|
9
|
+
//# sourceMappingURL=emulateMediaPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulateMediaPlugin.d.ts","sourceRoot":"","sources":["../src/emulateMediaPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,eAAe,CAAC;IACjD,aAAa,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC;IAC3C,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAClC;AAuBD,wBAAgB,kBAAkB,IAAI,gBAAgB,CA0CrD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emulateMediaPlugin = void 0;
|
|
4
|
+
function isObject(payload) {
|
|
5
|
+
return payload != null && typeof payload === 'object';
|
|
6
|
+
}
|
|
7
|
+
function isMediaObject(payload) {
|
|
8
|
+
if (!isObject(payload))
|
|
9
|
+
throw new Error('You must provide a viewport object');
|
|
10
|
+
if (payload.media != null && !(typeof payload.media === 'string')) {
|
|
11
|
+
throw new Error('media should be a string.');
|
|
12
|
+
}
|
|
13
|
+
if (payload.colorScheme != null && !(typeof payload.colorScheme === 'string')) {
|
|
14
|
+
throw new Error('colorScheme should be a string.');
|
|
15
|
+
}
|
|
16
|
+
if (payload.reducedMotion != null && !(typeof payload.reducedMotion === 'string')) {
|
|
17
|
+
throw new Error('reducedMotion should be a string.');
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
function emulateMediaPlugin() {
|
|
22
|
+
return {
|
|
23
|
+
name: 'emulate-media-command',
|
|
24
|
+
async executeCommand({ command, payload, session }) {
|
|
25
|
+
if (command === 'emulate-media') {
|
|
26
|
+
if (!isMediaObject(payload)) {
|
|
27
|
+
throw new Error('You must provide a viewport object');
|
|
28
|
+
}
|
|
29
|
+
if (session.browser.type === 'puppeteer') {
|
|
30
|
+
const page = session.browser.getPage(session.id);
|
|
31
|
+
const features = [];
|
|
32
|
+
if (payload.colorScheme != null) {
|
|
33
|
+
features.push({ name: 'prefers-color-scheme', value: payload.colorScheme });
|
|
34
|
+
}
|
|
35
|
+
if (payload.reducedMotion != null) {
|
|
36
|
+
features.push({ name: 'prefers-reduced-motion', value: payload.reducedMotion });
|
|
37
|
+
}
|
|
38
|
+
await page.emulateMediaFeatures(features);
|
|
39
|
+
if (payload.media) {
|
|
40
|
+
await page.emulateMediaType(payload.media);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (session.browser.type === 'playwright') {
|
|
45
|
+
const page = session.browser.getPage(session.id);
|
|
46
|
+
await page.emulateMedia(payload);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Setting viewport is not supported for browser type ${session.browser.type}.`);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
exports.emulateMediaPlugin = emulateMediaPlugin;
|
|
55
|
+
//# sourceMappingURL=emulateMediaPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulateMediaPlugin.js","sourceRoot":"","sources":["../src/emulateMediaPlugin.ts"],"names":[],"mappings":";;;AAWA,SAAS,QAAQ,CAAC,OAAgB;IAChC,OAAO,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC;AACxD,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACrC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAE9E,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,EAAE;QACjE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;KAC9C;IAED,IAAI,OAAO,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,EAAE;QAC7E,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;KACpD;IAED,IAAI,OAAO,CAAC,aAAa,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,aAAa,KAAK,QAAQ,CAAC,EAAE;QACjF,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;KACtD;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,uBAAuB;QAE7B,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YAChD,IAAI,OAAO,KAAK,eAAe,EAAE;gBAC/B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;oBAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;iBACvD;gBAED,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE;oBACxC,MAAM,IAAI,GAAI,OAAO,CAAC,OAA0B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACrE,MAAM,QAAQ,GAAG,EAAE,CAAC;oBAEpB,IAAI,OAAO,CAAC,WAAW,IAAI,IAAI,EAAE;wBAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;qBAC7E;oBACD,IAAI,OAAO,CAAC,aAAa,IAAI,IAAI,EAAE;wBACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;qBACjF;oBAED,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;oBAE1C,IAAI,OAAO,CAAC,KAAK,EAAE;wBACjB,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;qBAC5C;oBAED,OAAO,IAAI,CAAC;iBACb;gBAED,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE;oBACzC,MAAM,IAAI,GAAI,OAAO,CAAC,OAA8B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACzE,MAAM,IAAI,CAAC,YAAY,CAAC,OAAc,CAAC,CAAC;oBACxC,OAAO,IAAI,CAAC;iBACb;gBAED,MAAM,IAAI,KAAK,CACb,sDAAsD,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CAC9E,CAAC;aACH;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AA1CD,gDA0CC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { TestRunnerPlugin } from '@web/test-runner-core';
|
|
3
|
+
export interface WriteFilePayload {
|
|
4
|
+
path: string;
|
|
5
|
+
content: string;
|
|
6
|
+
encoding?: BufferEncoding;
|
|
7
|
+
}
|
|
8
|
+
export interface ReadFilePayload {
|
|
9
|
+
path: string;
|
|
10
|
+
encoding?: BufferEncoding;
|
|
11
|
+
}
|
|
12
|
+
export interface RemoveFilePayload {
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function filePlugin(): TestRunnerPlugin;
|
|
16
|
+
//# sourceMappingURL=filePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filePlugin.d.ts","sourceRoot":"","sources":["../src/filePlugin.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAwBzD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AA6BD,wBAAgB,UAAU,IAAI,gBAAgB,CAyC7C"}
|