@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.
Files changed (45) hide show
  1. package/build/BrowserFactory.d.ts +15 -27
  2. package/build/BrowserFactory.d.ts.map +1 -1
  3. package/build/BrowserFactory.js +71 -50
  4. package/build/BrowserFactory.js.map +1 -1
  5. package/build/PlaywrightEvents.d.ts +3 -3
  6. package/build/PlaywrightEvents.d.ts.map +1 -1
  7. package/build/PlaywrightEvents.js +17 -13
  8. package/build/PlaywrightEvents.js.map +1 -1
  9. package/build/monitor/XXlogToMonitor.d.ts +8 -0
  10. package/build/monitor/XXlogToMonitor.d.ts.map +1 -0
  11. package/build/monitor/XXlogToMonitor.js +151 -0
  12. package/build/monitor/XXlogToMonitor.js.map +1 -0
  13. package/build/monitor/controls.d.ts +3 -0
  14. package/build/monitor/controls.d.ts.map +1 -0
  15. package/build/monitor/controls.js +318 -0
  16. package/build/monitor/controls.js.map +1 -0
  17. package/build/monitor/disclosureJson.d.ts +3 -0
  18. package/build/monitor/disclosureJson.d.ts.map +1 -0
  19. package/build/monitor/disclosureJson.js +121 -0
  20. package/build/monitor/disclosureJson.js.map +1 -0
  21. package/build/monitor/mermaidDiagram.d.ts +22 -0
  22. package/build/monitor/mermaidDiagram.d.ts.map +1 -0
  23. package/build/monitor/mermaidDiagram.js +199 -0
  24. package/build/monitor/mermaidDiagram.js.map +1 -0
  25. package/build/monitor/messages.d.ts +18 -0
  26. package/build/monitor/messages.d.ts.map +1 -0
  27. package/build/monitor/messages.js +303 -0
  28. package/build/monitor/messages.js.map +1 -0
  29. package/build/monitor/monitor.d.ts +16 -0
  30. package/build/monitor/monitor.d.ts.map +1 -0
  31. package/build/monitor/monitor.js +61 -0
  32. package/build/monitor/monitor.js.map +1 -0
  33. package/build/monitor/monitorHandler.d.ts +12 -0
  34. package/build/monitor/monitorHandler.d.ts.map +1 -0
  35. package/build/monitor/monitorHandler.js +89 -0
  36. package/build/monitor/monitorHandler.js.map +1 -0
  37. package/build/rest-playwright.d.ts +87 -0
  38. package/build/rest-playwright.d.ts.map +1 -0
  39. package/build/rest-playwright.js +219 -0
  40. package/build/rest-playwright.js.map +1 -0
  41. package/build/web-playwright.d.ts +424 -328
  42. package/build/web-playwright.d.ts.map +1 -1
  43. package/build/web-playwright.js +334 -125
  44. package/build/web-playwright.js.map +1 -1
  45. 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,EAAsB,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAmB,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEjI,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAiB,MAAM,qBAAqB,CAAC;AAE5F,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,EAAE,YAAY,EAA2B,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AAK1H,QAAA,MAAM,aAAa;;;;;;+BAOE,MAAM;;;;;;;;;;+BAYN,MAAM;;;;;;;;;;+BAIN,MAAM;;;;;;;;;;+BASN,MAAM;;;;;;;;;;;+BAzBN,MAAM;;;;;;;;;;+BAIN,MAAM;;;;;;;;;;+BAQN,MAAM;;;;;;;;;;+BAIN,MAAM;;;;;;;;;;;+BAKN,MAAM;;;;;;;;;;+BAIN,MAAM;;;;;;;;;;aAQpB,cAAc;kBACT,QAAQ;yBACD,sBAAsB;;mBAE5B,MAAM;oBACL,MAAM,EAAE;sBACN,MAAM;wBAEE,MAAM,YAAY,QAAQ,EAAE;4BA2BxB,MAAM;6BAML,OAAO,CAAC,cAAc,CAAC;;;iBA0BnC,OAAO,KAAK,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;0BAQ/B,WAAW,SAAS,YAAY,GAAG,OAAO,CAAC,IAAI,GAAG,oBAAoB,CAAC;uBAO1E,YAAY;;;;mBAuChB,MAAM,YAAY,MAAM;;;;kCAgBb,MAAM;;;;mCAOL,MAAM;;;;0CAOC,MAAM;;;;4CAOJ,MAAM;;;;gDAUF,MAAM;;;;yCAQb,MAAM;;;;qCAOV,MAAM;;;;6CAOE,MAAM;;;;mCAMhB,MAAM;;;;mCAMN,MAAM;;;;;;;;kCAmBP,MAAM;;;;kCAgBN,MAAM;;;;mCAOL,MAAM;;;;kCAaP,MAAM;;;;0CA6BE,MAAM;;;;mCAUb,MAAM;;;;0CAOC,MAAM;;;;oCAWZ,MAAM;;;;mCAOP,MAAM;;;;mCAON,MAAM;;;;sCAWH,MAAM;;;;qCAOP,MAAM;;;;0CAOD,MAAM;;;;sCAOV,MAAM;;;;2CAcD,MAAM;;;;oCAOb,MAAM;;;;mCAQP,MAAM;;;;mCAON,MAAM;;;;mCAQN,MAAM;;;;iCASR,MAAM;;;;mCAQJ,MAAM;;;;mCAQN,MAAM,eAAe,YAAY;;;;iCAUnC,MAAM;;;;mCAaJ,MAAM;;;;;;;;;;;;mCA0BN,MAAM;;;;;;;;sCA2BH,MAAM;;;;6CAQC,MAAM;;;;mCAQhB,MAAM;;;;mCAgBN,MAAM;;;;oCAOL,MAAM;;;;oDAiBK,YAAY;;;;0CAOjB,MAAM;;;;0CAUN,MAAM;;;;4CASJ,MAAM;;;4BAMxB,MAAM;;wCAOY,SAAS,SAAS,YAAY,QAAQ,YAAY;wCAGlD,SAAS,SAAS,YAAY,OAAO,MAAM;iCAIlD,SAAS,GAAG,SAAS,SAAS,YAAY,WAAW;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,YAAY,CAAA;SAAE;;;;;;CAc1H,CAAC;AAEF,eAAe,aAAa,CAAC;AAE7B,MAAM,MAAM,cAAc,GAAG,OAAO,aAAa,CAAC"}
1
+ {"version":3,"file":"web-playwright.d.ts","sourceRoot":"","sources":["../src/web-playwright.ts"],"names":[],"mappings":"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"}
@@ -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 Logger from '@haibun/core/build/lib/Logger.js';
7
- import { resolve } from 'path';
8
- const WebPlaywright = class WebPlaywright extends AStepper {
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) => boolOrError(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
- const args = [...(getStepperOption(this, 'ARGS', world.moduleOptions)?.split(';') || ''), '--disable-gpu'];
61
- const persistentDirectory = getStepperOption(this, WebPlaywright.PERSISTENT_DIRECTORY, world.moduleOptions) === 'true';
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
- browser: {
72
- headless,
73
- args,
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().logger, this.factoryOptions);
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 getContext() {
94
- const context = (await this.getBrowserFactory()).getExistingContext(this.getWorld().tag);
95
- return context;
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 topics = { textContent: { summary: `in ${textContent?.length} characters`, details: textContent } };
167
- return actionNotOK(`Did not find text "${text}" in ${selector}`, { topics });
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?.browser.headless) {
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 context = await this.getContext();
293
- if (!context) {
294
- throw Error(`no context`);
363
+ const browserContext = await this.getExistingBrowserContext();
364
+ if (!browserContext) {
365
+ throw Error(`no browserContext`);
295
366
  }
296
- const background = context?.serviceWorkers()[0];
367
+ const background = browserContext?.serviceWorkers()[0];
297
368
  if (!background) {
298
369
  // background = await context.waitForEvent("serviceworker");
299
370
  }
300
- console.debug('background', background, context.serviceWorkers());
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 context = await this.getContext();
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.line.match(/ with alt /) ? { modifiers: ['Alt'] } : {};
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 ? OK : actionNotOK(`response not ok`, { topics: { response: { ...response.allHeaders, summary: response.statusText() } } });
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
- return this.setBrowser(browser);
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) => page.on('dialog', async (dialog) => {
553
- const res = {
554
- defaultValue: dialog.defaultValue(),
555
- message: dialog.message(),
556
- type: dialog.type(),
557
- };
558
- await dialog.accept();
559
- this.getWorld().shared.set(where, res);
560
- }));
561
- return OK;
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
- await this.captureScreenshot('request', 'action', featureStep);
568
- return OK;
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, stage, step) {
606
- return await this.captureScreenshot(event, stage, { step });
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 captureRequestScreenshot(event, stage, seq) {
609
- return await this.captureScreenshot(event, stage, { seq });
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, stage, details) {
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
- path,
617
- }));
618
- const artifact = Logger.logArtifact({ type: 'picture', path });
619
- const artifactTopic = { topic: { ...details, event, stage }, artifact, tag: this.getWorld().tag };
620
- this.getWorld().logger.info('screenshot', artifactTopic);
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