@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.
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 +60 -2
  38. package/build/rest-playwright.d.ts.map +1 -1
  39. package/build/rest-playwright.js +170 -51
  40. package/build/rest-playwright.js.map +1 -1
  41. package/build/web-playwright.d.ts +142 -54
  42. package/build/web-playwright.d.ts.map +1 -1
  43. package/build/web-playwright.js +327 -110
  44. package/build/web-playwright.js.map +1 -1
  45. package/package.json +4 -3
@@ -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) => boolOrError(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
- const args = [...(getStepperOption(this, 'ARGS', world.moduleOptions)?.split(';') || ''), '--disable-gpu'];
58
- 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);
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
- browser: {
69
- headless,
70
- args,
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().logger, this.factoryOptions);
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 getContext() {
91
- const context = (await this.getBrowserFactory()).getExistingContext(this.getWorld().tag);
92
- return context;
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 topics = { textContent: { summary: `in ${textContent?.length} characters`, details: textContent } };
157
- 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();
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?.browser.headless) {
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 context = await this.getContext();
284
- if (!context) {
285
- throw Error(`no context`);
363
+ const browserContext = await this.getExistingBrowserContext();
364
+ if (!browserContext) {
365
+ throw Error(`no browserContext`);
286
366
  }
287
- const background = context?.serviceWorkers()[0];
367
+ const background = browserContext?.serviceWorkers()[0];
288
368
  if (!background) {
289
369
  // background = await context.waitForEvent("serviceworker");
290
370
  }
291
- console.debug('background', background, context.serviceWorkers());
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 context = await this.getContext();
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 ? 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
+ });
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
- 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));
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) => page.on('dialog', async (dialog) => {
544
- const res = {
545
- defaultValue: dialog.defaultValue(),
546
- message: dialog.message(),
547
- type: dialog.type(),
548
- };
549
- await dialog.accept();
550
- this.getWorld().shared.set(where, res);
551
- }));
552
- 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);
553
673
  },
554
674
  },
555
675
  takeScreenshot: {
556
676
  gwta: 'take a screenshot',
557
677
  action: async (notUsed, featureStep) => {
558
- await this.captureScreenshot('request', 'action', featureStep);
559
- return OK;
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, stage, step) {
597
- 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
+ }
598
727
  }
599
- async captureRequestScreenshot(event, stage, seq) {
600
- 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);
601
731
  }
602
- async captureScreenshot(event, stage, details) {
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
- path,
608
- }));
609
- const artifact = Logger.logArtifact({ type: 'picture', path });
610
- const artifactTopic = { topic: { ...details, event, stage }, artifact, tag: this.getWorld().tag };
611
- 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;
612
830
  }
613
831
  }
614
- ;
615
832
  export default WebPlaywright;
616
833
  //# sourceMappingURL=web-playwright.js.map