@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.
Files changed (58) hide show
  1. package/README.md +7 -0
  2. package/browser/commands.d.ts +297 -0
  3. package/browser/commands.mjs +223 -0
  4. package/dist/a11ySnapshotPlugin.d.ts +6 -0
  5. package/dist/a11ySnapshotPlugin.d.ts.map +1 -0
  6. package/dist/a11ySnapshotPlugin.js +44 -0
  7. package/dist/a11ySnapshotPlugin.js.map +1 -0
  8. package/dist/emulateMediaPlugin.d.ts +9 -0
  9. package/dist/emulateMediaPlugin.d.ts.map +1 -0
  10. package/dist/emulateMediaPlugin.js +55 -0
  11. package/dist/emulateMediaPlugin.js.map +1 -0
  12. package/dist/filePlugin.d.ts +16 -0
  13. package/dist/filePlugin.d.ts.map +1 -0
  14. package/dist/filePlugin.js +95 -0
  15. package/dist/filePlugin.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +20 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/selectOptionPlugin.d.ts +17 -0
  21. package/dist/selectOptionPlugin.d.ts.map +1 -0
  22. package/dist/selectOptionPlugin.js +97 -0
  23. package/dist/selectOptionPlugin.js.map +1 -0
  24. package/dist/sendKeysPlugin.d.ts +17 -0
  25. package/dist/sendKeysPlugin.d.ts.map +1 -0
  26. package/dist/sendKeysPlugin.js +104 -0
  27. package/dist/sendKeysPlugin.js.map +1 -0
  28. package/dist/sendMousePlugin.d.ts +24 -0
  29. package/dist/sendMousePlugin.d.ts.map +1 -0
  30. package/dist/sendMousePlugin.js +133 -0
  31. package/dist/sendMousePlugin.js.map +1 -0
  32. package/dist/setUserAgentPlugin.d.ts +3 -0
  33. package/dist/setUserAgentPlugin.d.ts.map +1 -0
  34. package/dist/setUserAgentPlugin.js +23 -0
  35. package/dist/setUserAgentPlugin.js.map +1 -0
  36. package/dist/setViewportPlugin.d.ts +9 -0
  37. package/dist/setViewportPlugin.d.ts.map +1 -0
  38. package/dist/setViewportPlugin.js +48 -0
  39. package/dist/setViewportPlugin.js.map +1 -0
  40. package/dist/snapshotPlugin.d.ts +10 -0
  41. package/dist/snapshotPlugin.d.ts.map +1 -0
  42. package/dist/snapshotPlugin.js +177 -0
  43. package/dist/snapshotPlugin.js.map +1 -0
  44. package/index.d.ts +2 -0
  45. package/package.json +60 -0
  46. package/plugins.d.ts +2 -0
  47. package/plugins.js +2 -0
  48. package/plugins.mjs +24 -0
  49. package/src/a11ySnapshotPlugin.ts +53 -0
  50. package/src/emulateMediaPlugin.ts +75 -0
  51. package/src/filePlugin.ts +108 -0
  52. package/src/index.ts +8 -0
  53. package/src/selectOptionPlugin.ts +122 -0
  54. package/src/sendKeysPlugin.ts +124 -0
  55. package/src/sendMousePlugin.ts +191 -0
  56. package/src/setUserAgentPlugin.ts +25 -0
  57. package/src/setViewportPlugin.ts +59 -0
  58. package/src/snapshotPlugin.ts +205 -0
@@ -0,0 +1,122 @@
1
+ import { TestRunnerPlugin } from '@web/test-runner-core';
2
+ import type { ChromeLauncher } from '@web/test-runner-chrome';
3
+ import type { PlaywrightLauncher } from '@web/test-runner-playwright';
4
+
5
+ type SelectByValuePayload = { selector: string; value: string };
6
+ type SelectByLabelPayload = { selector: string; label: string };
7
+ type SelectMultipleValuesPayload = { selector: string; values: string[] };
8
+
9
+ export type SelectOptionPayload =
10
+ | SelectByLabelPayload
11
+ | SelectByValuePayload
12
+ | SelectMultipleValuesPayload;
13
+
14
+ function isObject(payload: unknown): payload is Record<string, unknown> {
15
+ return payload != null && typeof payload === 'object';
16
+ }
17
+
18
+ function isSelectOptionPayload(payload: unknown): boolean {
19
+ const validOptions = ['value', 'label', 'values'];
20
+
21
+ if (!isObject(payload)) throw new Error('You must provide a `SelectOptionPayload` object');
22
+
23
+ if (!payload.selector || typeof payload.selector !== 'string') {
24
+ throw new Error(`You must provide a selector representing the select you wish to locate`);
25
+ }
26
+
27
+ const numberOfValidOptions = Object.keys(payload).filter(key =>
28
+ validOptions.includes(key),
29
+ ).length;
30
+ const unknownOptions = Object.keys(payload).filter(
31
+ key => ![...validOptions, 'selector'].includes(key),
32
+ );
33
+
34
+ if (numberOfValidOptions === 0)
35
+ throw new Error(
36
+ `You must provide one of the following properties to pass to the browser runner: ${validOptions.join(
37
+ ', ',
38
+ )}.`,
39
+ );
40
+ if (unknownOptions.length > 0) {
41
+ throw new Error('Unknown options `' + unknownOptions.join(', ') + '` present.');
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function isValuePayload(payload: SelectOptionPayload): payload is SelectByValuePayload {
47
+ return 'selector' in payload && 'value' in payload;
48
+ }
49
+
50
+ function isLabelPayload(payload: SelectOptionPayload): payload is SelectByLabelPayload {
51
+ return 'selector' in payload && 'label' in payload;
52
+ }
53
+
54
+ function isMultiplePayload(payload: SelectOptionPayload): payload is SelectMultipleValuesPayload {
55
+ return 'selector' in payload && 'values' in payload;
56
+ }
57
+
58
+ export function selectOptionPlugin(): TestRunnerPlugin<SelectOptionPayload> {
59
+ return {
60
+ name: 'select-option-command',
61
+ async executeCommand({ command, payload, session }): Promise<any> {
62
+ if (command === 'select-option') {
63
+ if (!isSelectOptionPayload(payload) || !payload) {
64
+ throw new Error('You must provide a `SelectOptionPayload` object');
65
+ }
66
+ // handle specific behavior for playwright
67
+ if (session.browser.type === 'playwright') {
68
+ const page = (session.browser as PlaywrightLauncher).getPage(session.id);
69
+ if (isValuePayload(payload)) {
70
+ const { selector, value } = payload;
71
+ await page.locator(selector).selectOption(value);
72
+ return true;
73
+ } else if (isLabelPayload(payload)) {
74
+ const { selector, label } = payload;
75
+ await page.locator(selector).selectOption({ label });
76
+ return true;
77
+ } else if (isMultiplePayload(payload)) {
78
+ const { selector, values } = payload;
79
+ await page.locator(selector).selectOption([...values]);
80
+ return true;
81
+ }
82
+ }
83
+
84
+ // handle specific behavior for puppeteer
85
+ if (session.browser.type === 'puppeteer') {
86
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
87
+ if (isValuePayload(payload)) {
88
+ const { selector, value } = payload;
89
+ await page.select(selector, value);
90
+ return true;
91
+ } else if (isMultiplePayload(payload)) {
92
+ const { selector, values } = payload;
93
+ await page.select(selector, ...values);
94
+ return true;
95
+ } else {
96
+ throw new Error(`Puppeteer only supports selection of an option by its value(s):
97
+ https://pptr.dev/next/api/puppeteer.page.select#parameters
98
+ `);
99
+ }
100
+ }
101
+
102
+ // handle specific behavior for webdriver
103
+ if (session.browser.type === 'webdriver') {
104
+ throw new Error(`Selecting an option via a browser driver command is not currently implemented in WebDriver yet.
105
+ https://www.selenium.dev/documentation/webdriver/elements/select_lists/
106
+
107
+ When using WebDriver, the current recommended (September 2022) approach is to:
108
+ - locate your select element (via querySelector or querySelectorAll)
109
+ - focus the element
110
+ - use sendKeys() to type the label of the option you wish to select
111
+ - use select.dispatchEvent(new Event('change')) to trigger a change event on the select.
112
+ `);
113
+ }
114
+
115
+ // you might not be able to support all browser launchers
116
+ throw new Error(
117
+ `Selection of select element options is not supported for browser type ${session.browser.type}.`,
118
+ );
119
+ }
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,124 @@
1
+ import { TestRunnerPlugin } from '@web/test-runner-core';
2
+ import type { ChromeLauncher, puppeteerCore } from '@web/test-runner-chrome';
3
+ import type { PlaywrightLauncher } from '@web/test-runner-playwright';
4
+ import type { WebdriverLauncher } from '@web/test-runner-webdriver';
5
+
6
+ type TypePayload = { type: string };
7
+ type PressPayload = { press: string };
8
+ type DownPayload = { down: string };
9
+ type UpPayload = { up: string };
10
+
11
+ export type SendKeysPayload = TypePayload | PressPayload | DownPayload | UpPayload;
12
+
13
+ function isObject(payload: unknown): payload is Record<string, unknown> {
14
+ return payload != null && typeof payload === 'object';
15
+ }
16
+
17
+ function isSendKeysPayload(payload: unknown): boolean {
18
+ const validOptions = ['type', 'press', 'down', 'up'];
19
+
20
+ if (!isObject(payload)) throw new Error('You must provide a `SendKeysPayload` object');
21
+
22
+ const numberOfValidOptions = Object.keys(payload).filter(key =>
23
+ validOptions.includes(key),
24
+ ).length;
25
+ const unknownOptions = Object.keys(payload).filter(key => !validOptions.includes(key));
26
+
27
+ if (numberOfValidOptions > 1)
28
+ throw new Error(
29
+ `You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join(
30
+ ', ',
31
+ )}.`,
32
+ );
33
+ if (numberOfValidOptions === 0)
34
+ throw new Error(
35
+ `You must provide one of the following properties to pass to the browser runner: ${validOptions.join(
36
+ ', ',
37
+ )}.`,
38
+ );
39
+ if (unknownOptions.length > 0) {
40
+ throw new Error('Unknown options `' + unknownOptions.join(', ') + '` present.');
41
+ }
42
+ return true;
43
+ }
44
+
45
+ function isTypePayload(payload: SendKeysPayload): payload is TypePayload {
46
+ return 'type' in payload;
47
+ }
48
+
49
+ function isPressPayload(payload: SendKeysPayload): payload is PressPayload {
50
+ return 'press' in payload;
51
+ }
52
+
53
+ function isDownPayload(payload: SendKeysPayload): payload is DownPayload {
54
+ return 'down' in payload;
55
+ }
56
+
57
+ function isUpPayload(payload: SendKeysPayload): payload is UpPayload {
58
+ return 'up' in payload;
59
+ }
60
+
61
+ export function sendKeysPlugin(): TestRunnerPlugin<SendKeysPayload> {
62
+ return {
63
+ name: 'send-keys-command',
64
+ async executeCommand({ command, payload, session }): Promise<any> {
65
+ if (command === 'send-keys') {
66
+ if (!isSendKeysPayload(payload) || !payload) {
67
+ throw new Error('You must provide a `SendKeysPayload` object');
68
+ }
69
+ // handle specific behavior for playwright
70
+ if (session.browser.type === 'playwright') {
71
+ const page = (session.browser as PlaywrightLauncher).getPage(session.id);
72
+ if (isTypePayload(payload)) {
73
+ await page.keyboard.type(payload.type);
74
+ return true;
75
+ } else if (isPressPayload(payload)) {
76
+ await page.keyboard.press(payload.press);
77
+ return true;
78
+ } else if (isDownPayload(payload)) {
79
+ await page.keyboard.down(payload.down);
80
+ return true;
81
+ } else if (isUpPayload(payload)) {
82
+ await page.keyboard.up(payload.up);
83
+ return true;
84
+ }
85
+ }
86
+
87
+ // handle specific behavior for puppeteer
88
+ if (session.browser.type === 'puppeteer') {
89
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
90
+ if (isTypePayload(payload)) {
91
+ await page.keyboard.type(payload.type);
92
+ return true;
93
+ } else if (isPressPayload(payload)) {
94
+ await page.keyboard.press(payload.press as puppeteerCore.KeyInput);
95
+ return true;
96
+ } else if (isDownPayload(payload)) {
97
+ await page.keyboard.down(payload.down as puppeteerCore.KeyInput);
98
+ return true;
99
+ } else if (isUpPayload(payload)) {
100
+ await page.keyboard.up(payload.up as puppeteerCore.KeyInput);
101
+ return true;
102
+ }
103
+ }
104
+
105
+ // handle specific behavior for webdriver
106
+ if (session.browser.type === 'webdriver') {
107
+ const browser = session.browser as WebdriverLauncher;
108
+ if (isTypePayload(payload)) {
109
+ await browser.sendKeys(session.id, payload.type.split(''));
110
+ return true;
111
+ } else if (isPressPayload(payload)) {
112
+ await browser.sendKeys(session.id, [payload.press]);
113
+ return true;
114
+ } else {
115
+ throw new Error('Only "press" and "type" are supported by webdriver.');
116
+ }
117
+ }
118
+
119
+ // you might not be able to support all browser launchers
120
+ throw new Error(`Sending keys is not supported for browser type ${session.browser.type}.`);
121
+ }
122
+ },
123
+ };
124
+ }
@@ -0,0 +1,191 @@
1
+ import { TestRunnerPlugin } from '@web/test-runner-core';
2
+ import type { ChromeLauncher } from '@web/test-runner-chrome';
3
+ import type { PlaywrightLauncher } from '@web/test-runner-playwright';
4
+ import type { WebdriverLauncher } from '@web/test-runner-webdriver';
5
+
6
+ type MousePosition = [number, number];
7
+ type MouseButton = 'left' | 'middle' | 'right';
8
+
9
+ export type SendMousePayload = MovePayload | ClickPayload | DownPayload | UpPayload;
10
+
11
+ export type MovePayload = {
12
+ type: 'move';
13
+ position: MousePosition;
14
+ };
15
+
16
+ export type ClickPayload = {
17
+ type: 'click';
18
+ position: MousePosition;
19
+ button?: MouseButton;
20
+ };
21
+
22
+ export type DownPayload = {
23
+ type: 'down';
24
+ button?: MouseButton;
25
+ };
26
+
27
+ export type UpPayload = {
28
+ type: 'up';
29
+ button?: MouseButton;
30
+ };
31
+
32
+ function isObject(payload: unknown): payload is Record<string, unknown> {
33
+ return payload != null && typeof payload === 'object';
34
+ }
35
+
36
+ function isSendMousePayload(payload: unknown): payload is SendMousePayload {
37
+ const validTypes = ['move', 'click', 'down', 'up'];
38
+ const validButtons = ['left', 'middle', 'right'];
39
+
40
+ if (!isObject(payload)) {
41
+ throw new Error('You must provide a `SendMousePayload` object');
42
+ }
43
+
44
+ if (typeof payload.type !== 'string' || !validTypes.includes(payload.type)) {
45
+ throw new Error(
46
+ `You must provide a type option with one of the following values: ${validTypes.join(', ')}.`,
47
+ );
48
+ }
49
+
50
+ if (['click', 'move'].includes(payload.type)) {
51
+ if (
52
+ !Array.isArray(payload.position) ||
53
+ typeof payload.position[0] !== 'number' ||
54
+ typeof payload.position[1] !== 'number' ||
55
+ !Number.isInteger(payload.position[0]) ||
56
+ !Number.isInteger(payload.position[1])
57
+ ) {
58
+ throw new Error(
59
+ 'You must provide a position option as a [x, y] tuple where x and y are integers.',
60
+ );
61
+ }
62
+ }
63
+
64
+ if (['click', 'up', 'down'].includes(payload.type)) {
65
+ if (typeof payload.button === 'string' && !validButtons.includes(payload.button)) {
66
+ throw new Error(
67
+ `The button option must be one of the following values when provided: ${validButtons.join(
68
+ ', ',
69
+ )}.`,
70
+ );
71
+ }
72
+ }
73
+
74
+ return true;
75
+ }
76
+
77
+ export function sendMousePlugin(): TestRunnerPlugin<SendMousePayload> {
78
+ return {
79
+ name: 'send-mouse-command',
80
+ async executeCommand({ command, payload, session }): Promise<any> {
81
+ if (command === 'send-mouse') {
82
+ if (!payload || !isSendMousePayload(payload)) {
83
+ throw new Error('You must provide a `SendMousePayload` object');
84
+ }
85
+
86
+ // handle specific behavior for playwright
87
+ if (session.browser.type === 'playwright') {
88
+ const page = (session.browser as PlaywrightLauncher).getPage(session.id);
89
+ switch (payload.type) {
90
+ case 'move':
91
+ await page.mouse.move(payload.position[0], payload.position[1]);
92
+ return true;
93
+ case 'click':
94
+ await page.mouse.click(payload.position[0], payload.position[1], {
95
+ button: payload.button,
96
+ });
97
+ return true;
98
+ case 'down':
99
+ await page.mouse.down({ button: payload.button });
100
+ return true;
101
+ case 'up':
102
+ await page.mouse.up({ button: payload.button });
103
+ return true;
104
+ }
105
+ }
106
+
107
+ // handle specific behavior for puppeteer
108
+ if (session.browser.type === 'puppeteer') {
109
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
110
+ switch (payload.type) {
111
+ case 'move':
112
+ await page.mouse.move(payload.position[0], payload.position[1]);
113
+ return true;
114
+ case 'click':
115
+ await page.mouse.click(payload.position[0], payload.position[1], {
116
+ button: payload.button,
117
+ });
118
+ return true;
119
+ case 'down':
120
+ await page.mouse.down({ button: payload.button });
121
+ return true;
122
+ case 'up':
123
+ await page.mouse.up({ button: payload.button });
124
+ return true;
125
+ }
126
+ }
127
+
128
+ // handle specific behavior for webdriver
129
+ if (session.browser.type === 'webdriver') {
130
+ const page = session.browser as WebdriverLauncher;
131
+ switch (payload.type) {
132
+ case 'move':
133
+ await page.sendMouseMove(session.id, payload.position[0], payload.position[1]);
134
+ return true;
135
+ case 'click':
136
+ await page.sendMouseClick(
137
+ session.id,
138
+ payload.position[0],
139
+ payload.position[1],
140
+ payload.button,
141
+ );
142
+ return true;
143
+ case 'down':
144
+ await page.sendMouseDown(session.id, payload.button);
145
+ return true;
146
+ case 'up':
147
+ await page.sendMouseUp(session.id, payload.button);
148
+ return true;
149
+ }
150
+ }
151
+
152
+ // you might not be able to support all browser launchers
153
+ throw new Error(`Sending mouse is not supported for browser type ${session.browser.type}.`);
154
+ }
155
+
156
+ if (command === 'reset-mouse') {
157
+ // handle specific behavior for playwright
158
+ if (session.browser.type === 'playwright') {
159
+ const page = (session.browser as PlaywrightLauncher).getPage(session.id);
160
+ await page.mouse.up({ button: 'left' });
161
+ await page.mouse.up({ button: 'middle' });
162
+ await page.mouse.up({ button: 'right' });
163
+ await page.mouse.move(0, 0);
164
+ return true;
165
+ }
166
+
167
+ // handle specific behavior for puppeteer
168
+ if (session.browser.type === 'puppeteer') {
169
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
170
+ await page.mouse.up({ button: 'left' });
171
+ await page.mouse.up({ button: 'middle' });
172
+ await page.mouse.up({ button: 'right' });
173
+ await page.mouse.move(0, 0);
174
+ return true;
175
+ }
176
+
177
+ // handle specific behavior for webdriver
178
+ if (session.browser.type === 'webdriver') {
179
+ const page = session.browser as WebdriverLauncher;
180
+ await page.resetMouse(session.id);
181
+ return true;
182
+ }
183
+
184
+ // you might not be able to support all browser launchers
185
+ throw new Error(
186
+ `Resetting mouse is not supported for browser type ${session.browser.type}.`,
187
+ );
188
+ }
189
+ },
190
+ };
191
+ }
@@ -0,0 +1,25 @@
1
+ import { TestRunnerPlugin } from '@web/test-runner-core';
2
+ import type { ChromeLauncher } from '@web/test-runner-chrome';
3
+
4
+ export function setUserAgentPlugin(): TestRunnerPlugin {
5
+ return {
6
+ name: 'set-user-agent-command',
7
+
8
+ async executeCommand({ command, payload, session }) {
9
+ if (command === 'set-user-agent') {
10
+ if (typeof payload !== 'string') {
11
+ throw new Error('You must provide a user agent as a string');
12
+ }
13
+
14
+ if (session.browser.type === 'puppeteer') {
15
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
16
+
17
+ await page.setUserAgent(payload);
18
+ return true;
19
+ }
20
+
21
+ throw new Error('set user agent is only supported on puppeteer.');
22
+ }
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,59 @@
1
+ import { TestRunnerPlugin } from '@web/test-runner-core';
2
+ import type { ChromeLauncher } from '@web/test-runner-chrome';
3
+ import type { PlaywrightLauncher } from '@web/test-runner-playwright';
4
+
5
+ export interface Viewport {
6
+ width: number;
7
+ height: number;
8
+
9
+ // puppeteer only
10
+ deviceScaleFactor?: number;
11
+ isLandscape?: boolean;
12
+ }
13
+
14
+ function isObject(payload: unknown): payload is Record<string, unknown> {
15
+ return payload != null && typeof payload === 'object';
16
+ }
17
+
18
+ function isViewportObject(payload: unknown): payload is { width: number; height: number } {
19
+ if (!isObject(payload)) throw new Error('You must provide a viewport object');
20
+ if (payload.height == null) throw new Error('You must provide a viewport width');
21
+ if (payload.width == null) throw new Error('You must provide a viewport width');
22
+ if (typeof payload.height !== 'number') throw new Error('Viewport height must be a number');
23
+ if (typeof payload.height !== 'number') throw new Error('Viewport width must be a number');
24
+ if (payload.isMobile != null)
25
+ throw new Error('isMobile cannot be used because it triggers a page reload');
26
+ if (payload.hasTouch != null)
27
+ throw new Error('hasTouch cannot be used because it triggers a page reload');
28
+ return true;
29
+ }
30
+
31
+ export function setViewportPlugin(): TestRunnerPlugin {
32
+ return {
33
+ name: 'set-viewport-command',
34
+
35
+ async executeCommand({ command, payload, session }) {
36
+ if (command === 'set-viewport') {
37
+ if (!isViewportObject(payload)) {
38
+ throw new Error('You must provide a viewport object');
39
+ }
40
+
41
+ if (session.browser.type === 'puppeteer') {
42
+ const page = (session.browser as ChromeLauncher).getPage(session.id);
43
+ await page.setViewport(payload);
44
+ return true;
45
+ }
46
+
47
+ if (session.browser.type === 'playwright') {
48
+ const page = (session.browser as PlaywrightLauncher).getPage(session.id);
49
+ await page.setViewportSize(payload);
50
+ return true;
51
+ }
52
+
53
+ throw new Error(
54
+ `Setting viewport is not supported for browser type ${session.browser.type}.`,
55
+ );
56
+ }
57
+ },
58
+ };
59
+ }