@web/test-runner-commands 0.6.2 → 0.6.5
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/browser/commands.mjs +6 -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/snapshotPlugin.d.ts.map +1 -1
- package/dist/snapshotPlugin.js +44 -5
- package/dist/snapshotPlugin.js.map +1 -1
- package/package.json +3 -3
- package/src/selectOptionPlugin.ts +122 -0
- package/src/snapshotPlugin.ts +54 -6
package/browser/commands.mjs
CHANGED
|
@@ -69,6 +69,10 @@ export function sendKeys(options) {
|
|
|
69
69
|
return executeServerCommand('send-keys', options);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export function selectOption(options) {
|
|
73
|
+
return executeServerCommand('select-option', options);
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
export function sendMouse(options) {
|
|
73
77
|
return executeServerCommand('send-mouse', options);
|
|
74
78
|
}
|
|
@@ -211,6 +215,8 @@ export async function compareSnapshot({ name, content }) {
|
|
|
211
215
|
`New:\n${content}`,
|
|
212
216
|
);
|
|
213
217
|
}
|
|
218
|
+
} else if (currentSnapshot === content) {
|
|
219
|
+
return;
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
await saveSnapshot({ name, content });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TestRunnerPlugin } from '@web/test-runner-core';
|
|
2
|
+
declare type SelectByValuePayload = {
|
|
3
|
+
selector: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
declare type SelectByLabelPayload = {
|
|
7
|
+
selector: string;
|
|
8
|
+
label: string;
|
|
9
|
+
};
|
|
10
|
+
declare type SelectMultipleValuesPayload = {
|
|
11
|
+
selector: string;
|
|
12
|
+
values: string[];
|
|
13
|
+
};
|
|
14
|
+
export declare type SelectOptionPayload = SelectByLabelPayload | SelectByValuePayload | SelectMultipleValuesPayload;
|
|
15
|
+
export declare function selectOptionPlugin(): TestRunnerPlugin<SelectOptionPayload>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=selectOptionPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptionPlugin.d.ts","sourceRoot":"","sources":["../src/selectOptionPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,aAAK,oBAAoB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAChE,aAAK,oBAAoB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAChE,aAAK,2BAA2B,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE1E,oBAAY,mBAAmB,GAC3B,oBAAoB,GACpB,oBAAoB,GACpB,2BAA2B,CAAC;AA8ChC,wBAAgB,kBAAkB,IAAI,gBAAgB,CAAC,mBAAmB,CAAC,CAgE1E"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectOptionPlugin = void 0;
|
|
4
|
+
function isObject(payload) {
|
|
5
|
+
return payload != null && typeof payload === 'object';
|
|
6
|
+
}
|
|
7
|
+
function isSelectOptionPayload(payload) {
|
|
8
|
+
const validOptions = ['value', 'label', 'values'];
|
|
9
|
+
if (!isObject(payload))
|
|
10
|
+
throw new Error('You must provide a `SelectOptionPayload` object');
|
|
11
|
+
if (!payload.selector || typeof payload.selector !== 'string') {
|
|
12
|
+
throw new Error(`You must provide a selector representing the select you wish to locate`);
|
|
13
|
+
}
|
|
14
|
+
const numberOfValidOptions = Object.keys(payload).filter(key => validOptions.includes(key)).length;
|
|
15
|
+
const unknownOptions = Object.keys(payload).filter(key => ![...validOptions, 'selector'].includes(key));
|
|
16
|
+
if (numberOfValidOptions === 0)
|
|
17
|
+
throw new Error(`You must provide one of the following properties to pass to the browser runner: ${validOptions.join(', ')}.`);
|
|
18
|
+
if (unknownOptions.length > 0) {
|
|
19
|
+
throw new Error('Unknown options `' + unknownOptions.join(', ') + '` present.');
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function isValuePayload(payload) {
|
|
24
|
+
return 'selector' in payload && 'value' in payload;
|
|
25
|
+
}
|
|
26
|
+
function isLabelPayload(payload) {
|
|
27
|
+
return 'selector' in payload && 'label' in payload;
|
|
28
|
+
}
|
|
29
|
+
function isMultiplePayload(payload) {
|
|
30
|
+
return 'selector' in payload && 'values' in payload;
|
|
31
|
+
}
|
|
32
|
+
function selectOptionPlugin() {
|
|
33
|
+
return {
|
|
34
|
+
name: 'select-option-command',
|
|
35
|
+
async executeCommand({ command, payload, session }) {
|
|
36
|
+
if (command === 'select-option') {
|
|
37
|
+
if (!isSelectOptionPayload(payload) || !payload) {
|
|
38
|
+
throw new Error('You must provide a `SelectOptionPayload` object');
|
|
39
|
+
}
|
|
40
|
+
// handle specific behavior for playwright
|
|
41
|
+
if (session.browser.type === 'playwright') {
|
|
42
|
+
const page = session.browser.getPage(session.id);
|
|
43
|
+
if (isValuePayload(payload)) {
|
|
44
|
+
const { selector, value } = payload;
|
|
45
|
+
await page.locator(selector).selectOption(value);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
else if (isLabelPayload(payload)) {
|
|
49
|
+
const { selector, label } = payload;
|
|
50
|
+
await page.locator(selector).selectOption({ label });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
else if (isMultiplePayload(payload)) {
|
|
54
|
+
const { selector, values } = payload;
|
|
55
|
+
await page.locator(selector).selectOption([...values]);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// handle specific behavior for puppeteer
|
|
60
|
+
if (session.browser.type === 'puppeteer') {
|
|
61
|
+
const page = session.browser.getPage(session.id);
|
|
62
|
+
if (isValuePayload(payload)) {
|
|
63
|
+
const { selector, value } = payload;
|
|
64
|
+
await page.select(selector, value);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
else if (isMultiplePayload(payload)) {
|
|
68
|
+
const { selector, values } = payload;
|
|
69
|
+
await page.select(selector, ...values);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
throw new Error(`Puppeteer only supports selection of an option by its value(s):
|
|
74
|
+
https://pptr.dev/next/api/puppeteer.page.select#parameters
|
|
75
|
+
`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// handle specific behavior for webdriver
|
|
79
|
+
if (session.browser.type === 'webdriver') {
|
|
80
|
+
throw new Error(`Selecting an option via a browser driver command is not currently implemented in WebDriver yet.
|
|
81
|
+
https://www.selenium.dev/documentation/webdriver/elements/select_lists/
|
|
82
|
+
|
|
83
|
+
When using WebDriver, the current recommended (September 2022) approach is to:
|
|
84
|
+
- locate your select element (via querySelector or querySelectorAll)
|
|
85
|
+
- focus the element
|
|
86
|
+
- use sendKeys() to type the label of the option you wish to select
|
|
87
|
+
- use select.dispatchEvent(new Event('change')) to trigger a change event on the select.
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
// you might not be able to support all browser launchers
|
|
91
|
+
throw new Error(`Selection of select element options is not supported for browser type ${session.browser.type}.`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
exports.selectOptionPlugin = selectOptionPlugin;
|
|
97
|
+
//# sourceMappingURL=selectOptionPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectOptionPlugin.js","sourceRoot":"","sources":["../src/selectOptionPlugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotPlugin.d.ts","sourceRoot":"","sources":["../src/snapshotPlugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshotPlugin.d.ts","sourceRoot":"","sources":["../src/snapshotPlugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAyBzD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAuHD,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAoD9E"}
|
package/dist/snapshotPlugin.js
CHANGED
|
@@ -43,6 +43,7 @@ function getSnapshotPath(testFile) {
|
|
|
43
43
|
class SnapshotStore {
|
|
44
44
|
constructor() {
|
|
45
45
|
this.snapshots = new Map();
|
|
46
|
+
this.sessionToSnapshotPath = new Map();
|
|
46
47
|
this.readOperations = new Map();
|
|
47
48
|
}
|
|
48
49
|
async get(testFilePath) {
|
|
@@ -52,9 +53,10 @@ class SnapshotStore {
|
|
|
52
53
|
// something else is already reading, wait for it
|
|
53
54
|
await ((_a = this.readOperations.get(snapshotPath)) === null || _a === void 0 ? void 0 : _a.promise);
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
+
const cachedContent = this.snapshots.get(snapshotPath);
|
|
57
|
+
if (cachedContent) {
|
|
56
58
|
// return from cache
|
|
57
|
-
return
|
|
59
|
+
return cachedContent;
|
|
58
60
|
}
|
|
59
61
|
const promiseObj = { resolve: () => { }, promise: Promise.resolve() };
|
|
60
62
|
promiseObj.promise = new Promise(resolve => {
|
|
@@ -71,7 +73,7 @@ class SnapshotStore {
|
|
|
71
73
|
this.readOperations.delete(snapshotPath);
|
|
72
74
|
return content;
|
|
73
75
|
}
|
|
74
|
-
async saveSnapshot(testFilePath, name, updatedSnapshot) {
|
|
76
|
+
async saveSnapshot(sessionId, testFilePath, name, updatedSnapshot) {
|
|
75
77
|
const snapshotPath = getSnapshotPath(testFilePath);
|
|
76
78
|
const nameStr = JSON.stringify(name);
|
|
77
79
|
const startMarker = `snapshots[${nameStr}]`;
|
|
@@ -79,7 +81,7 @@ class SnapshotStore {
|
|
|
79
81
|
const replacement = updatedSnapshot
|
|
80
82
|
? `${startMarker} = \n\`${updatedSnapshot}\`;\n${endMarker}`
|
|
81
83
|
: '';
|
|
82
|
-
const content = await this.get(
|
|
84
|
+
const content = await this.get(testFilePath);
|
|
83
85
|
let updatedContent;
|
|
84
86
|
const startIndex = content.indexOf(startMarker);
|
|
85
87
|
if (startIndex !== -1) {
|
|
@@ -96,7 +98,21 @@ class SnapshotStore {
|
|
|
96
98
|
// add new snapshot
|
|
97
99
|
updatedContent = `${content}${replacement}`;
|
|
98
100
|
}
|
|
101
|
+
if (updatedContent === content) {
|
|
102
|
+
// snapshot did not actually change, avoid marking snapshot as dirty
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.sessionToSnapshotPath.set(sessionId, snapshotPath);
|
|
99
106
|
this.snapshots.set(snapshotPath, updatedContent);
|
|
107
|
+
}
|
|
108
|
+
getSnapshotPathForSession(sessionId) {
|
|
109
|
+
return this.sessionToSnapshotPath.get(sessionId);
|
|
110
|
+
}
|
|
111
|
+
async writeSnapshot(snapshotPath) {
|
|
112
|
+
const updatedContent = this.snapshots.get(snapshotPath);
|
|
113
|
+
if (!updatedContent) {
|
|
114
|
+
throw new Error('Unexpected error while writing snapshots, could not find snapshot content.');
|
|
115
|
+
}
|
|
100
116
|
if (updatedContent.includes('/* end snapshot')) {
|
|
101
117
|
// update or create snapshot
|
|
102
118
|
const fileDir = path_1.default.dirname(snapshotPath);
|
|
@@ -114,8 +130,31 @@ class SnapshotStore {
|
|
|
114
130
|
function snapshotPlugin(config) {
|
|
115
131
|
const updateSnapshots = config && config.updateSnapshots;
|
|
116
132
|
const snapshots = new SnapshotStore();
|
|
133
|
+
const writePromises = new Set();
|
|
117
134
|
return {
|
|
118
135
|
name: 'file-commands',
|
|
136
|
+
serverStart({ webSockets }) {
|
|
137
|
+
webSockets.on('message', async ({ data }) => {
|
|
138
|
+
const { type, sessionId } = data;
|
|
139
|
+
if (type === 'wtr-session-finished') {
|
|
140
|
+
if (typeof sessionId !== 'string') {
|
|
141
|
+
throw new Error('Missing session id in wtr-session-finished event');
|
|
142
|
+
}
|
|
143
|
+
const snapshotPath = snapshots.getSnapshotPathForSession(sessionId);
|
|
144
|
+
if (!snapshotPath) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const writePromise = snapshots.writeSnapshot(snapshotPath);
|
|
148
|
+
writePromises.add(writePromise);
|
|
149
|
+
await writePromise;
|
|
150
|
+
writePromises.delete(writePromise);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
async serverStop() {
|
|
155
|
+
// ensure all write operations are finished
|
|
156
|
+
await Promise.all([...writePromises]);
|
|
157
|
+
},
|
|
119
158
|
async executeCommand({ command, payload, session }) {
|
|
120
159
|
if (command === 'get-snapshot-config') {
|
|
121
160
|
return { updateSnapshots };
|
|
@@ -128,7 +167,7 @@ function snapshotPlugin(config) {
|
|
|
128
167
|
if (!isSaveSnapshotPayload(payload)) {
|
|
129
168
|
throw new Error('Invalid save snapshot payload');
|
|
130
169
|
}
|
|
131
|
-
await snapshots.saveSnapshot(session.testFile, payload.name, payload.content);
|
|
170
|
+
await snapshots.saveSnapshot(session.id, session.testFile, payload.name, payload.content);
|
|
132
171
|
return true;
|
|
133
172
|
}
|
|
134
173
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotPlugin.js","sourceRoot":"","sources":["../src/snapshotPlugin.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"snapshotPlugin.js","sourceRoot":"","sources":["../src/snapshotPlugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@web/test-runner-commands",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"commands"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@web/test-runner-core": "^0.10.
|
|
51
|
+
"@web/test-runner-core": "^0.10.27",
|
|
52
52
|
"mkdirp": "^1.0.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@web/test-runner-chrome": "^0.10.2",
|
|
56
56
|
"@web/test-runner-playwright": "^0.8.9",
|
|
57
|
-
"@web/test-runner-webdriver": "^0.5.
|
|
57
|
+
"@web/test-runner-webdriver": "^0.5.1",
|
|
58
58
|
"mocha": "^8.2.1"
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -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
|
+
}
|
package/src/snapshotPlugin.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
2
|
import { TestRunnerPlugin } from '@web/test-runner-core';
|
|
3
|
+
import { ServerStartParams } from '@web/dev-server-core';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import { promisify } from 'util';
|
|
@@ -46,6 +47,7 @@ function getSnapshotPath(testFile: string) {
|
|
|
46
47
|
|
|
47
48
|
class SnapshotStore {
|
|
48
49
|
private snapshots = new Map<string, string>();
|
|
50
|
+
private sessionToSnapshotPath = new Map<string, string>();
|
|
49
51
|
private readOperations = new Map<string, { promise: Promise<void>; resolve: () => void }>();
|
|
50
52
|
|
|
51
53
|
async get(testFilePath: string): Promise<string> {
|
|
@@ -56,9 +58,10 @@ class SnapshotStore {
|
|
|
56
58
|
await this.readOperations.get(snapshotPath)?.promise;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
const cachedContent = this.snapshots.get(snapshotPath);
|
|
62
|
+
if (cachedContent) {
|
|
60
63
|
// return from cache
|
|
61
|
-
return
|
|
64
|
+
return cachedContent;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const promiseObj = { resolve: () => {}, promise: Promise.resolve() };
|
|
@@ -66,7 +69,6 @@ class SnapshotStore {
|
|
|
66
69
|
promiseObj.resolve = resolve;
|
|
67
70
|
});
|
|
68
71
|
this.readOperations.set(testFilePath, promiseObj);
|
|
69
|
-
|
|
70
72
|
// store in cache
|
|
71
73
|
const content = (await fileExists(snapshotPath))
|
|
72
74
|
? await readFile(snapshotPath, 'utf-8')
|
|
@@ -79,7 +81,12 @@ class SnapshotStore {
|
|
|
79
81
|
return content;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
async saveSnapshot(
|
|
84
|
+
async saveSnapshot(
|
|
85
|
+
sessionId: string,
|
|
86
|
+
testFilePath: string,
|
|
87
|
+
name: string,
|
|
88
|
+
updatedSnapshot: string,
|
|
89
|
+
) {
|
|
83
90
|
const snapshotPath = getSnapshotPath(testFilePath);
|
|
84
91
|
const nameStr = JSON.stringify(name);
|
|
85
92
|
const startMarker = `snapshots[${nameStr}]`;
|
|
@@ -88,7 +95,7 @@ class SnapshotStore {
|
|
|
88
95
|
? `${startMarker} = \n\`${updatedSnapshot}\`;\n${endMarker}`
|
|
89
96
|
: '';
|
|
90
97
|
|
|
91
|
-
const content = await this.get(
|
|
98
|
+
const content = await this.get(testFilePath);
|
|
92
99
|
let updatedContent: string;
|
|
93
100
|
|
|
94
101
|
const startIndex = content.indexOf(startMarker);
|
|
@@ -107,8 +114,24 @@ class SnapshotStore {
|
|
|
107
114
|
// add new snapshot
|
|
108
115
|
updatedContent = `${content}${replacement}`;
|
|
109
116
|
}
|
|
117
|
+
if (updatedContent === content) {
|
|
118
|
+
// snapshot did not actually change, avoid marking snapshot as dirty
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
110
121
|
|
|
122
|
+
this.sessionToSnapshotPath.set(sessionId, snapshotPath);
|
|
111
123
|
this.snapshots.set(snapshotPath, updatedContent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getSnapshotPathForSession(sessionId: string) {
|
|
127
|
+
return this.sessionToSnapshotPath.get(sessionId);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async writeSnapshot(snapshotPath: string) {
|
|
131
|
+
const updatedContent = this.snapshots.get(snapshotPath);
|
|
132
|
+
if (!updatedContent) {
|
|
133
|
+
throw new Error('Unexpected error while writing snapshots, could not find snapshot content.');
|
|
134
|
+
}
|
|
112
135
|
if (updatedContent.includes('/* end snapshot')) {
|
|
113
136
|
// update or create snapshot
|
|
114
137
|
const fileDir = path.dirname(snapshotPath);
|
|
@@ -130,10 +153,35 @@ export interface SnapshotPluginConfig {
|
|
|
130
153
|
export function snapshotPlugin(config?: SnapshotPluginConfig): TestRunnerPlugin {
|
|
131
154
|
const updateSnapshots = config && config.updateSnapshots;
|
|
132
155
|
const snapshots = new SnapshotStore();
|
|
156
|
+
const writePromises = new Set<Promise<void>>();
|
|
133
157
|
|
|
134
158
|
return {
|
|
135
159
|
name: 'file-commands',
|
|
136
160
|
|
|
161
|
+
serverStart({ webSockets }: ServerStartParams) {
|
|
162
|
+
webSockets!.on('message', async ({ data }) => {
|
|
163
|
+
const { type, sessionId } = data;
|
|
164
|
+
if (type === 'wtr-session-finished') {
|
|
165
|
+
if (typeof sessionId !== 'string') {
|
|
166
|
+
throw new Error('Missing session id in wtr-session-finished event');
|
|
167
|
+
}
|
|
168
|
+
const snapshotPath = snapshots.getSnapshotPathForSession(sessionId);
|
|
169
|
+
if (!snapshotPath) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const writePromise = snapshots.writeSnapshot(snapshotPath);
|
|
173
|
+
writePromises.add(writePromise);
|
|
174
|
+
await writePromise;
|
|
175
|
+
writePromises.delete(writePromise);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async serverStop() {
|
|
181
|
+
// ensure all write operations are finished
|
|
182
|
+
await Promise.all([...writePromises]);
|
|
183
|
+
},
|
|
184
|
+
|
|
137
185
|
async executeCommand({ command, payload, session }) {
|
|
138
186
|
if (command === 'get-snapshot-config') {
|
|
139
187
|
return { updateSnapshots };
|
|
@@ -149,7 +197,7 @@ export function snapshotPlugin(config?: SnapshotPluginConfig): TestRunnerPlugin
|
|
|
149
197
|
throw new Error('Invalid save snapshot payload');
|
|
150
198
|
}
|
|
151
199
|
|
|
152
|
-
await snapshots.saveSnapshot(session.testFile, payload.name, payload.content);
|
|
200
|
+
await snapshots.saveSnapshot(session.id, session.testFile, payload.name, payload.content);
|
|
153
201
|
return true;
|
|
154
202
|
}
|
|
155
203
|
},
|