@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.
- package/e2e/constants/common.js +22 -0
- package/e2e/index.js +65 -0
- package/e2e/pages/common/base.page.js +13 -0
- package/e2e/pages/common/constant.js +5 -0
- package/e2e/pages/common/login.page.js +95 -0
- package/e2e/services/{lockService.ts → lockService.js} +63 -67
- package/e2e/utils/{antdVersionUtil.ts → antdVersionUtil.js} +24 -32
- package/e2e/utils/{automationBypassUtil.ts → automationBypassUtil.js} +11 -8
- package/e2e/utils/{debugModeUtil.ts → debugModeUtil.js} +9 -7
- package/e2e/utils/deletionRegistry.js +16 -0
- package/e2e/utils/{elementUtil.ts → elementUtil.js} +224 -296
- package/e2e/utils/expectUtil.js +40 -0
- package/e2e/utils/featureFlagUtil.js +25 -0
- package/e2e/utils/garudaDropdownUtil.js +34 -0
- package/e2e/utils/htmlEditorUtil.js +59 -0
- package/e2e/utils/logCollectorUtil.js +65 -0
- package/e2e/utils/{mockResponse.ts → mockResponse.js} +5 -2
- package/e2e/utils/{reportUploader.ts → reportUploader.js} +69 -66
- package/e2e/utils/{requestRecorderUtil.ts → requestRecorderUtil.js} +69 -97
- package/e2e/utils/{screenshotRecorderUtil.ts → screenshotRecorderUtil.js} +61 -82
- package/e2e/utils/setupMock.js +18 -0
- package/e2e/utils/unmatchedBracesUtil.js +87 -0
- package/e2e/utils/uploaders/fileServiceUploader.js +113 -0
- package/e2e/utils/uploaders/uploader.js +2 -0
- package/e2e/utils/virtualListUtil.js +74 -0
- package/package.json +1 -1
- package/e2e/constants/common.ts +0 -23
- package/e2e/index.ts +0 -30
- package/e2e/pages/common/base.page.ts +0 -11
- package/e2e/pages/common/constant.ts +0 -2
- package/e2e/pages/common/login.page.ts +0 -95
- package/e2e/utils/deletionRegistry.ts +0 -15
- package/e2e/utils/expectUtil.ts +0 -19
- package/e2e/utils/featureFlagUtil.ts +0 -30
- package/e2e/utils/garudaDropdownUtil.ts +0 -60
- package/e2e/utils/htmlEditorUtil.ts +0 -56
- package/e2e/utils/logCollectorUtil.ts +0 -59
- package/e2e/utils/setupMock.ts +0 -29
- package/e2e/utils/unmatchedBracesUtil.ts +0 -101
- package/e2e/utils/uploaders/fileServiceUploader.ts +0 -84
- package/e2e/utils/uploaders/uploader.ts +0 -20
- 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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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()
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
|
|
77
|
-
let timer
|
|
78
|
-
const timeout = new Promise
|
|
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))
|
|
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
|
-
|
|
101
|
+
git(args) {
|
|
88
102
|
try {
|
|
89
|
-
return execFileSync('git', args, { cwd: process.cwd() }).toString().trim();
|
|
90
|
-
}
|
|
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
|
-
|
|
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))
|
|
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
|
-
}
|
|
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
|
-
|
|
150
|
+
getTestType() {
|
|
141
151
|
const argv = process.argv;
|
|
142
152
|
const idx = argv.findIndex((a) => a === '--suite');
|
|
143
|
-
if (idx !== -1 && 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)
|
|
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();
|