@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,205 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import { TestRunnerPlugin } from '@web/test-runner-core';
3
+ import { ServerStartParams } from '@web/dev-server-core';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import { promisify } from 'util';
7
+ import mkdirp from 'mkdirp';
8
+
9
+ const readFile = promisify(fs.readFile);
10
+ const writeFile = promisify(fs.writeFile);
11
+ const unlink = promisify(fs.unlink);
12
+ const access = promisify(fs.access);
13
+
14
+ async function fileExists(filePath: string) {
15
+ try {
16
+ await access(filePath);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ function isObject(payload: unknown): payload is Record<string, unknown> {
24
+ return payload != null && typeof payload === 'object';
25
+ }
26
+
27
+ export interface SaveSnapshotPayload {
28
+ name: string;
29
+ content: string;
30
+ }
31
+
32
+ function isSaveSnapshotPayload(payload: unknown): payload is SaveSnapshotPayload {
33
+ if (!isObject(payload)) throw new Error('You must provide a payload object');
34
+ if (typeof payload.name !== 'string') throw new Error('You must provide a path option');
35
+ if (payload.content !== undefined && typeof payload.content !== 'string')
36
+ throw new Error('You must provide a content option');
37
+ return true;
38
+ }
39
+
40
+ function getSnapshotPath(testFile: string) {
41
+ const testDir = path.dirname(testFile);
42
+ const testFileName = path.basename(testFile);
43
+ const ext = path.extname(testFileName);
44
+ const fileWithoutExt = testFileName.substring(0, testFileName.length - ext.length);
45
+ return path.join(testDir, '__snapshots__', `${fileWithoutExt}.snap.js`);
46
+ }
47
+
48
+ class SnapshotStore {
49
+ private snapshots = new Map<string, string>();
50
+ private sessionToSnapshotPath = new Map<string, string>();
51
+ private readOperations = new Map<string, { promise: Promise<void>; resolve: () => void }>();
52
+
53
+ async get(testFilePath: string): Promise<string> {
54
+ const snapshotPath = getSnapshotPath(testFilePath);
55
+
56
+ if (this.readOperations.has(snapshotPath)) {
57
+ // something else is already reading, wait for it
58
+ await this.readOperations.get(snapshotPath)?.promise;
59
+ }
60
+
61
+ const cachedContent = this.snapshots.get(snapshotPath);
62
+ if (cachedContent) {
63
+ // return from cache
64
+ return cachedContent;
65
+ }
66
+
67
+ const promiseObj = { resolve: () => {}, promise: Promise.resolve() };
68
+ promiseObj.promise = new Promise<void>(resolve => {
69
+ promiseObj.resolve = resolve;
70
+ });
71
+ this.readOperations.set(testFilePath, promiseObj);
72
+ // store in cache
73
+ const content = (await fileExists(snapshotPath))
74
+ ? await readFile(snapshotPath, 'utf-8')
75
+ : '/* @web/test-runner snapshot v1 */\nexport const snapshots = {};\n\n';
76
+ this.snapshots.set(snapshotPath, content);
77
+
78
+ // resolve read promise to let others who are waiting continue
79
+ this.readOperations.get(snapshotPath)?.resolve();
80
+ this.readOperations.delete(snapshotPath);
81
+ return content;
82
+ }
83
+
84
+ async saveSnapshot(
85
+ sessionId: string,
86
+ testFilePath: string,
87
+ name: string,
88
+ updatedSnapshot: string,
89
+ ) {
90
+ const snapshotPath = getSnapshotPath(testFilePath);
91
+ const nameStr = JSON.stringify(name);
92
+ const startMarker = `snapshots[${nameStr}]`;
93
+ const endMarker = `/* end snapshot ${name} */\n\n`;
94
+ const replacement = updatedSnapshot
95
+ ? `${startMarker} = \n\`${updatedSnapshot}\`;\n${endMarker}`
96
+ : '';
97
+
98
+ const content = await this.get(testFilePath);
99
+ let updatedContent: string;
100
+
101
+ const startIndex = content.indexOf(startMarker);
102
+ if (startIndex !== -1) {
103
+ // replace existing snapshot
104
+ const endIndex = content.indexOf(endMarker);
105
+ if (endIndex === -1) {
106
+ throw new Error('Missing snapshot end marker');
107
+ }
108
+
109
+ const beforeReplace = content.substring(0, startIndex);
110
+ const afterReplace = content.substring(endIndex + endMarker.length);
111
+
112
+ updatedContent = `${beforeReplace}${replacement}${afterReplace}`;
113
+ } else {
114
+ // add new snapshot
115
+ updatedContent = `${content}${replacement}`;
116
+ }
117
+ if (updatedContent === content) {
118
+ // snapshot did not actually change, avoid marking snapshot as dirty
119
+ return;
120
+ }
121
+
122
+ this.sessionToSnapshotPath.set(sessionId, snapshotPath);
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
+ }
135
+ if (updatedContent.includes('/* end snapshot')) {
136
+ // update or create snapshot
137
+ const fileDir = path.dirname(snapshotPath);
138
+ await mkdirp(fileDir);
139
+ await writeFile(snapshotPath, updatedContent);
140
+ } else {
141
+ // snapshot file is empty, remove it
142
+ if (await fileExists(snapshotPath)) {
143
+ await unlink(snapshotPath);
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ export interface SnapshotPluginConfig {
150
+ updateSnapshots?: boolean;
151
+ }
152
+
153
+ export function snapshotPlugin(config?: SnapshotPluginConfig): TestRunnerPlugin {
154
+ const updateSnapshots = config && config.updateSnapshots;
155
+ const snapshots = new SnapshotStore();
156
+ const writePromises = new Set<Promise<void>>();
157
+
158
+ return {
159
+ name: 'file-commands',
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
+
185
+ async executeCommand({ command, payload, session }) {
186
+ if (command === 'get-snapshot-config') {
187
+ return { updateSnapshots };
188
+ }
189
+
190
+ if (command === 'get-snapshots') {
191
+ const content = await snapshots.get(session.testFile);
192
+ return { content };
193
+ }
194
+
195
+ if (command === 'save-snapshot') {
196
+ if (!isSaveSnapshotPayload(payload)) {
197
+ throw new Error('Invalid save snapshot payload');
198
+ }
199
+
200
+ await snapshots.saveSnapshot(session.id, session.testFile, payload.name, payload.content);
201
+ return true;
202
+ }
203
+ },
204
+ };
205
+ }