@haibun/web-playwright 1.42.4 → 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 +87 -0
- package/build/rest-playwright.d.ts.map +1 -0
- package/build/rest-playwright.js +219 -0
- package/build/rest-playwright.js.map +1 -0
- package/build/web-playwright.d.ts +424 -328
- package/build/web-playwright.d.ts.map +1 -1
- package/build/web-playwright.js +334 -125
- package/build/web-playwright.js.map +1 -1
- package/package.json +5 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-playwright.d.ts","sourceRoot":"","sources":["../src/web-playwright.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"web-playwright.d.ts","sourceRoot":"","sources":["../src/web-playwright.ts"],"names":[],"mappings":"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"}
|
package/build/web-playwright.js
CHANGED
|
@@ -1,15 +1,86 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
1
3
|
import { OK, AStepper } from '@haibun/core/build/lib/defs.js';
|
|
2
4
|
import { WEB_PAGE, WEB_CONTROL } from '@haibun/core/build/lib/domain-types.js';
|
|
3
|
-
import { BrowserFactory } from './BrowserFactory.js';
|
|
4
|
-
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
|
|
5
8
|
import { EMediaTypes } from '@haibun/domain-storage/build/media-types.js';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
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
|
+
});
|
|
74
|
+
class WebPlaywright extends AStepper {
|
|
75
|
+
cycles = cycles(this);
|
|
9
76
|
static STORAGE = 'STORAGE';
|
|
10
77
|
static PERSISTENT_DIRECTORY = 'PERSISTENT_DIRECTORY';
|
|
11
78
|
requireDomains = [WEB_PAGE, WEB_CONTROL];
|
|
12
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
|
+
},
|
|
13
84
|
HEADLESS: {
|
|
14
85
|
desc: 'run browsers without a window (true, false)',
|
|
15
86
|
parse: (input) => boolOrError(input),
|
|
@@ -20,7 +91,7 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
20
91
|
},
|
|
21
92
|
[WebPlaywright.PERSISTENT_DIRECTORY]: {
|
|
22
93
|
desc: 'run browsers with a persistent directory (true or false)',
|
|
23
|
-
parse: (input) =>
|
|
94
|
+
parse: (input) => stringOrError(input),
|
|
24
95
|
},
|
|
25
96
|
ARGS: {
|
|
26
97
|
desc: 'pass arguments',
|
|
@@ -31,17 +102,14 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
31
102
|
parse: (input) => boolOrError(input),
|
|
32
103
|
dependsOn: ['STORAGE'],
|
|
33
104
|
},
|
|
34
|
-
STEP_CAPTURE_SCREENSHOT: {
|
|
35
|
-
desc: 'capture screenshot for every step',
|
|
36
|
-
parse: (input) => boolOrError(input),
|
|
37
|
-
},
|
|
38
105
|
TIMEOUT: {
|
|
39
|
-
desc: 'timeout for each step',
|
|
106
|
+
desc: 'browser timeout for each step',
|
|
40
107
|
parse: (input) => intOrError(input),
|
|
41
108
|
},
|
|
42
109
|
[WebPlaywright.STORAGE]: {
|
|
43
110
|
desc: 'Storage for output',
|
|
44
111
|
parse: (input) => stringOrError(input),
|
|
112
|
+
required: true
|
|
45
113
|
},
|
|
46
114
|
};
|
|
47
115
|
hasFactory = false;
|
|
@@ -52,28 +120,43 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
52
120
|
withFrame;
|
|
53
121
|
downloaded = [];
|
|
54
122
|
captureVideo;
|
|
123
|
+
closers = [];
|
|
124
|
+
logElementError;
|
|
125
|
+
monitor;
|
|
126
|
+
static monitorPage;
|
|
127
|
+
userAgentPages = {};
|
|
128
|
+
apiUserAgent;
|
|
129
|
+
extraHTTPHeaders = {};
|
|
130
|
+
BROWSER_STATE_PATH = undefined;
|
|
131
|
+
expectedDownload;
|
|
55
132
|
async setWorld(world, steppers) {
|
|
56
133
|
await super.setWorld(world, steppers);
|
|
134
|
+
const args = [...(getStepperOption(this, 'ARGS', world.moduleOptions)?.split(';') || ''),]; //'--disable-gpu'
|
|
57
135
|
this.storage = findStepperFromOption(steppers, this, world.moduleOptions, WebPlaywright.STORAGE);
|
|
58
136
|
const headless = getStepperOption(this, 'HEADLESS', world.moduleOptions) === 'true' || !!process.env.CI;
|
|
59
137
|
const devtools = getStepperOption(this, 'DEVTOOLS', world.moduleOptions) === 'true';
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
|
62
143
|
const defaultTimeout = parseInt(getStepperOption(this, 'TIMEOUT', world.moduleOptions)) || 30000;
|
|
63
|
-
this.captureVideo = getStepperOption(this, 'CAPTURE_VIDEO', world.moduleOptions);
|
|
144
|
+
this.captureVideo = getStepperOption(this, 'CAPTURE_VIDEO', world.moduleOptions) === 'true';
|
|
64
145
|
let recordVideo;
|
|
65
146
|
if (this.captureVideo) {
|
|
66
147
|
recordVideo = {
|
|
67
|
-
dir: await this.getCaptureDir('video')
|
|
148
|
+
dir: await this.getCaptureDir('video'),
|
|
68
149
|
};
|
|
69
150
|
}
|
|
151
|
+
const launchOptions = {
|
|
152
|
+
headless,
|
|
153
|
+
args,
|
|
154
|
+
devtools,
|
|
155
|
+
};
|
|
70
156
|
this.factoryOptions = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
devtools,
|
|
75
|
-
},
|
|
76
|
-
recordVideo,
|
|
157
|
+
options: { recordVideo, },
|
|
158
|
+
browserType: BROWSERS.chromium,
|
|
159
|
+
launchOptions,
|
|
77
160
|
defaultTimeout,
|
|
78
161
|
persistentDirectory,
|
|
79
162
|
};
|
|
@@ -85,14 +168,14 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
85
168
|
}
|
|
86
169
|
async getBrowserFactory() {
|
|
87
170
|
if (!this.hasFactory) {
|
|
88
|
-
this.bf = await BrowserFactory.getBrowserFactory(this.getWorld()
|
|
171
|
+
this.bf = await BrowserFactory.getBrowserFactory(this.getWorld(), this.factoryOptions);
|
|
89
172
|
this.hasFactory = true;
|
|
90
173
|
}
|
|
91
174
|
return this.bf;
|
|
92
175
|
}
|
|
93
|
-
async
|
|
94
|
-
const
|
|
95
|
-
return
|
|
176
|
+
async getExistingBrowserContext(tag = this.getWorld().tag) {
|
|
177
|
+
const browserContext = (await this.getBrowserFactory()).getExistingBrowserContextWithTag(tag);
|
|
178
|
+
return browserContext;
|
|
96
179
|
}
|
|
97
180
|
async getPage() {
|
|
98
181
|
const { tag } = this.getWorld();
|
|
@@ -111,49 +194,6 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
111
194
|
this.withFrame = undefined;
|
|
112
195
|
return await f(page);
|
|
113
196
|
}
|
|
114
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
115
|
-
async onFailure(result, step) {
|
|
116
|
-
if (this.bf?.hasPage(this.getWorld().tag, this.tab)) {
|
|
117
|
-
await this.captureFailureScreenshot('failure', 'onFailure', step);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// FIXME currently not executed
|
|
121
|
-
async nextStep(step) {
|
|
122
|
-
const captureScreenshot = getStepperOption(this, 'STEP_CAPTURE_SCREENSHOT', this.getWorld().moduleOptions);
|
|
123
|
-
if (captureScreenshot) {
|
|
124
|
-
await this.captureRequestScreenshot('request', 'nextStep', step.seq);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
async endFeature() {
|
|
128
|
-
// close the context, which closes any pages
|
|
129
|
-
if (this.hasFactory) {
|
|
130
|
-
if (this.captureVideo) {
|
|
131
|
-
const page = await this.getPage();
|
|
132
|
-
const path = await page.video().path();
|
|
133
|
-
const artifact = { type: 'video', path };
|
|
134
|
-
this.getWorld().logger.info('endFeature video', { artifact, topic: { event: 'summary', stage: 'endFeature' }, tag: this.getWorld().tag });
|
|
135
|
-
}
|
|
136
|
-
// await this.bf?.closeContext(this.getWorld().tag);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async close() {
|
|
140
|
-
// close the context, which closes any pages
|
|
141
|
-
if (this.hasFactory) {
|
|
142
|
-
await this.bf?.closeContext(this.getWorld().tag);
|
|
143
|
-
}
|
|
144
|
-
for (const file of this.downloaded) {
|
|
145
|
-
this.getWorld().logger.debug(`removing ${JSON.stringify(file)}`);
|
|
146
|
-
// rmSync(file);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// FIXME
|
|
150
|
-
async finish() {
|
|
151
|
-
if (this.hasFactory) {
|
|
152
|
-
await this.bf?.close();
|
|
153
|
-
this.bf = undefined;
|
|
154
|
-
this.hasFactory = false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
197
|
async sees(text, selector) {
|
|
158
198
|
let textContent = null;
|
|
159
199
|
// FIXME retry sometimes required?
|
|
@@ -163,10 +203,27 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
163
203
|
return OK;
|
|
164
204
|
}
|
|
165
205
|
}
|
|
166
|
-
const
|
|
167
|
-
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();
|
|
168
212
|
}
|
|
169
213
|
steps = {
|
|
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
|
+
},
|
|
170
227
|
// INPUT
|
|
171
228
|
press: {
|
|
172
229
|
gwta: `press {key}`,
|
|
@@ -202,14 +259,14 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
202
259
|
gwta: 'dialog {what} {type} says {value}',
|
|
203
260
|
action: async ({ what, type, value }) => {
|
|
204
261
|
const cur = this.getWorld().shared.get(what)?.[type];
|
|
205
|
-
return cur === value ? OK : actionNotOK(`${what} is ${cur}`);
|
|
262
|
+
return Promise.resolve(cur === value ? OK : actionNotOK(`${what} is ${cur}`));
|
|
206
263
|
},
|
|
207
264
|
},
|
|
208
265
|
dialogIsUnset: {
|
|
209
266
|
gwta: 'dialog {what} {type} not set',
|
|
210
267
|
action: async ({ what, type }) => {
|
|
211
268
|
const cur = this.getWorld().shared.get(what)?.[type];
|
|
212
|
-
return !cur ? OK : actionNotOK(`${what} is ${cur}`);
|
|
269
|
+
return Promise.resolve(!cur ? OK : actionNotOK(`${what} is ${cur}`));
|
|
213
270
|
},
|
|
214
271
|
},
|
|
215
272
|
seeTestId: {
|
|
@@ -242,11 +299,25 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
242
299
|
return actionNotOK(`Did not find ${what}`);
|
|
243
300
|
},
|
|
244
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
|
+
},
|
|
245
316
|
onNewPage: {
|
|
246
317
|
gwta: `on a new tab`,
|
|
247
318
|
action: async () => {
|
|
248
319
|
this.newTab();
|
|
249
|
-
return OK;
|
|
320
|
+
return Promise.resolve(OK);
|
|
250
321
|
},
|
|
251
322
|
},
|
|
252
323
|
waitForTabX: {
|
|
@@ -267,7 +338,7 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
267
338
|
gwta: `on tab {tab}`,
|
|
268
339
|
action: async ({ tab }) => {
|
|
269
340
|
this.tab = parseInt(tab, 10);
|
|
270
|
-
return OK;
|
|
341
|
+
return Promise.resolve(OK);
|
|
271
342
|
},
|
|
272
343
|
},
|
|
273
344
|
beOnPage: {
|
|
@@ -286,18 +357,18 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
286
357
|
extensionContext: {
|
|
287
358
|
gwta: `open extension popup for tab {tab}`,
|
|
288
359
|
action: async ({ tab }) => {
|
|
289
|
-
if (!this.factoryOptions?.persistentDirectory || this.factoryOptions?.
|
|
360
|
+
if (!this.factoryOptions?.persistentDirectory || this.factoryOptions?.launchOptions.headless) {
|
|
290
361
|
throw Error(`extensions require ${WebPlaywright.PERSISTENT_DIRECTORY} and not HEADLESS`);
|
|
291
362
|
}
|
|
292
|
-
const
|
|
293
|
-
if (!
|
|
294
|
-
throw Error(`no
|
|
363
|
+
const browserContext = await this.getExistingBrowserContext();
|
|
364
|
+
if (!browserContext) {
|
|
365
|
+
throw Error(`no browserContext`);
|
|
295
366
|
}
|
|
296
|
-
const background =
|
|
367
|
+
const background = browserContext?.serviceWorkers()[0];
|
|
297
368
|
if (!background) {
|
|
298
369
|
// background = await context.waitForEvent("serviceworker");
|
|
299
370
|
}
|
|
300
|
-
console.debug('background', background,
|
|
371
|
+
console.debug('background', background, browserContext.serviceWorkers());
|
|
301
372
|
const extensionId = background.url().split('/')[2];
|
|
302
373
|
this.getWorld().shared.set('extensionContext', extensionId);
|
|
303
374
|
await this.withPage(async (page) => {
|
|
@@ -310,10 +381,9 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
310
381
|
cookieIs: {
|
|
311
382
|
gwta: 'cookie {name} is {value}',
|
|
312
383
|
action: async ({ name, value }) => {
|
|
313
|
-
const
|
|
314
|
-
const cookies = await context?.cookies();
|
|
384
|
+
const cookies = await this.getCookies();
|
|
315
385
|
const found = cookies?.find((c) => c.name === name && c.value === value);
|
|
316
|
-
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)}`);
|
|
317
387
|
},
|
|
318
388
|
},
|
|
319
389
|
URIContains: {
|
|
@@ -450,7 +520,7 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
450
520
|
// TODO: generalize modifier
|
|
451
521
|
gwta: 'click( with alt)? the link {name}',
|
|
452
522
|
action: async ({ name }, featureStep) => {
|
|
453
|
-
const modifier = featureStep.
|
|
523
|
+
const modifier = featureStep.in.match(/ with alt /) ? { modifiers: ['Alt'] } : {};
|
|
454
524
|
const field = this.getWorld().shared.get(name) || name;
|
|
455
525
|
await this.withPage(async (page) => await page.click(field, modifier));
|
|
456
526
|
return OK;
|
|
@@ -472,7 +542,12 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
472
542
|
const response = await this.withPage(async (page) => {
|
|
473
543
|
return await page.goto(name);
|
|
474
544
|
});
|
|
475
|
-
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
|
+
});
|
|
476
551
|
},
|
|
477
552
|
},
|
|
478
553
|
reloadPage: {
|
|
@@ -492,20 +567,7 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
492
567
|
blur: {
|
|
493
568
|
gwta: 'blur {what}',
|
|
494
569
|
action: async ({ what }) => {
|
|
495
|
-
await this.withPage(async (page) => await page.locator(what).evaluate(e => e.blur()));
|
|
496
|
-
return OK;
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
pressBack: {
|
|
500
|
-
gwta: 'press the back button',
|
|
501
|
-
action: async () => {
|
|
502
|
-
// FIXME
|
|
503
|
-
await this.withPage(async (page) => await page.evaluate(() => {
|
|
504
|
-
console.debug('going back', globalThis.history);
|
|
505
|
-
globalThis.history.go(-1);
|
|
506
|
-
}));
|
|
507
|
-
// await page.focus('body');
|
|
508
|
-
// await page.keyboard.press('Alt+ArrowRight');
|
|
570
|
+
await this.withPage(async (page) => await page.locator(what).evaluate((e) => e.blur()));
|
|
509
571
|
return OK;
|
|
510
572
|
},
|
|
511
573
|
},
|
|
@@ -513,7 +575,10 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
513
575
|
usingBrowserVar: {
|
|
514
576
|
gwta: 'using {browser} browser',
|
|
515
577
|
action: async ({ browser }) => {
|
|
516
|
-
|
|
578
|
+
if (!BROWSERS[browser]) {
|
|
579
|
+
throw Error(`browserType not recognized ${browser} from ${BROWSERS.toString()}`);
|
|
580
|
+
}
|
|
581
|
+
return Promise.resolve(this.setBrowser(browser));
|
|
517
582
|
},
|
|
518
583
|
},
|
|
519
584
|
// FILE DOWNLOAD/UPLOAD
|
|
@@ -524,6 +589,49 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
524
589
|
return OK;
|
|
525
590
|
},
|
|
526
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
|
+
},
|
|
527
635
|
waitForDownload: {
|
|
528
636
|
gwta: 'save download to {file}',
|
|
529
637
|
action: async ({ file }) => {
|
|
@@ -543,29 +651,36 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
543
651
|
gwta: 'with frame {name}',
|
|
544
652
|
action: async ({ name }) => {
|
|
545
653
|
this.withFrame = name;
|
|
546
|
-
return OK;
|
|
654
|
+
return Promise.resolve(OK);
|
|
547
655
|
},
|
|
548
656
|
},
|
|
549
657
|
captureDialog: {
|
|
550
658
|
gwta: 'Accept next dialog to {where}',
|
|
551
659
|
action: async ({ where }) => {
|
|
552
|
-
await this.withPage(async (page) =>
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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);
|
|
562
673
|
},
|
|
563
674
|
},
|
|
564
675
|
takeScreenshot: {
|
|
565
676
|
gwta: 'take a screenshot',
|
|
566
677
|
action: async (notUsed, featureStep) => {
|
|
567
|
-
|
|
568
|
-
|
|
678
|
+
try {
|
|
679
|
+
await this.captureScreenshotAndLog(EExecutionMessageType.ACTION, featureStep);
|
|
680
|
+
}
|
|
681
|
+
catch (e) {
|
|
682
|
+
return actionNotOK(e);
|
|
683
|
+
}
|
|
569
684
|
},
|
|
570
685
|
},
|
|
571
686
|
assertOpen: {
|
|
@@ -602,23 +717,117 @@ const WebPlaywright = class WebPlaywright extends AStepper {
|
|
|
602
717
|
newTab() {
|
|
603
718
|
this.tab = this.tab + 1;
|
|
604
719
|
}
|
|
605
|
-
async captureFailureScreenshot(event,
|
|
606
|
-
|
|
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
|
+
}
|
|
607
727
|
}
|
|
608
|
-
async
|
|
609
|
-
|
|
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);
|
|
610
731
|
}
|
|
611
|
-
async captureScreenshot(event,
|
|
732
|
+
async captureScreenshot(event, details) {
|
|
612
733
|
const loc = await this.getCaptureDir('image');
|
|
613
734
|
// FIXME shouldn't be fs dependant
|
|
614
735
|
const path = resolve(this.storage.fromLocation(EMediaTypes.image, loc, `${event}-${Date.now()}.png`));
|
|
615
|
-
await this.withPage(async (page) => await page.screenshot({
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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;
|
|
621
830
|
}
|
|
622
|
-
}
|
|
831
|
+
}
|
|
623
832
|
export default WebPlaywright;
|
|
624
833
|
//# sourceMappingURL=web-playwright.js.map
|