@haibun/web-playwright 1.43.0 → 1.44.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/build/BrowserFactory.d.ts +15 -27
- package/build/BrowserFactory.d.ts.map +1 -1
- package/build/BrowserFactory.js +71 -50
- package/build/BrowserFactory.js.map +1 -1
- package/build/PlaywrightEvents.d.ts +3 -3
- package/build/PlaywrightEvents.d.ts.map +1 -1
- package/build/PlaywrightEvents.js +17 -13
- package/build/PlaywrightEvents.js.map +1 -1
- package/build/monitor/XXlogToMonitor.d.ts +8 -0
- package/build/monitor/XXlogToMonitor.d.ts.map +1 -0
- package/build/monitor/XXlogToMonitor.js +151 -0
- package/build/monitor/XXlogToMonitor.js.map +1 -0
- package/build/monitor/controls.d.ts +3 -0
- package/build/monitor/controls.d.ts.map +1 -0
- package/build/monitor/controls.js +318 -0
- package/build/monitor/controls.js.map +1 -0
- package/build/monitor/disclosureJson.d.ts +3 -0
- package/build/monitor/disclosureJson.d.ts.map +1 -0
- package/build/monitor/disclosureJson.js +121 -0
- package/build/monitor/disclosureJson.js.map +1 -0
- package/build/monitor/mermaidDiagram.d.ts +22 -0
- package/build/monitor/mermaidDiagram.d.ts.map +1 -0
- package/build/monitor/mermaidDiagram.js +199 -0
- package/build/monitor/mermaidDiagram.js.map +1 -0
- package/build/monitor/messages.d.ts +18 -0
- package/build/monitor/messages.d.ts.map +1 -0
- package/build/monitor/messages.js +303 -0
- package/build/monitor/messages.js.map +1 -0
- package/build/monitor/monitor.d.ts +16 -0
- package/build/monitor/monitor.d.ts.map +1 -0
- package/build/monitor/monitor.js +61 -0
- package/build/monitor/monitor.js.map +1 -0
- package/build/monitor/monitorHandler.d.ts +12 -0
- package/build/monitor/monitorHandler.d.ts.map +1 -0
- package/build/monitor/monitorHandler.js +89 -0
- package/build/monitor/monitorHandler.js.map +1 -0
- package/build/rest-playwright.d.ts +60 -2
- package/build/rest-playwright.d.ts.map +1 -1
- package/build/rest-playwright.js +170 -51
- package/build/rest-playwright.js.map +1 -1
- package/build/web-playwright.d.ts +142 -54
- package/build/web-playwright.d.ts.map +1 -1
- package/build/web-playwright.js +327 -110
- package/build/web-playwright.js.map +1 -1
- package/package.json +4 -3
package/build/web-playwright.js
CHANGED
|
@@ -1,16 +1,86 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
2
3
|
import { OK, AStepper } from '@haibun/core/build/lib/defs.js';
|
|
3
4
|
import { WEB_PAGE, WEB_CONTROL } from '@haibun/core/build/lib/domain-types.js';
|
|
4
|
-
import { BrowserFactory } from './BrowserFactory.js';
|
|
5
|
-
import { actionNotOK, getStepperOption, boolOrError, intOrError, stringOrError, findStepperFromOption, sleep } from '@haibun/core/build/lib/util/index.js';
|
|
5
|
+
import { BrowserFactory, BROWSERS } from './BrowserFactory.js';
|
|
6
|
+
import { actionNotOK, getStepperOption, boolOrError, intOrError, stringOrError, findStepperFromOption, sleep, optionOrError } from '@haibun/core/build/lib/util/index.js';
|
|
7
|
+
import { EExecutionMessageType } from '@haibun/core/build/lib/interfaces/logger.js'; // Removed TArtifactMessageContext, added TMessageContext
|
|
6
8
|
import { EMediaTypes } from '@haibun/domain-storage/build/media-types.js';
|
|
7
|
-
import Logger from '@haibun/core/build/lib/Logger.js';
|
|
8
9
|
import { restSteps } from './rest-playwright.js';
|
|
10
|
+
import { createMonitorPageAndSubscriber, writeMonitor } from './monitor/monitorHandler.js';
|
|
11
|
+
import { rmSync } from 'fs';
|
|
12
|
+
export var EMonitoringTypes;
|
|
13
|
+
(function (EMonitoringTypes) {
|
|
14
|
+
EMonitoringTypes["MONITOR_ALL"] = "all";
|
|
15
|
+
EMonitoringTypes["MONITOR_EACH"] = "each";
|
|
16
|
+
})(EMonitoringTypes || (EMonitoringTypes = {}));
|
|
17
|
+
const cycles = (wp) => ({
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
+
async onFailure(result, step) {
|
|
20
|
+
if (wp.bf?.hasPage(wp.getWorld().tag, wp.tab)) {
|
|
21
|
+
await wp.captureFailureScreenshot(EExecutionMessageType.ON_FAILURE, step);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async startFeature() {
|
|
25
|
+
if (wp.monitor === EMonitoringTypes.MONITOR_EACH) {
|
|
26
|
+
await wp.createMonitor();
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async endFeature({ shouldClose = true, world }) {
|
|
30
|
+
// leave web server running if there was a failure and it's the last feature
|
|
31
|
+
if (shouldClose) {
|
|
32
|
+
for (const file of wp.downloaded) {
|
|
33
|
+
wp.getWorld().logger.debug(`removing ${JSON.stringify(file)}`);
|
|
34
|
+
rmSync(file);
|
|
35
|
+
}
|
|
36
|
+
if (wp.hasFactory) {
|
|
37
|
+
if (wp.captureVideo) {
|
|
38
|
+
const page = await wp.getPage();
|
|
39
|
+
const path = await wp.storage.getRelativePath(await page.video().path());
|
|
40
|
+
const artifact = { artifactType: 'video', path, runtimePath: await wp.runtimePath(world) };
|
|
41
|
+
const context = {
|
|
42
|
+
incident: EExecutionMessageType.FEATURE_END, // Use appropriate incident type
|
|
43
|
+
artifact,
|
|
44
|
+
tag: wp.getWorld().tag
|
|
45
|
+
};
|
|
46
|
+
wp.getWorld().logger.log('feature video', context);
|
|
47
|
+
}
|
|
48
|
+
// close the context, which closes any pages
|
|
49
|
+
if (wp.hasFactory) {
|
|
50
|
+
await wp.bf?.closeContext(wp.getWorld().tag);
|
|
51
|
+
}
|
|
52
|
+
await wp.bf?.close();
|
|
53
|
+
wp.bf = undefined;
|
|
54
|
+
wp.hasFactory = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (wp.monitor === EMonitoringTypes.MONITOR_EACH) {
|
|
58
|
+
await wp.callClosers();
|
|
59
|
+
await writeMonitor(wp.world, wp.storage, WebPlaywright.monitorPage);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async startExecution() {
|
|
63
|
+
if (wp.monitor === EMonitoringTypes.MONITOR_ALL) {
|
|
64
|
+
await wp.createMonitor();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
async endExecution() {
|
|
68
|
+
if (wp.monitor === EMonitoringTypes.MONITOR_ALL) {
|
|
69
|
+
await wp.callClosers();
|
|
70
|
+
await writeMonitor(wp.world, wp.storage, WebPlaywright.monitorPage);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
9
74
|
class WebPlaywright extends AStepper {
|
|
75
|
+
cycles = cycles(this);
|
|
10
76
|
static STORAGE = 'STORAGE';
|
|
11
77
|
static PERSISTENT_DIRECTORY = 'PERSISTENT_DIRECTORY';
|
|
12
78
|
requireDomains = [WEB_PAGE, WEB_CONTROL];
|
|
13
79
|
options = {
|
|
80
|
+
MONITOR: {
|
|
81
|
+
desc: `display a monitor with ongoing results (${EMonitoringTypes.MONITOR_ALL} or ${EMonitoringTypes.MONITOR_EACH})`,
|
|
82
|
+
parse: (input) => optionOrError(input, [EMonitoringTypes.MONITOR_ALL, EMonitoringTypes.MONITOR_EACH]),
|
|
83
|
+
},
|
|
14
84
|
HEADLESS: {
|
|
15
85
|
desc: 'run browsers without a window (true, false)',
|
|
16
86
|
parse: (input) => boolOrError(input),
|
|
@@ -21,7 +91,7 @@ class WebPlaywright extends AStepper {
|
|
|
21
91
|
},
|
|
22
92
|
[WebPlaywright.PERSISTENT_DIRECTORY]: {
|
|
23
93
|
desc: 'run browsers with a persistent directory (true or false)',
|
|
24
|
-
parse: (input) =>
|
|
94
|
+
parse: (input) => stringOrError(input),
|
|
25
95
|
},
|
|
26
96
|
ARGS: {
|
|
27
97
|
desc: 'pass arguments',
|
|
@@ -39,6 +109,7 @@ class WebPlaywright extends AStepper {
|
|
|
39
109
|
[WebPlaywright.STORAGE]: {
|
|
40
110
|
desc: 'Storage for output',
|
|
41
111
|
parse: (input) => stringOrError(input),
|
|
112
|
+
required: true
|
|
42
113
|
},
|
|
43
114
|
};
|
|
44
115
|
hasFactory = false;
|
|
@@ -49,28 +120,43 @@ class WebPlaywright extends AStepper {
|
|
|
49
120
|
withFrame;
|
|
50
121
|
downloaded = [];
|
|
51
122
|
captureVideo;
|
|
123
|
+
closers = [];
|
|
124
|
+
logElementError;
|
|
125
|
+
monitor;
|
|
126
|
+
static monitorPage;
|
|
127
|
+
userAgentPages = {};
|
|
128
|
+
apiUserAgent;
|
|
129
|
+
extraHTTPHeaders = {};
|
|
130
|
+
BROWSER_STATE_PATH = undefined;
|
|
131
|
+
expectedDownload;
|
|
52
132
|
async setWorld(world, steppers) {
|
|
53
133
|
await super.setWorld(world, steppers);
|
|
134
|
+
const args = [...(getStepperOption(this, 'ARGS', world.moduleOptions)?.split(';') || ''),]; //'--disable-gpu'
|
|
54
135
|
this.storage = findStepperFromOption(steppers, this, world.moduleOptions, WebPlaywright.STORAGE);
|
|
55
136
|
const headless = getStepperOption(this, 'HEADLESS', world.moduleOptions) === 'true' || !!process.env.CI;
|
|
56
137
|
const devtools = getStepperOption(this, 'DEVTOOLS', world.moduleOptions) === 'true';
|
|
57
|
-
|
|
58
|
-
|
|
138
|
+
if (devtools) {
|
|
139
|
+
args.concat(['--auto-open-devtools-for-tabs', '--devtools-flags=panel-network', '--remote-debugging-port=9223']);
|
|
140
|
+
}
|
|
141
|
+
this.monitor = getStepperOption(this, 'MONITOR', world.moduleOptions);
|
|
142
|
+
const persistentDirectory = getStepperOption(this, WebPlaywright.PERSISTENT_DIRECTORY, world.moduleOptions);
|
|
59
143
|
const defaultTimeout = parseInt(getStepperOption(this, 'TIMEOUT', world.moduleOptions)) || 30000;
|
|
60
|
-
this.captureVideo = getStepperOption(this, 'CAPTURE_VIDEO', world.moduleOptions);
|
|
144
|
+
this.captureVideo = getStepperOption(this, 'CAPTURE_VIDEO', world.moduleOptions) === 'true';
|
|
61
145
|
let recordVideo;
|
|
62
146
|
if (this.captureVideo) {
|
|
63
147
|
recordVideo = {
|
|
64
|
-
dir: await this.getCaptureDir('video')
|
|
148
|
+
dir: await this.getCaptureDir('video'),
|
|
65
149
|
};
|
|
66
150
|
}
|
|
151
|
+
const launchOptions = {
|
|
152
|
+
headless,
|
|
153
|
+
args,
|
|
154
|
+
devtools,
|
|
155
|
+
};
|
|
67
156
|
this.factoryOptions = {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
devtools,
|
|
72
|
-
},
|
|
73
|
-
recordVideo,
|
|
157
|
+
options: { recordVideo, },
|
|
158
|
+
browserType: BROWSERS.chromium,
|
|
159
|
+
launchOptions,
|
|
74
160
|
defaultTimeout,
|
|
75
161
|
persistentDirectory,
|
|
76
162
|
};
|
|
@@ -82,14 +168,14 @@ class WebPlaywright extends AStepper {
|
|
|
82
168
|
}
|
|
83
169
|
async getBrowserFactory() {
|
|
84
170
|
if (!this.hasFactory) {
|
|
85
|
-
this.bf = await BrowserFactory.getBrowserFactory(this.getWorld()
|
|
171
|
+
this.bf = await BrowserFactory.getBrowserFactory(this.getWorld(), this.factoryOptions);
|
|
86
172
|
this.hasFactory = true;
|
|
87
173
|
}
|
|
88
174
|
return this.bf;
|
|
89
175
|
}
|
|
90
|
-
async
|
|
91
|
-
const
|
|
92
|
-
return
|
|
176
|
+
async getExistingBrowserContext(tag = this.getWorld().tag) {
|
|
177
|
+
const browserContext = (await this.getBrowserFactory()).getExistingBrowserContextWithTag(tag);
|
|
178
|
+
return browserContext;
|
|
93
179
|
}
|
|
94
180
|
async getPage() {
|
|
95
181
|
const { tag } = this.getWorld();
|
|
@@ -108,42 +194,6 @@ class WebPlaywright extends AStepper {
|
|
|
108
194
|
this.withFrame = undefined;
|
|
109
195
|
return await f(page);
|
|
110
196
|
}
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
112
|
-
async onFailure(result, step) {
|
|
113
|
-
if (this.bf?.hasPage(this.getWorld().tag, this.tab)) {
|
|
114
|
-
await this.captureFailureScreenshot('failure', 'onFailure', step);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
async endFeature() {
|
|
118
|
-
// close the context, which closes any pages
|
|
119
|
-
if (this.hasFactory) {
|
|
120
|
-
if (this.captureVideo) {
|
|
121
|
-
const page = await this.getPage();
|
|
122
|
-
const path = await page.video().path();
|
|
123
|
-
const artifact = { type: 'video', path };
|
|
124
|
-
this.getWorld().logger.info('endFeature video', { artifact, topic: { event: 'summary', stage: 'endFeature' }, tag: this.getWorld().tag });
|
|
125
|
-
}
|
|
126
|
-
// await this.bf?.closeContext(this.getWorld().tag);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async close() {
|
|
130
|
-
// close the context, which closes any pages
|
|
131
|
-
if (this.hasFactory) {
|
|
132
|
-
await this.bf?.closeContext(this.getWorld().tag);
|
|
133
|
-
}
|
|
134
|
-
for (const file of this.downloaded) {
|
|
135
|
-
this.getWorld().logger.debug(`removing ${JSON.stringify(file)}`);
|
|
136
|
-
// rmSync(file);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// FIXME
|
|
140
|
-
async finish() {
|
|
141
|
-
if (this.hasFactory) {
|
|
142
|
-
await this.bf?.close();
|
|
143
|
-
this.bf = undefined;
|
|
144
|
-
this.hasFactory = false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
197
|
async sees(text, selector) {
|
|
148
198
|
let textContent = null;
|
|
149
199
|
// FIXME retry sometimes required?
|
|
@@ -153,11 +203,27 @@ class WebPlaywright extends AStepper {
|
|
|
153
203
|
return OK;
|
|
154
204
|
}
|
|
155
205
|
}
|
|
156
|
-
const
|
|
157
|
-
return actionNotOK(`Did not find text "${text}" in ${selector}`,
|
|
206
|
+
const incident = { incident: EExecutionMessageType.ON_FAILURE, incidentDetails: { summary: `in ${textContent?.length} characters`, details: textContent } };
|
|
207
|
+
return actionNotOK(`Did not find text "${text}" in ${selector}`, incident);
|
|
208
|
+
}
|
|
209
|
+
async getCookies() {
|
|
210
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
211
|
+
return await browserContext?.cookies();
|
|
158
212
|
}
|
|
159
213
|
steps = {
|
|
160
214
|
...restSteps(this),
|
|
215
|
+
openDevTools: {
|
|
216
|
+
gwta: `open devtools`,
|
|
217
|
+
action: async () => {
|
|
218
|
+
await this.withPage(async (page) => {
|
|
219
|
+
await page.goto('about:blank');
|
|
220
|
+
await sleep(2000);
|
|
221
|
+
const targetId = await fetch('http://localhost:9223/json/list');
|
|
222
|
+
await page.goto(`devtools://devtools/bundled/inspector.html?ws=localhost:9223/devtools/page/${targetId}&panel=network`);
|
|
223
|
+
});
|
|
224
|
+
return OK;
|
|
225
|
+
},
|
|
226
|
+
},
|
|
161
227
|
// INPUT
|
|
162
228
|
press: {
|
|
163
229
|
gwta: `press {key}`,
|
|
@@ -193,14 +259,14 @@ class WebPlaywright extends AStepper {
|
|
|
193
259
|
gwta: 'dialog {what} {type} says {value}',
|
|
194
260
|
action: async ({ what, type, value }) => {
|
|
195
261
|
const cur = this.getWorld().shared.get(what)?.[type];
|
|
196
|
-
return cur === value ? OK : actionNotOK(`${what} is ${cur}`);
|
|
262
|
+
return Promise.resolve(cur === value ? OK : actionNotOK(`${what} is ${cur}`));
|
|
197
263
|
},
|
|
198
264
|
},
|
|
199
265
|
dialogIsUnset: {
|
|
200
266
|
gwta: 'dialog {what} {type} not set',
|
|
201
267
|
action: async ({ what, type }) => {
|
|
202
268
|
const cur = this.getWorld().shared.get(what)?.[type];
|
|
203
|
-
return !cur ? OK : actionNotOK(`${what} is ${cur}`);
|
|
269
|
+
return Promise.resolve(!cur ? OK : actionNotOK(`${what} is ${cur}`));
|
|
204
270
|
},
|
|
205
271
|
},
|
|
206
272
|
seeTestId: {
|
|
@@ -233,11 +299,25 @@ class WebPlaywright extends AStepper {
|
|
|
233
299
|
return actionNotOK(`Did not find ${what}`);
|
|
234
300
|
},
|
|
235
301
|
},
|
|
302
|
+
createMonitor: {
|
|
303
|
+
gwta: 'create monitor',
|
|
304
|
+
action: async () => {
|
|
305
|
+
await this.createMonitor();
|
|
306
|
+
return OK;
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
finishMonitor: {
|
|
310
|
+
gwta: 'finish monitor',
|
|
311
|
+
action: async () => {
|
|
312
|
+
await writeMonitor(this.world, this.storage, WebPlaywright.monitorPage);
|
|
313
|
+
return OK;
|
|
314
|
+
},
|
|
315
|
+
},
|
|
236
316
|
onNewPage: {
|
|
237
317
|
gwta: `on a new tab`,
|
|
238
318
|
action: async () => {
|
|
239
319
|
this.newTab();
|
|
240
|
-
return OK;
|
|
320
|
+
return Promise.resolve(OK);
|
|
241
321
|
},
|
|
242
322
|
},
|
|
243
323
|
waitForTabX: {
|
|
@@ -258,7 +338,7 @@ class WebPlaywright extends AStepper {
|
|
|
258
338
|
gwta: `on tab {tab}`,
|
|
259
339
|
action: async ({ tab }) => {
|
|
260
340
|
this.tab = parseInt(tab, 10);
|
|
261
|
-
return OK;
|
|
341
|
+
return Promise.resolve(OK);
|
|
262
342
|
},
|
|
263
343
|
},
|
|
264
344
|
beOnPage: {
|
|
@@ -277,18 +357,18 @@ class WebPlaywright extends AStepper {
|
|
|
277
357
|
extensionContext: {
|
|
278
358
|
gwta: `open extension popup for tab {tab}`,
|
|
279
359
|
action: async ({ tab }) => {
|
|
280
|
-
if (!this.factoryOptions?.persistentDirectory || this.factoryOptions?.
|
|
360
|
+
if (!this.factoryOptions?.persistentDirectory || this.factoryOptions?.launchOptions.headless) {
|
|
281
361
|
throw Error(`extensions require ${WebPlaywright.PERSISTENT_DIRECTORY} and not HEADLESS`);
|
|
282
362
|
}
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
285
|
-
throw Error(`no
|
|
363
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
364
|
+
if (!browserContext) {
|
|
365
|
+
throw Error(`no browserContext`);
|
|
286
366
|
}
|
|
287
|
-
const background =
|
|
367
|
+
const background = browserContext?.serviceWorkers()[0];
|
|
288
368
|
if (!background) {
|
|
289
369
|
// background = await context.waitForEvent("serviceworker");
|
|
290
370
|
}
|
|
291
|
-
console.debug('background', background,
|
|
371
|
+
console.debug('background', background, browserContext.serviceWorkers());
|
|
292
372
|
const extensionId = background.url().split('/')[2];
|
|
293
373
|
this.getWorld().shared.set('extensionContext', extensionId);
|
|
294
374
|
await this.withPage(async (page) => {
|
|
@@ -301,10 +381,9 @@ class WebPlaywright extends AStepper {
|
|
|
301
381
|
cookieIs: {
|
|
302
382
|
gwta: 'cookie {name} is {value}',
|
|
303
383
|
action: async ({ name, value }) => {
|
|
304
|
-
const
|
|
305
|
-
const cookies = await context?.cookies();
|
|
384
|
+
const cookies = await this.getCookies();
|
|
306
385
|
const found = cookies?.find((c) => c.name === name && c.value === value);
|
|
307
|
-
return found ? OK : actionNotOK(`did not find cookie ${name} with value ${value}`);
|
|
386
|
+
return found ? OK : actionNotOK(`did not find cookie ${name} with value ${value} from ${JSON.stringify(cookies)}`);
|
|
308
387
|
},
|
|
309
388
|
},
|
|
310
389
|
URIContains: {
|
|
@@ -463,7 +542,12 @@ class WebPlaywright extends AStepper {
|
|
|
463
542
|
const response = await this.withPage(async (page) => {
|
|
464
543
|
return await page.goto(name);
|
|
465
544
|
});
|
|
466
|
-
return response?.ok
|
|
545
|
+
return response?.ok
|
|
546
|
+
? OK
|
|
547
|
+
: actionNotOK(`response not ok`, {
|
|
548
|
+
incident: EExecutionMessageType.ACTION,
|
|
549
|
+
incidentDetails: { ...response?.allHeaders, summary: response?.statusText() }
|
|
550
|
+
});
|
|
467
551
|
},
|
|
468
552
|
},
|
|
469
553
|
reloadPage: {
|
|
@@ -483,20 +567,7 @@ class WebPlaywright extends AStepper {
|
|
|
483
567
|
blur: {
|
|
484
568
|
gwta: 'blur {what}',
|
|
485
569
|
action: async ({ what }) => {
|
|
486
|
-
await this.withPage(async (page) => await page.locator(what).evaluate(e => e.blur()));
|
|
487
|
-
return OK;
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
|
-
pressBack: {
|
|
491
|
-
gwta: 'press the back button',
|
|
492
|
-
action: async () => {
|
|
493
|
-
// FIXME
|
|
494
|
-
await this.withPage(async (page) => await page.evaluate(() => {
|
|
495
|
-
console.debug('going back', globalThis.history);
|
|
496
|
-
globalThis.history.go(-1);
|
|
497
|
-
}));
|
|
498
|
-
// await page.focus('body');
|
|
499
|
-
// await page.keyboard.press('Alt+ArrowRight');
|
|
570
|
+
await this.withPage(async (page) => await page.locator(what).evaluate((e) => e.blur()));
|
|
500
571
|
return OK;
|
|
501
572
|
},
|
|
502
573
|
},
|
|
@@ -504,7 +575,10 @@ class WebPlaywright extends AStepper {
|
|
|
504
575
|
usingBrowserVar: {
|
|
505
576
|
gwta: 'using {browser} browser',
|
|
506
577
|
action: async ({ browser }) => {
|
|
507
|
-
|
|
578
|
+
if (!BROWSERS[browser]) {
|
|
579
|
+
throw Error(`browserType not recognized ${browser} from ${BROWSERS.toString()}`);
|
|
580
|
+
}
|
|
581
|
+
return Promise.resolve(this.setBrowser(browser));
|
|
508
582
|
},
|
|
509
583
|
},
|
|
510
584
|
// FILE DOWNLOAD/UPLOAD
|
|
@@ -515,6 +589,49 @@ class WebPlaywright extends AStepper {
|
|
|
515
589
|
return OK;
|
|
516
590
|
},
|
|
517
591
|
},
|
|
592
|
+
waitForFileChooser: {
|
|
593
|
+
gwta: 'upload file {file} with {selector}',
|
|
594
|
+
action: async ({ file, selector }) => {
|
|
595
|
+
try {
|
|
596
|
+
await this.withPage(async (page) => {
|
|
597
|
+
const [fileChooser] = await Promise.all([page.waitForEvent('filechooser'), page.locator('#uploadFile').click()]);
|
|
598
|
+
const changeButton = page.locator(selector);
|
|
599
|
+
await changeButton.click();
|
|
600
|
+
await fileChooser.setFiles(file);
|
|
601
|
+
});
|
|
602
|
+
return OK;
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
return actionNotOK(e);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
expectDownload: {
|
|
610
|
+
gwta: 'expect a download',
|
|
611
|
+
action: async () => {
|
|
612
|
+
try {
|
|
613
|
+
this.expectedDownload = this.withPage(async (page) => page.waitForEvent('download'));
|
|
614
|
+
return Promise.resolve(OK);
|
|
615
|
+
}
|
|
616
|
+
catch (e) {
|
|
617
|
+
return Promise.resolve(actionNotOK(e));
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
receiveDownload: {
|
|
622
|
+
gwta: 'receive download as {file}',
|
|
623
|
+
action: async ({ file }) => {
|
|
624
|
+
try {
|
|
625
|
+
const download = await this.expectedDownload;
|
|
626
|
+
await await download.saveAs(file);
|
|
627
|
+
this.downloaded.push(file);
|
|
628
|
+
return OK;
|
|
629
|
+
}
|
|
630
|
+
catch (e) {
|
|
631
|
+
return actionNotOK(e);
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
},
|
|
518
635
|
waitForDownload: {
|
|
519
636
|
gwta: 'save download to {file}',
|
|
520
637
|
action: async ({ file }) => {
|
|
@@ -534,29 +651,36 @@ class WebPlaywright extends AStepper {
|
|
|
534
651
|
gwta: 'with frame {name}',
|
|
535
652
|
action: async ({ name }) => {
|
|
536
653
|
this.withFrame = name;
|
|
537
|
-
return OK;
|
|
654
|
+
return Promise.resolve(OK);
|
|
538
655
|
},
|
|
539
656
|
},
|
|
540
657
|
captureDialog: {
|
|
541
658
|
gwta: 'Accept next dialog to {where}',
|
|
542
659
|
action: async ({ where }) => {
|
|
543
|
-
await this.withPage(async (page) =>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
660
|
+
await this.withPage(async (page) => {
|
|
661
|
+
return page.on('dialog', async (dialog) => {
|
|
662
|
+
const res = {
|
|
663
|
+
defaultValue: dialog.defaultValue(),
|
|
664
|
+
message: dialog.message(),
|
|
665
|
+
type: dialog.type(),
|
|
666
|
+
};
|
|
667
|
+
await dialog.accept();
|
|
668
|
+
this.getWorld().shared.set(where, res);
|
|
669
|
+
});
|
|
670
|
+
return Promise.resolve();
|
|
671
|
+
});
|
|
672
|
+
return Promise.resolve(OK);
|
|
553
673
|
},
|
|
554
674
|
},
|
|
555
675
|
takeScreenshot: {
|
|
556
676
|
gwta: 'take a screenshot',
|
|
557
677
|
action: async (notUsed, featureStep) => {
|
|
558
|
-
|
|
559
|
-
|
|
678
|
+
try {
|
|
679
|
+
await this.captureScreenshotAndLog(EExecutionMessageType.ACTION, featureStep);
|
|
680
|
+
}
|
|
681
|
+
catch (e) {
|
|
682
|
+
return actionNotOK(e);
|
|
683
|
+
}
|
|
560
684
|
},
|
|
561
685
|
},
|
|
562
686
|
assertOpen: {
|
|
@@ -593,24 +717,117 @@ class WebPlaywright extends AStepper {
|
|
|
593
717
|
newTab() {
|
|
594
718
|
this.tab = this.tab + 1;
|
|
595
719
|
}
|
|
596
|
-
async captureFailureScreenshot(event,
|
|
597
|
-
|
|
720
|
+
async captureFailureScreenshot(event, step) {
|
|
721
|
+
try {
|
|
722
|
+
return await this.captureScreenshotAndLog(event, { step });
|
|
723
|
+
}
|
|
724
|
+
catch (e) {
|
|
725
|
+
this.getWorld().logger.debug(`captureFailureScreenshot error ${e}`);
|
|
726
|
+
}
|
|
598
727
|
}
|
|
599
|
-
async
|
|
600
|
-
|
|
728
|
+
async captureScreenshotAndLog(event, details) {
|
|
729
|
+
const { context, path } = await this.captureScreenshot(event, details);
|
|
730
|
+
this.getWorld().logger.log(`${event} screenshot to ${pathToFileURL(path)}`, context);
|
|
601
731
|
}
|
|
602
|
-
async captureScreenshot(event,
|
|
732
|
+
async captureScreenshot(event, details) {
|
|
603
733
|
const loc = await this.getCaptureDir('image');
|
|
604
734
|
// FIXME shouldn't be fs dependant
|
|
605
735
|
const path = resolve(this.storage.fromLocation(EMediaTypes.image, loc, `${event}-${Date.now()}.png`));
|
|
606
|
-
await this.withPage(async (page) => await page.screenshot({
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
736
|
+
await this.withPage(async (page) => await page.screenshot({ path }));
|
|
737
|
+
const artifact = { artifactType: 'image', path: await this.storage.getRelativePath(path), runtimePath: await this.runtimePath() };
|
|
738
|
+
const context = {
|
|
739
|
+
incident: EExecutionMessageType.ACTION,
|
|
740
|
+
artifact,
|
|
741
|
+
tag: this.getWorld().tag,
|
|
742
|
+
incidentDetails: { ...details, event } // Store original topic details if needed
|
|
743
|
+
};
|
|
744
|
+
return { context, path };
|
|
745
|
+
}
|
|
746
|
+
async setExtraHTTPHeaders(headers) {
|
|
747
|
+
await this.withPage(async () => {
|
|
748
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
749
|
+
await browserContext.setExtraHTTPHeaders(headers);
|
|
750
|
+
this.extraHTTPHeaders = headers;
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
async withPageFetch(endpoint, method = 'get', requestOptions = {}) {
|
|
754
|
+
const { headers, postData, userAgent } = requestOptions;
|
|
755
|
+
const ua = userAgent || this.apiUserAgent;
|
|
756
|
+
const page = await this.getPage();
|
|
757
|
+
// FIXME Part I this could suffer from race conditions
|
|
758
|
+
if (ua) {
|
|
759
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
760
|
+
const headers = { ...this.extraHTTPHeaders || {}, ...{ 'User-Agent': ua } };
|
|
761
|
+
await browserContext.setExtraHTTPHeaders(headers);
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
const pageConsoleMessages = [];
|
|
765
|
+
try {
|
|
766
|
+
page.on('console', (msg) => {
|
|
767
|
+
pageConsoleMessages.push({ type: msg.type(), text: msg.text() });
|
|
768
|
+
});
|
|
769
|
+
const ret = await page.evaluate(async ({ endpoint, method, headers, postData }) => {
|
|
770
|
+
const fetchOptions = {
|
|
771
|
+
method,
|
|
772
|
+
};
|
|
773
|
+
fetchOptions.headers = headers ? headers : {};
|
|
774
|
+
if (postData)
|
|
775
|
+
fetchOptions.body = postData;
|
|
776
|
+
const response = await fetch(endpoint, fetchOptions);
|
|
777
|
+
const capturedResponse = {
|
|
778
|
+
status: response.status,
|
|
779
|
+
statusText: response.statusText,
|
|
780
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
781
|
+
url: response.url,
|
|
782
|
+
json: await response.json().catch(() => null),
|
|
783
|
+
text: await response.text().catch(() => null),
|
|
784
|
+
};
|
|
785
|
+
return capturedResponse;
|
|
786
|
+
}, { endpoint, method, headers, postData });
|
|
787
|
+
return ret;
|
|
788
|
+
}
|
|
789
|
+
catch (e) {
|
|
790
|
+
throw new Error(`Evaluate fetch error: ${JSON.stringify({ endpoint, method, headers, ua })} : ${e.message}. Page console messages: ${pageConsoleMessages.map(msg => `[${msg.type}] ${msg.text}`).join('; ')}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch (e) {
|
|
794
|
+
const ua = userAgent || this.apiUserAgent;
|
|
795
|
+
throw new Error(`Evaluate fetch error: ${JSON.stringify({ endpoint, method, headers, ua })} : ${e.message}`);
|
|
796
|
+
}
|
|
797
|
+
finally {
|
|
798
|
+
// FIXME Part II this could suffer from race conditions
|
|
799
|
+
if (ua) {
|
|
800
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
801
|
+
await browserContext.setExtraHTTPHeaders(this.extraHTTPHeaders);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async callClosers() {
|
|
806
|
+
if (this.closers) {
|
|
807
|
+
for (const closer of this.closers) {
|
|
808
|
+
await closer();
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
createMonitor = async () => {
|
|
813
|
+
if (WebPlaywright.monitorPage && !WebPlaywright.monitorPage.isClosed()) {
|
|
814
|
+
console.log("Monitor page already exists.");
|
|
815
|
+
await WebPlaywright.monitorPage.bringToFront();
|
|
816
|
+
return OK;
|
|
817
|
+
}
|
|
818
|
+
const { monitorPage, subscriber } = await (await createMonitorPageAndSubscriber())(); // Removed runtimePath argument
|
|
819
|
+
WebPlaywright.monitorPage = monitorPage;
|
|
820
|
+
this.getWorld().logger.addSubscriber(subscriber);
|
|
821
|
+
this.closers.push(async () => {
|
|
822
|
+
console.log("Removing monitor logger subscriber.");
|
|
823
|
+
this.getWorld().logger.removeSubscriber(subscriber);
|
|
824
|
+
return Promise.resolve();
|
|
825
|
+
});
|
|
826
|
+
return OK;
|
|
827
|
+
};
|
|
828
|
+
async runtimePath(world) {
|
|
829
|
+
return pathToFileURL(await this.storage.getCaptureLocation({ ...(world || this.getWorld()), mediaType: EMediaTypes.html })).pathname;
|
|
612
830
|
}
|
|
613
831
|
}
|
|
614
|
-
;
|
|
615
832
|
export default WebPlaywright;
|
|
616
833
|
//# sourceMappingURL=web-playwright.js.map
|