@capillarytech/cap-ui-utils 3.1.1 → 3.1.2

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 (42) hide show
  1. package/e2e/constants/common.js +22 -0
  2. package/e2e/index.js +65 -0
  3. package/e2e/pages/common/base.page.js +13 -0
  4. package/e2e/pages/common/constant.js +5 -0
  5. package/e2e/pages/common/login.page.js +95 -0
  6. package/e2e/services/{lockService.ts → lockService.js} +63 -67
  7. package/e2e/utils/{antdVersionUtil.ts → antdVersionUtil.js} +24 -32
  8. package/e2e/utils/{automationBypassUtil.ts → automationBypassUtil.js} +11 -8
  9. package/e2e/utils/{debugModeUtil.ts → debugModeUtil.js} +9 -7
  10. package/e2e/utils/deletionRegistry.js +16 -0
  11. package/e2e/utils/{elementUtil.ts → elementUtil.js} +224 -296
  12. package/e2e/utils/expectUtil.js +40 -0
  13. package/e2e/utils/featureFlagUtil.js +25 -0
  14. package/e2e/utils/garudaDropdownUtil.js +34 -0
  15. package/e2e/utils/htmlEditorUtil.js +59 -0
  16. package/e2e/utils/logCollectorUtil.js +65 -0
  17. package/e2e/utils/{mockResponse.ts → mockResponse.js} +5 -2
  18. package/e2e/utils/{reportUploader.ts → reportUploader.js} +69 -66
  19. package/e2e/utils/{requestRecorderUtil.ts → requestRecorderUtil.js} +69 -97
  20. package/e2e/utils/{screenshotRecorderUtil.ts → screenshotRecorderUtil.js} +61 -82
  21. package/e2e/utils/setupMock.js +18 -0
  22. package/e2e/utils/unmatchedBracesUtil.js +87 -0
  23. package/e2e/utils/uploaders/fileServiceUploader.js +113 -0
  24. package/e2e/utils/uploaders/uploader.js +2 -0
  25. package/e2e/utils/virtualListUtil.js +74 -0
  26. package/package.json +1 -1
  27. package/e2e/constants/common.ts +0 -23
  28. package/e2e/index.ts +0 -30
  29. package/e2e/pages/common/base.page.ts +0 -11
  30. package/e2e/pages/common/constant.ts +0 -2
  31. package/e2e/pages/common/login.page.ts +0 -95
  32. package/e2e/utils/deletionRegistry.ts +0 -15
  33. package/e2e/utils/expectUtil.ts +0 -19
  34. package/e2e/utils/featureFlagUtil.ts +0 -30
  35. package/e2e/utils/garudaDropdownUtil.ts +0 -60
  36. package/e2e/utils/htmlEditorUtil.ts +0 -56
  37. package/e2e/utils/logCollectorUtil.ts +0 -59
  38. package/e2e/utils/setupMock.ts +0 -29
  39. package/e2e/utils/unmatchedBracesUtil.ts +0 -101
  40. package/e2e/utils/uploaders/fileServiceUploader.ts +0 -84
  41. package/e2e/utils/uploaders/uploader.ts +0 -20
  42. package/e2e/utils/virtualListUtil.ts +0 -115
@@ -1,133 +1,107 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import debugMode from './debugModeUtil';
4
-
5
- /**
6
- * Records request payloads (POST / PATCH / DELETE) sent from the UI during a
7
- * test run and saves them to JSON files under reports/debug/request-payloads/.
8
- *
9
- * Off by default; enabled only when FULL_DEBUG_MODE=true. When off, every
10
- * method is a no-op so there is zero overhead.
11
- *
12
- * One JSON file is written per test, containing an array of all the
13
- * POST / PATCH / DELETE requests captured while that test was running.
14
- */
15
-
16
- interface InitiatorFrame {
17
- functionName: string;
18
- url: string;
19
- line: number;
20
- }
21
-
22
- interface RequestInitiator {
23
- // 'script' (a JS handler fired it), 'parser', 'preflight', 'other', ...
24
- type: string;
25
- // Top JS call frames that triggered the request — i.e. which handler fired it.
26
- stack: InitiatorFrame[];
27
- }
28
-
29
- interface RecordedRequest {
30
- requestId: string;
31
- method: string;
32
- url: string;
33
- timestamp: string;
34
- postData: string | null;
35
- headers: Record<string, string>;
36
- // Which app code initiated the request (from CDP initiator).
37
- initiator: RequestInitiator | null;
38
- }
39
-
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const debugModeUtil_1 = __importDefault(require("./debugModeUtil"));
40
9
  const RECORDED_METHODS = ['POST', 'PATCH', 'DELETE', 'PUT'];
41
10
  // How many call frames to keep — enough to identify the handler without bloat.
42
11
  const MAX_INITIATOR_FRAMES = 3;
43
-
44
12
  class RequestRecorder {
45
- // Off by default; record only when capture is enabled for this module
46
- // (FULL_DEBUG_MODE=true and, if DEBUG_MODULES is set, module is listed).
47
- private enabled = debugMode.isCaptureEnabled();
48
- private outputDir = path.join(process.cwd(), 'reports', 'debug', 'request-payloads');
49
- private currentTestTitle = '';
50
- private buffer: RecordedRequest[] = [];
51
- private seenRequestIds = new Set<string>();
52
-
53
- isEnabled(): boolean {
13
+ constructor() {
14
+ // Off by default; record only when capture is enabled for this module
15
+ // (FULL_DEBUG_MODE=true and, if DEBUG_MODULES is set, module is listed).
16
+ this.enabled = debugModeUtil_1.default.isCaptureEnabled();
17
+ this.outputDir = path_1.default.join(process.cwd(), 'reports', 'debug', 'request-payloads');
18
+ this.currentTestTitle = '';
19
+ this.buffer = [];
20
+ this.seenRequestIds = new Set();
21
+ }
22
+ isEnabled() {
54
23
  return this.enabled;
55
24
  }
56
-
57
25
  /** Reset the buffer and remember the test we are currently recording for. */
58
- startTest(testTitle: string): void {
59
- if (!this.enabled) return;
26
+ startTest(testTitle) {
27
+ if (!this.enabled)
28
+ return;
60
29
  this.currentTestTitle = testTitle || 'unknown_test';
61
30
  this.buffer = [];
62
31
  this.seenRequestIds.clear();
63
32
  }
64
-
65
33
  /**
66
34
  * Capture a single request from a CDP Network.requestWillBeSent event.
67
35
  * Only POST / PATCH / DELETE requests matching baseUrl are recorded.
68
36
  * Large request bodies are fetched via Network.getRequestPostData when
69
37
  * the event payload does not inline them.
70
38
  */
71
- async record(params: any, baseUrl: string): Promise<void> {
72
- if (!this.enabled) return;
39
+ async record(params, baseUrl) {
40
+ var _a, _b, _c;
41
+ if (!this.enabled)
42
+ return;
73
43
  try {
74
- const request = params?.request;
75
- if (!request) return;
76
-
44
+ const request = params === null || params === void 0 ? void 0 : params.request;
45
+ if (!request)
46
+ return;
77
47
  const method = (request.method || '').toUpperCase();
78
- if (!RECORDED_METHODS.includes(method)) return;
79
- if (baseUrl && !String(request.url).startsWith(baseUrl)) return;
80
-
48
+ if (!RECORDED_METHODS.includes(method))
49
+ return;
50
+ if (baseUrl && !String(request.url).startsWith(baseUrl))
51
+ return;
81
52
  // CDP can emit requestWillBeSent more than once per request; dedupe by id.
82
53
  const requestId = String(params.requestId);
83
- if (this.seenRequestIds.has(requestId)) return;
54
+ if (this.seenRequestIds.has(requestId))
55
+ return;
84
56
  this.seenRequestIds.add(requestId);
85
-
86
- const entry: RecordedRequest = {
57
+ const entry = {
87
58
  requestId,
88
59
  method,
89
60
  url: request.url,
90
61
  timestamp: new Date().toISOString(),
91
- postData: request.postData ?? null,
92
- headers: request.headers ?? {},
93
- initiator: this.extractInitiator(params?.initiator),
62
+ postData: (_a = request.postData) !== null && _a !== void 0 ? _a : null,
63
+ headers: (_b = request.headers) !== null && _b !== void 0 ? _b : {},
64
+ initiator: this.extractInitiator(params === null || params === void 0 ? void 0 : params.initiator),
94
65
  };
95
66
  // Preserve ordering: push synchronously, backfill body if needed.
96
67
  this.buffer.push(entry);
97
-
98
68
  if (entry.postData == null && request.hasPostData) {
99
69
  try {
100
- const res: any = await browser.cdp('Network', 'getRequestPostData', {
70
+ const res = await browser.cdp('Network', 'getRequestPostData', {
101
71
  requestId: params.requestId,
102
72
  });
103
- entry.postData = res?.postData ?? null;
104
- } catch (err) {
73
+ entry.postData = (_c = res === null || res === void 0 ? void 0 : res.postData) !== null && _c !== void 0 ? _c : null;
74
+ }
75
+ catch (err) {
105
76
  console.log(`[requestRecorder] could not fetch post data: ${err}`);
106
77
  }
107
78
  }
108
- } catch (err) {
79
+ }
80
+ catch (err) {
109
81
  console.log(`[requestRecorder] failed to record request: ${err}`);
110
82
  }
111
83
  }
112
-
113
84
  /**
114
85
  * Reduce a CDP initiator to a compact, comparable shape: the request type
115
86
  * and the top few JS call frames (the handler that fired the request).
116
87
  * CDP line/column numbers are 0-based, so +1 for human-readable lines.
117
88
  */
118
- private extractInitiator(initiator: any): RequestInitiator | null {
119
- if (!initiator) return null;
120
- const frames: InitiatorFrame[] = [];
121
- const callFrames = initiator?.stack?.callFrames;
89
+ extractInitiator(initiator) {
90
+ var _a;
91
+ if (!initiator)
92
+ return null;
93
+ const frames = [];
94
+ const callFrames = (_a = initiator === null || initiator === void 0 ? void 0 : initiator.stack) === null || _a === void 0 ? void 0 : _a.callFrames;
122
95
  if (Array.isArray(callFrames)) {
123
96
  for (const f of callFrames.slice(0, MAX_INITIATOR_FRAMES)) {
124
97
  frames.push({
125
- functionName: f?.functionName || '(anonymous)',
126
- url: f?.url || '',
127
- line: typeof f?.lineNumber === 'number' ? f.lineNumber + 1 : -1,
98
+ functionName: (f === null || f === void 0 ? void 0 : f.functionName) || '(anonymous)',
99
+ url: (f === null || f === void 0 ? void 0 : f.url) || '',
100
+ line: typeof (f === null || f === void 0 ? void 0 : f.lineNumber) === 'number' ? f.lineNumber + 1 : -1,
128
101
  });
129
102
  }
130
- } else if (initiator.url) {
103
+ }
104
+ else if (initiator.url) {
131
105
  // parser-initiated requests carry url/lineNumber directly.
132
106
  frames.push({
133
107
  functionName: '',
@@ -137,24 +111,22 @@ class RequestRecorder {
137
111
  }
138
112
  return { type: initiator.type || 'other', stack: frames };
139
113
  }
140
-
141
114
  /** Write the buffered requests for the current test to a JSON file. */
142
- flushTest(): void {
143
- if (!this.enabled) return;
115
+ flushTest() {
116
+ if (!this.enabled)
117
+ return;
144
118
  try {
145
- if (this.buffer.length === 0) return;
146
-
119
+ if (this.buffer.length === 0)
120
+ return;
147
121
  const moduleName = process.env.module || 'unknown_module';
148
- const dir = path.join(this.outputDir, moduleName);
149
- fs.mkdirSync(dir, { recursive: true });
150
-
122
+ const dir = path_1.default.join(this.outputDir, moduleName);
123
+ fs_1.default.mkdirSync(dir, { recursive: true });
151
124
  const safeTitle = this.currentTestTitle
152
125
  .replace(/[^a-zA-Z0-9_-]+/g, '_')
153
126
  .slice(0, 120);
154
127
  // Stable filename (no timestamp) so the same test maps to the same
155
128
  // path across runs/zips — enables zip-to-zip payload diffing.
156
129
  const fileName = `${safeTitle}.json`;
157
-
158
130
  const payload = {
159
131
  test: this.currentTestTitle,
160
132
  module: moduleName,
@@ -162,17 +134,17 @@ class RequestRecorder {
162
134
  requestCount: this.buffer.length,
163
135
  requests: this.buffer,
164
136
  };
165
-
166
- fs.writeFileSync(path.join(dir, fileName), JSON.stringify(payload, null, 2));
167
- console.log(`[requestRecorder] saved ${this.buffer.length} request(s) to ${path.join(dir, fileName)}`);
168
- } catch (err) {
137
+ fs_1.default.writeFileSync(path_1.default.join(dir, fileName), JSON.stringify(payload, null, 2));
138
+ console.log(`[requestRecorder] saved ${this.buffer.length} request(s) to ${path_1.default.join(dir, fileName)}`);
139
+ }
140
+ catch (err) {
169
141
  console.log(`[requestRecorder] failed to write payload file: ${err}`);
170
- } finally {
142
+ }
143
+ finally {
171
144
  this.buffer = [];
172
145
  this.seenRequestIds.clear();
173
146
  this.currentTestTitle = '';
174
147
  }
175
148
  }
176
149
  }
177
-
178
- export default new RequestRecorder();
150
+ exports.default = new RequestRecorder();
@@ -1,80 +1,60 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import debugMode from './debugModeUtil';
4
-
5
- /**
6
- * Captures a full-page screenshot after every click and saves them under
7
- * reports/debug/screenshots/<module>/<test>/<NNN>_<command>.png.
8
- *
9
- * Off by default; enabled only when FULL_DEBUG_MODE=true. When off, every
10
- * method is a no-op so there is zero overhead.
11
- *
12
- * Driven by the WDIO afterCommand hook. Only click commands trigger a capture,
13
- * so other commands (typing, lookups, navigation) don't spam the report.
14
- *
15
- * Also writes a steps.json per test mapping each click (step index) to its
16
- * screenshot file and the wall-clock time it fired. The compare tool uses the
17
- * click times to attribute recorded request payloads to the click that
18
- * triggered them (request.timestamp falls between consecutive click times).
19
- */
20
-
21
- interface RecordedStep {
22
- step: number;
23
- file: string;
24
- command: string;
25
- clickTime: string;
26
- }
27
-
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const debugModeUtil_1 = __importDefault(require("./debugModeUtil"));
28
9
  // WDIO command names that represent a click worth screenshotting.
29
10
  const INTERACTION_COMMANDS = new Set([
30
11
  'click',
31
12
  'doubleClick',
32
13
  ]);
33
-
34
14
  // afterCommand fires before the post-click content has rendered; wait this long
35
15
  // so the screenshot reflects the settled page. Tunable via env.
36
16
  const SETTLE_MS = Number(process.env.FULL_DEBUG_SCREENSHOT_DELAY_MS) || 500;
37
-
38
17
  // Hard cap on a single CDP screenshot. The capture can hang indefinitely when it
39
18
  // fires while the page is mid-navigation (e.g. during login), which would block
40
19
  // the awaiting afterCommand hook and stall the whole run until the Mocha hook
41
20
  // timeout. Bounding it guarantees screenshots can never block automations.
42
21
  const CAPTURE_TIMEOUT_MS = Number(process.env.FULL_DEBUG_SCREENSHOT_TIMEOUT_MS) || 8000;
43
-
44
22
  class ScreenshotRecorder {
45
- private enabled = debugMode.isCaptureEnabled();
46
- private outputDir = path.join(process.cwd(), 'reports', 'debug', 'screenshots');
47
- private currentTestTitle = '';
48
- private counter = 0;
49
- // Last saved image; used to drop byte-identical duplicate captures.
50
- private lastImage: Buffer | null = null;
51
- // Guard against re-entrancy: our own cdp/screenshot calls also fire afterCommand.
52
- private capturing = false;
53
- // Per-test record of clicks → screenshot file + time, for request correlation.
54
- private steps: RecordedStep[] = [];
55
-
56
- isEnabled(): boolean {
23
+ constructor() {
24
+ this.enabled = debugModeUtil_1.default.isCaptureEnabled();
25
+ this.outputDir = path_1.default.join(process.cwd(), 'reports', 'debug', 'screenshots');
26
+ this.currentTestTitle = '';
27
+ this.counter = 0;
28
+ // Last saved image; used to drop byte-identical duplicate captures.
29
+ this.lastImage = null;
30
+ // Guard against re-entrancy: our own cdp/screenshot calls also fire afterCommand.
31
+ this.capturing = false;
32
+ // Per-test record of clicks → screenshot file + time, for request correlation.
33
+ this.steps = [];
34
+ }
35
+ isEnabled() {
57
36
  return this.enabled;
58
37
  }
59
-
60
38
  /** Reset the per-test counter and remember the current test. */
61
- startTest(testTitle: string): void {
62
- if (!this.enabled) return;
39
+ startTest(testTitle) {
40
+ if (!this.enabled)
41
+ return;
63
42
  this.currentTestTitle = testTitle || 'unknown_test';
64
43
  this.counter = 0;
65
44
  this.lastImage = null;
66
45
  this.steps = [];
67
46
  }
68
-
69
47
  /** Capture a full-page screenshot if commandName is a click. */
70
- async captureAfter(commandName: string): Promise<void> {
71
- if (!this.enabled || this.capturing) return;
72
- if (!INTERACTION_COMMANDS.has(commandName)) return;
48
+ async captureAfter(commandName) {
49
+ if (!this.enabled || this.capturing)
50
+ return;
51
+ if (!INTERACTION_COMMANDS.has(commandName))
52
+ return;
73
53
  // No active test means we're in the login/setup phase (the root before-all
74
54
  // hook). Capturing here is pointless and, worse, the CDP screenshot can hang
75
55
  // mid-navigation and block that hook until its Mocha timeout. Skip it.
76
- if (!this.currentTestTitle) return;
77
-
56
+ if (!this.currentTestTitle)
57
+ return;
78
58
  this.capturing = true;
79
59
  // Stamp the click time before the settle pause so it lines up with when
80
60
  // the click actually fired (and thus with the requests it triggered).
@@ -82,20 +62,18 @@ class ScreenshotRecorder {
82
62
  try {
83
63
  // Let the post-click content render before capturing.
84
64
  await browser.pause(SETTLE_MS);
85
-
86
65
  // captureBeyondViewport => full scrollable page, not just the viewport.
87
66
  // Bound the CDP call: it can hang indefinitely if it fires while the page
88
67
  // is mid-navigation, which would block the awaiting afterCommand hook.
89
- const capturePromise: Promise<any> = browser.cdp('Page', 'captureScreenshot', {
68
+ const capturePromise = browser.cdp('Page', 'captureScreenshot', {
90
69
  format: 'png',
91
70
  captureBeyondViewport: true,
92
71
  });
93
72
  // If the timeout wins, the CDP promise may still settle (or reject) later;
94
73
  // swallow it so a late rejection never surfaces as an unhandled rejection.
95
- capturePromise.catch(() => {});
96
-
97
- let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
98
- const res: any = await Promise.race([
74
+ capturePromise.catch(() => { });
75
+ let timeoutHandle;
76
+ const res = await Promise.race([
99
77
  capturePromise,
100
78
  new Promise((resolve) => {
101
79
  timeoutHandle = setTimeout(() => resolve(null), CAPTURE_TIMEOUT_MS);
@@ -104,71 +82,72 @@ class ScreenshotRecorder {
104
82
  // Clear the fallback timer once the race settles so a pending timeout
105
83
  // can't keep the Node event loop alive (delaying process exit by up to
106
84
  // CAPTURE_TIMEOUT_MS) when the CDP call won.
107
- if (timeoutHandle) clearTimeout(timeoutHandle);
108
- if (!res?.data) return;
109
-
85
+ if (timeoutHandle)
86
+ clearTimeout(timeoutHandle);
87
+ if (!(res === null || res === void 0 ? void 0 : res.data))
88
+ return;
110
89
  const image = Buffer.from(res.data, 'base64');
111
90
  // afterCommand fires twice per click; once settled both frames are
112
91
  // identical, so skip the byte-identical repeat.
113
- if (this.lastImage && this.lastImage.equals(image)) return;
92
+ if (this.lastImage && this.lastImage.equals(image))
93
+ return;
114
94
  this.lastImage = image;
115
-
116
95
  const moduleName = process.env.module || 'unknown_module';
117
96
  const safeTitle = (this.currentTestTitle || '_pre-test')
118
97
  .replace(/[^a-zA-Z0-9_-]+/g, '_')
119
98
  .slice(0, 120);
120
- const dir = path.join(this.outputDir, moduleName, safeTitle);
121
- fs.mkdirSync(dir, { recursive: true });
122
-
99
+ const dir = path_1.default.join(this.outputDir, moduleName, safeTitle);
100
+ fs_1.default.mkdirSync(dir, { recursive: true });
123
101
  this.counter += 1;
124
102
  const seq = String(this.counter).padStart(3, '0');
125
103
  const fileName = `${seq}_${commandName}.png`;
126
- fs.writeFileSync(path.join(dir, fileName), image);
127
-
104
+ fs_1.default.writeFileSync(path_1.default.join(dir, fileName), image);
128
105
  this.steps.push({
129
106
  step: this.counter,
130
107
  file: fileName,
131
108
  command: commandName,
132
109
  clickTime,
133
110
  });
134
- } catch (err) {
111
+ }
112
+ catch (err) {
135
113
  console.log(`[screenshotRecorder] failed to capture after ${commandName}: ${err}`);
136
- } finally {
114
+ }
115
+ finally {
137
116
  this.capturing = false;
138
117
  }
139
118
  }
140
-
141
119
  /**
142
120
  * Write the per-test steps.json alongside this test's screenshots, mapping
143
121
  * each click step to its screenshot file and the time it fired. Used by the
144
122
  * compare tool to attribute request payloads to the click that triggered
145
123
  * them. No-op when nothing was captured.
146
124
  */
147
- flushTest(): void {
148
- if (!this.enabled) return;
125
+ flushTest() {
126
+ if (!this.enabled)
127
+ return;
149
128
  try {
150
- if (this.steps.length === 0) return;
151
-
129
+ if (this.steps.length === 0)
130
+ return;
152
131
  const moduleName = process.env.module || 'unknown_module';
153
132
  const safeTitle = (this.currentTestTitle || '_pre-test')
154
133
  .replace(/[^a-zA-Z0-9_-]+/g, '_')
155
134
  .slice(0, 120);
156
- const dir = path.join(this.outputDir, moduleName, safeTitle);
157
- fs.mkdirSync(dir, { recursive: true });
158
-
135
+ const dir = path_1.default.join(this.outputDir, moduleName, safeTitle);
136
+ fs_1.default.mkdirSync(dir, { recursive: true });
159
137
  const payload = {
160
138
  test: this.currentTestTitle,
161
139
  module: moduleName,
162
140
  stepCount: this.steps.length,
163
141
  steps: this.steps,
164
142
  };
165
- fs.writeFileSync(path.join(dir, 'steps.json'), JSON.stringify(payload, null, 2));
166
- } catch (err) {
143
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'steps.json'), JSON.stringify(payload, null, 2));
144
+ }
145
+ catch (err) {
167
146
  console.log(`[screenshotRecorder] failed to write steps.json: ${err}`);
168
- } finally {
147
+ }
148
+ finally {
169
149
  this.steps = [];
170
150
  }
171
151
  }
172
152
  }
173
-
174
- export default new ScreenshotRecorder();
153
+ exports.default = new ScreenshotRecorder();
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mockResponse_1 = require("./mockResponse");
4
+ class setupMock {
5
+ async mockBeamerAPIForPopup() {
6
+ const beamerReq1 = await browser.mock("https://backend.getbeamer.com/initialize" + "**", {
7
+ method: "get",
8
+ });
9
+ beamerReq1.respond(mockResponse_1.beamerApiResp);
10
+ const beamerReq2 = await browser.mock("https://backend.getbeamer.com/numberFeatures" + "**", {
11
+ method: "get",
12
+ });
13
+ beamerReq2.respond([{}], {
14
+ statusCode: 404,
15
+ });
16
+ }
17
+ }
18
+ exports.default = new setupMock();
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.injectAndRestore = exports.focusEmailEditorAddNewLineAtEnd = exports.componentWithLabelErrorSpan = exports.invalidLabelErrorSpan = exports.invalidLabelErrorFooterContainer = exports.INVALID_LABEL_ERROR_TEXT = exports.UNSUPPORTED_TAG = exports.UNMATCHED_BRACES_SNIPPET = void 0;
7
+ const elementUtil_1 = __importDefault(require("./elementUtil"));
8
+ /** Invalid snippet used to trigger unmatched-braces validation across Engage and Adiona flows. */
9
+ exports.UNMATCHED_BRACES_SNIPPET = "{{ my_standard_tag";
10
+ /** Unsupported/unrecognised tag used to trigger the send-for-approval server-side validation error. */
11
+ exports.UNSUPPORTED_TAG = "{{my_invalid_standard_tag}}";
12
+ /** Full footer / inline error copy for standard-tag validation (SMS, MPush, Web Push, etc.). */
13
+ exports.INVALID_LABEL_ERROR_TEXT = "Invalid label, please close all curly braces";
14
+ /**
15
+ * Footer error container for "Invalid label…" type errors (SMS, Journey SMS).
16
+ * XPath: //div[contains(@class,'error-container')]//div[text()='<message>']
17
+ */
18
+ function invalidLabelErrorFooterContainer(errorMessage) {
19
+ return $(`//div[contains(@class,'error-container')]//div[text()='${errorMessage}']`);
20
+ }
21
+ exports.invalidLabelErrorFooterContainer = invalidLabelErrorFooterContainer;
22
+ /**
23
+ * Inline error span for "Invalid label…" (Viber, Zalo, Journey Zalo).
24
+ * XPath: //span[contains(@class,'error-message') and contains(.,'Invalid label')]
25
+ */
26
+ function invalidLabelErrorSpan() {
27
+ return $("//span[contains(@class,'error-message') and contains(.,'Invalid label')]");
28
+ }
29
+ exports.invalidLabelErrorSpan = invalidLabelErrorSpan;
30
+ /**
31
+ * Validation error span below a component-with-label input (MPush Title / Message).
32
+ * XPath: //div[contains(@class,'component-with-label-label') and normalize-space()='<label>']
33
+ * /ancestor::div[contains(@class,'component-with-label')]//span[contains(@class,'error-message')]
34
+ * Use for channels where the error appears inline under the input rather than in the footer.
35
+ */
36
+ function componentWithLabelErrorSpan(label) {
37
+ return $(`//div[contains(@class,'component-with-label-label') and normalize-space()='${label}']/ancestor::div[contains(@class,'component-with-label')]//span[contains(@class,'error-message')]`);
38
+ }
39
+ exports.componentWithLabelErrorSpan = componentWithLabelErrorSpan;
40
+ /**
41
+ * CodeMirror email editor: click the last cm-line at a stable left-edge offset to acquire focus,
42
+ * wait for the editor to be focused, then press End + Enter to position the cursor on a new last line.
43
+ * Call this before typing a snippet into the editor.
44
+ *
45
+ * @param codeMirrorEditor The CodeMirror content-editable div (aria-placeholder contains "Write your HTML email code here").
46
+ * @param codeMirrorLastLine The last `.cm-line` div inside that editor.
47
+ */
48
+ async function focusEmailEditorAddNewLineAtEnd(codeMirrorEditor, codeMirrorLastLine) {
49
+ await codeMirrorEditor.waitForDisplayed({ timeout: 90000 });
50
+ await codeMirrorLastLine.waitForDisplayed({ timeout: 10000 });
51
+ const size = await codeMirrorLastLine.getSize();
52
+ const xOffset = Math.round(2 - size.width / 2);
53
+ await codeMirrorLastLine.click({ x: xOffset, y: 0 });
54
+ await browser.waitUntil(async () => {
55
+ const focused = await browser.execute(() => {
56
+ const codeMirrorEditorEl = document.querySelector('div[aria-placeholder*="Write your HTML email code here"]');
57
+ if (!codeMirrorEditorEl)
58
+ return false;
59
+ const active = document.activeElement;
60
+ const editorFocused = active === codeMirrorEditorEl || (active && codeMirrorEditorEl.contains(active));
61
+ const cmEditor = codeMirrorEditorEl.closest(".cm-editor");
62
+ const hasCmFocused = (cmEditor && cmEditor.classList.contains("cm-focused")) ||
63
+ (active && active.classList.contains("cm-focused"));
64
+ return !!(editorFocused || hasCmFocused);
65
+ });
66
+ return focused === true;
67
+ }, {
68
+ timeout: 10000,
69
+ timeoutMsg: "CodeMirror editor did not receive focus after clicking last line",
70
+ interval: 100,
71
+ });
72
+ await browser.keys(["End"]);
73
+ await browser.keys(["Enter"]);
74
+ }
75
+ exports.focusEmailEditorAddNewLineAtEnd = focusEmailEditorAddNewLineAtEnd;
76
+ /**
77
+ * Inject unmatched-braces snippet, try submit (if clickable), assert via callback, then restore field.
78
+ * Not used for Email (CodeMirror debounce / waitUntil sequencing is bespoke).
79
+ */
80
+ async function injectAndRestore(targetInput, submitButton, originalValue, assertFn) {
81
+ await elementUtil_1.default.enterText(targetInput, exports.UNMATCHED_BRACES_SNIPPET);
82
+ await elementUtil_1.default.clickIfClickable(submitButton);
83
+ await assertFn();
84
+ await elementUtil_1.default.clearByBackspace(exports.UNMATCHED_BRACES_SNIPPET, targetInput);
85
+ await elementUtil_1.default.typeText(targetInput, originalValue);
86
+ }
87
+ exports.injectAndRestore = injectAndRestore;