@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
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const assert = __importStar(require("assert"));
27
+ class AssertionUtil {
28
+ async assertValue(element, textToBeValidated) {
29
+ assert.strictEqual(await element.getText(), textToBeValidated);
30
+ }
31
+ async assertElementExistence(element) {
32
+ try {
33
+ assert.strictEqual(await element.isDisplayed(), true);
34
+ }
35
+ catch (error) {
36
+ throw new Error(error);
37
+ }
38
+ }
39
+ }
40
+ exports.default = new AssertionUtil();
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Reads feature flags from window.capAuth.accessibleFeatures — the same source
4
+ * that Auth.hasFeatureAccess() uses in the app (cap-ui-utils/auth/hasFeatureAccess.js).
5
+ *
6
+ * Call loadFeatureFlags() once after the app has loaded (e.g. after openAudiencePage),
7
+ * then use hasFeatureAccess() anywhere in the test run.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ let _accessibleFeatures = [];
11
+ async function loadFeatureFlags() {
12
+ // window.capAuth.accessibleFeatures is populated async after the app boots.
13
+ // Wait until it's array-shaped (up to 30s) before caching — an empty array
14
+ // is a valid "no features enabled" state, not a sign it's still loading.
15
+ await browser.waitUntil(async () => {
16
+ const features = await browser.execute(() => { var _a; return (_a = window.capAuth) === null || _a === void 0 ? void 0 : _a.accessibleFeatures; });
17
+ return Array.isArray(features);
18
+ }, { timeout: 30000, interval: 1000, timeoutMsg: '[featureFlags] window.capAuth.accessibleFeatures never populated' });
19
+ _accessibleFeatures = await browser.execute(() => { var _a; return ((_a = window.capAuth) === null || _a === void 0 ? void 0 : _a.accessibleFeatures) || []; });
20
+ console.log(`[featureFlags] loaded ${_accessibleFeatures.length} features`);
21
+ }
22
+ function hasFeatureAccess(feature) {
23
+ return _accessibleFeatures.includes(feature);
24
+ }
25
+ exports.default = { loadFeatureFlags, hasFeatureAccess };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class GarudaDropdownUtil {
4
+ treeOptionByText(text, index = 1, altText) {
5
+ const textMatch = altText
6
+ ? `normalize-space()="${text}" or normalize-space()="${altText}"`
7
+ : `normalize-space()="${text}"`;
8
+ const position = index === 'last' ? 'last()' : index;
9
+ return $(`(//span[contains(@class,"ant-select-tree-node-content-wrapper")][.//div[${textMatch}]])[${position}]`);
10
+ }
11
+ unifiedOptionByText(text) {
12
+ return $(`//div[contains(@class,"cap-unified-select-popup")]//div[.//div[text()="${text}"] and @role="treeitem"]`);
13
+ }
14
+ listOptionByLabel(label, index = 1) {
15
+ return $(`(//ul[@role="listbox"]//li[@role="option" and @label="${label}"])[${index}]`);
16
+ }
17
+ selectOptionByText(text, index = 1) {
18
+ return $(`(//div[contains(@class,"ant-select-item-option")][.//div[contains(@class,"ant-select-item-option-content")][normalize-space()="${text}"]])[${index}]`);
19
+ }
20
+ treeOption(index = 1) {
21
+ const position = index === 'last' ? 'last()' : index;
22
+ return $(`(//span[contains(@class,"ant-select-tree-node-content-wrapper")][normalize-space(.)])[${position}]`);
23
+ }
24
+ listOptionByLabelInPopup(ariaControlId, label) {
25
+ return $(`//div[contains(@id,"${ariaControlId}")]//ul//li[@role="option" and @label="${label}"]`);
26
+ }
27
+ menuItemByText(text, index = 1) {
28
+ return $(`(//li[contains(@class,"ant-dropdown-menu-item")][.//span[normalize-space()="${text}"]])[${index}]`);
29
+ }
30
+ unifiedSelectConfirmButton() {
31
+ return $(`//button[contains(@class,"cap-unified-select-confirm-button")]`);
32
+ }
33
+ }
34
+ exports.default = new GarudaDropdownUtil();
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Helpers for the antd v6 contenteditable HTML editor.
4
+ * After bulk HTML injection, the caret remains outside the `<body>` tag, causing label
5
+ * insertions at the wrong position. `placeCaretInsideBody` locates the `<body>` position
6
+ * across text nodes and updates the Selection range correctly.
7
+ *
8
+ * Caller passes the WDIO editor element (defined as a page selector), so this util
9
+ * does not own selector strings.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.placeCaretInsideBody = void 0;
13
+ async function placeCaretInsideBody(editor) {
14
+ return browser.execute((node) => {
15
+ if (!node)
16
+ return false;
17
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
18
+ const nodes = [];
19
+ const starts = [];
20
+ let combined = '';
21
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
22
+ const t = n;
23
+ starts.push(combined.length);
24
+ combined += t.nodeValue || '';
25
+ nodes.push(t);
26
+ }
27
+ if (!nodes.length)
28
+ return false;
29
+ const open = combined.match(/<\s*body\b[^>]*>/i);
30
+ if (!open || open.index === undefined)
31
+ return false;
32
+ let idx = open.index + open[0].length;
33
+ while (idx < combined.length && /\s/.test(combined.charAt(idx)))
34
+ idx++;
35
+ let nodeIdx = 0;
36
+ for (let i = 0; i < nodes.length; i++) {
37
+ const end = starts[i] + (nodes[i].nodeValue || '').length;
38
+ if (idx <= end) {
39
+ nodeIdx = i;
40
+ break;
41
+ }
42
+ nodeIdx = i;
43
+ }
44
+ const target = nodes[nodeIdx];
45
+ const offset = Math.max(0, Math.min(idx - starts[nodeIdx], (target.nodeValue || '').length));
46
+ node.focus();
47
+ const range = document.createRange();
48
+ range.setStart(target, offset);
49
+ range.collapse(true);
50
+ const selection = window.getSelection();
51
+ if (!selection)
52
+ return false;
53
+ selection.removeAllRanges();
54
+ selection.addRange(range);
55
+ return true;
56
+ }, editor);
57
+ }
58
+ exports.placeCaretInsideBody = placeCaretInsideBody;
59
+ exports.default = { placeCaretInsideBody };
@@ -0,0 +1,65 @@
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 supertest_1 = __importDefault(require("supertest"));
7
+ const wdio_commands_1 = __importDefault(require("@rpii/wdio-commands"));
8
+ class LogCollectorUtil {
9
+ constructor() {
10
+ this.callCount = 0;
11
+ }
12
+ async collectLogs(runId, test, error, duration, passed, skipped = false) {
13
+ this.callCount += 1;
14
+ let validationMessage;
15
+ let status;
16
+ if (passed) {
17
+ status = 'passed';
18
+ validationMessage = '';
19
+ }
20
+ else if (skipped) {
21
+ status = 'skipped';
22
+ validationMessage = '';
23
+ }
24
+ else {
25
+ wdio_commands_1.default.logScreenshot("Exception logged: " + error);
26
+ status = 'failed';
27
+ if (error === undefined) {
28
+ validationMessage = 'cannot capture error';
29
+ }
30
+ else {
31
+ validationMessage = error.toString().replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
32
+ }
33
+ }
34
+ // this.recordBrowserLogs()
35
+ const baseUrl = 'http://127.0.0.1:4000';
36
+ const request = (0, supertest_1.default)(baseUrl);
37
+ let exeTime = duration / 1000;
38
+ let message = { [runId]: { [test.parent]: { [test.title]: { "status": status,
39
+ "time": exeTime.toString(), "validataionMessage": validationMessage,
40
+ "total": this.callCount } } } };
41
+ const messagePayload = { "result": message };
42
+ try {
43
+ const response = await request
44
+ .post('/logger')
45
+ .timeout({ deadline: 10000 })
46
+ .send(messagePayload);
47
+ console.log('API Response', response.body);
48
+ }
49
+ catch (err) {
50
+ // The in-pod log collector can be slow or absent (e.g. skipLogCollector);
51
+ // a logging failure must never fail the test itself.
52
+ console.log('logCollector POST failed (non-fatal):', err.message);
53
+ }
54
+ console.log('Message', message);
55
+ }
56
+ async recordBrowserLogs() {
57
+ const logTypes = Promise.resolve(browser.getLogs('browser')).then(function (data) { return data; });
58
+ logTypes.then(data => (JSON.stringify(data.filter(function getObj(logs) {
59
+ if (logs['level'] === 'SEVERE' || logs['level'] === 'ERROR') {
60
+ wdio_commands_1.default.logMessage((logs['level'] + ": " + JSON.stringify(logs)));
61
+ }
62
+ }))));
63
+ }
64
+ }
65
+ exports.default = new LogCollectorUtil();
@@ -1,4 +1,7 @@
1
- export const beamerApiResp = {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.beamerApiResp = void 0;
4
+ exports.beamerApiResp = {
2
5
  "activateAutoRefresh": true,
3
6
  "autoRefreshTimeout": 12000000,
4
7
  "defaultSelectorColor": "#50ac44",
@@ -21,4 +24,4 @@ export const beamerApiResp = {
21
24
  "showLinkInSnippet": false,
22
25
  "topDomain": "capillarytech.com",
23
26
  "updatesDelay": 300000
24
- }
27
+ };
@@ -1,12 +1,34 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { execFile, execFileSync } from 'child_process';
4
- import { promisify } from 'util';
5
- import { Uploader } from './uploaders/uploader';
6
- import { FileServiceUploader } from './uploaders/fileServiceUploader';
7
-
8
- const execFileAsync = promisify(execFile);
9
-
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const fs = __importStar(require("fs"));
27
+ const path = __importStar(require("path"));
28
+ const child_process_1 = require("child_process");
29
+ const util_1 = require("util");
30
+ const fileServiceUploader_1 = require("./uploaders/fileServiceUploader");
31
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
10
32
  /**
11
33
  * Zips the recorded debug artifacts (request payloads + screenshots) into a
12
34
  * single .zip and uploads it via a pluggable Uploader (Capillary File Service
@@ -16,95 +38,86 @@ const execFileAsync = promisify(execFile);
16
38
  * registered when FULL_DEBUG_MODE is enabled).
17
39
  */
18
40
  class ReportUploader {
19
- private debugDir = path.join(process.cwd(), 'reports', 'debug');
20
- // Upper bound for the whole upload step; on expiry we log and let the run end.
21
- private uploadTimeoutMs = Number(process.env.UPLOAD_TIMEOUT_MS) || 90000;
22
-
23
- private getUploader(): Uploader {
24
- return new FileServiceUploader();
41
+ constructor() {
42
+ this.debugDir = path.join(process.cwd(), 'reports', 'debug');
43
+ // Upper bound for the whole upload step; on expiry we log and let the run end.
44
+ this.uploadTimeoutMs = Number(process.env.UPLOAD_TIMEOUT_MS) || 90000;
45
+ }
46
+ getUploader() {
47
+ return new fileServiceUploader_1.FileServiceUploader();
25
48
  }
26
-
27
49
  /** Remove prior-run artifacts so each archive contains only the current run. */
28
- clearPreviousArtifacts(): void {
50
+ clearPreviousArtifacts() {
29
51
  try {
30
52
  if (fs.existsSync(this.debugDir)) {
31
53
  fs.rmSync(this.debugDir, { recursive: true, force: true });
32
54
  console.log('[reportUploader] cleared previous debug artifacts');
33
55
  }
34
- } catch (err) {
56
+ }
57
+ catch (err) {
35
58
  console.log(`[reportUploader] failed to clear previous artifacts: ${err}`);
36
59
  }
37
60
  }
38
-
39
- async archiveAndUpload(): Promise<void> {
61
+ async archiveAndUpload() {
40
62
  try {
41
63
  if (!fs.existsSync(this.debugDir) || fs.readdirSync(this.debugDir).length === 0) {
42
64
  console.log('[reportUploader] no debug artifacts recorded — nothing to upload');
43
65
  return;
44
66
  }
45
-
46
67
  // Bundle run metadata + a snapshot of the automation code so a later
47
68
  // diff can tell whether a payload change came from the lib upgrade or
48
69
  // from the tests themselves.
49
70
  this.writeMetaAndCode();
50
-
51
71
  const archivePath = await this.createArchive();
52
72
  console.log(`[reportUploader] created archive ${archivePath}`);
53
-
54
73
  const uploader = this.getUploader();
55
74
  if (!uploader.isConfigured()) {
56
75
  console.log(`[reportUploader] upload skipped — missing config: ${uploader.missingConfigMessage()}. Archive kept locally at ${archivePath}`);
57
76
  return;
58
77
  }
59
-
60
78
  // Belt-and-suspenders: even with the uploader's own network timeout,
61
79
  // never let the upload block the run's onComplete hook (a hung upload
62
80
  // would keep the pod alive forever). On timeout we log and move on so
63
81
  // the pod terminates exactly as it did before this feature existed.
64
- const location = await this.withTimeout(
65
- uploader.upload(archivePath, path.basename(archivePath)),
66
- this.uploadTimeoutMs,
67
- 'upload',
68
- );
82
+ const location = await this.withTimeout(uploader.upload(archivePath, path.basename(archivePath)), this.uploadTimeoutMs, 'upload');
69
83
  console.log(`[reportUploader] uploaded request payloads to ${uploader.name}: ${location}`);
70
- } catch (err) {
84
+ }
85
+ catch (err) {
71
86
  console.log(`[reportUploader] archive/upload failed (continuing so the run can exit): ${err}`);
72
87
  }
73
88
  }
74
-
75
89
  /** Reject after ms so a stuck upload can never block process shutdown. */
76
- private withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
77
- let timer: NodeJS.Timeout;
78
- const timeout = new Promise<never>((_, reject) => {
90
+ withTimeout(promise, ms, label) {
91
+ let timer;
92
+ const timeout = new Promise((_, reject) => {
93
+ var _a;
79
94
  timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
80
95
  // Don't let this timer itself keep the event loop alive.
81
- timer.unref?.();
96
+ (_a = timer.unref) === null || _a === void 0 ? void 0 : _a.call(timer);
82
97
  });
83
- return Promise.race([promise, timeout]).finally(() => clearTimeout(timer)) as Promise<T>;
98
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
84
99
  }
85
-
86
100
  /** Run a git command and return its trimmed stdout, or '' on failure. */
87
- private git(args: string[]): string {
101
+ git(args) {
88
102
  try {
89
- return execFileSync('git', args, { cwd: process.cwd() }).toString().trim();
90
- } catch {
103
+ return (0, child_process_1.execFileSync)('git', args, { cwd: process.cwd() }).toString().trim();
104
+ }
105
+ catch {
91
106
  return '';
92
107
  }
93
108
  }
94
-
95
109
  /**
96
110
  * Write debug/meta.json (run identity + git state) and copy the automation
97
111
  * code for this module into debug/code/. The compare tool reads these to
98
112
  * flag when two archives were produced from different code, so payload diffs
99
113
  * aren't misattributed to the UI library upgrade.
100
114
  */
101
- private writeMetaAndCode(): void {
115
+ writeMetaAndCode() {
102
116
  try {
103
117
  const moduleName = process.env.module || 'all';
104
118
  const commit = this.git(['rev-parse', 'HEAD']);
105
119
  const branch = this.git(['rev-parse', '--abbrev-ref', 'HEAD']);
106
120
  const dirty = this.git(['status', '--porcelain']).length > 0;
107
-
108
121
  const meta = {
109
122
  cluster: process.env.cluster || 'unknown-cluster',
110
123
  module: moduleName,
@@ -112,11 +125,7 @@ class ReportUploader {
112
125
  recordedAt: new Date().toISOString(),
113
126
  git: { commit, branch, dirty },
114
127
  };
115
- fs.writeFileSync(
116
- path.join(this.debugDir, 'meta.json'),
117
- JSON.stringify(meta, null, 2),
118
- );
119
-
128
+ fs.writeFileSync(path.join(this.debugDir, 'meta.json'), JSON.stringify(meta, null, 2));
120
129
  // Snapshot only this module's automation code (small); never the whole
121
130
  // src/ tree (~110M). Page objects + specs are enough to explain a diff.
122
131
  const codeDir = path.join(this.debugDir, 'code');
@@ -126,47 +135,41 @@ class ReportUploader {
126
135
  ];
127
136
  for (const rel of sources) {
128
137
  const abs = path.join(process.cwd(), rel);
129
- if (!fs.existsSync(abs)) continue;
138
+ if (!fs.existsSync(abs))
139
+ continue;
130
140
  const dest = path.join(codeDir, rel);
131
141
  fs.mkdirSync(path.dirname(dest), { recursive: true });
132
142
  fs.cpSync(abs, dest, { recursive: true });
133
143
  }
134
- } catch (err) {
144
+ }
145
+ catch (err) {
135
146
  console.log(`[reportUploader] failed to write meta/code snapshot: ${err}`);
136
147
  }
137
148
  }
138
-
139
149
  /** Resolve the test type (suite) from the `--suite <name>` CLI flag. */
140
- private getTestType(): string {
150
+ getTestType() {
141
151
  const argv = process.argv;
142
152
  const idx = argv.findIndex((a) => a === '--suite');
143
- if (idx !== -1 && argv[idx + 1]) return argv[idx + 1];
153
+ if (idx !== -1 && argv[idx + 1])
154
+ return argv[idx + 1];
144
155
  const inline = argv.find((a) => a.startsWith('--suite='));
145
- if (inline) return inline.split('=')[1];
156
+ if (inline)
157
+ return inline.split('=')[1];
146
158
  return 'all';
147
159
  }
148
-
149
- private async createArchive(): Promise<string> {
160
+ async createArchive() {
150
161
  const cluster = process.env.cluster || 'unknown-cluster';
151
162
  const moduleName = process.env.module || 'all';
152
163
  const testType = this.getTestType();
153
-
154
164
  // date = YYYY-MM-DD, time = HH-MM-SS (UTC, from ISO timestamp)
155
165
  const [datePart, timePartRaw] = new Date().toISOString().split('T');
156
166
  const timePart = timePartRaw.split('.')[0].replace(/:/g, '-');
157
-
158
167
  const archiveName = `wdio-ui-${cluster}_${moduleName}_${testType}_${datePart}_${timePart}.zip`;
159
168
  const archivePath = path.join(process.cwd(), 'reports', archiveName);
160
-
161
169
  // Run zip from the parent dir so the archive contains the debug/ folder
162
170
  // with relative paths, not absolute ones.
163
- await execFileAsync(
164
- 'zip',
165
- ['-r', '-q', archivePath, path.basename(this.debugDir)],
166
- { cwd: path.dirname(this.debugDir) },
167
- );
171
+ await execFileAsync('zip', ['-r', '-q', archivePath, path.basename(this.debugDir)], { cwd: path.dirname(this.debugDir) });
168
172
  return archivePath;
169
173
  }
170
174
  }
171
-
172
- export default new ReportUploader();
175
+ exports.default = new ReportUploader();