@capillarytech/cap-ui-utils 3.1.0 → 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 (44) 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} +71 -70
  7. package/e2e/services/locks/.gitkeep +0 -0
  8. package/e2e/utils/{antdVersionUtil.ts → antdVersionUtil.js} +24 -32
  9. package/e2e/utils/{automationBypassUtil.ts → automationBypassUtil.js} +11 -8
  10. package/e2e/utils/{debugModeUtil.ts → debugModeUtil.js} +9 -7
  11. package/e2e/utils/deletionRegistry.js +16 -0
  12. package/e2e/utils/{elementUtil.ts → elementUtil.js} +226 -298
  13. package/e2e/utils/expectUtil.js +40 -0
  14. package/e2e/utils/featureFlagUtil.js +25 -0
  15. package/e2e/utils/garudaDropdownUtil.js +34 -0
  16. package/e2e/utils/htmlEditorUtil.js +59 -0
  17. package/e2e/utils/logCollectorUtil.js +65 -0
  18. package/e2e/utils/{mockResponse.ts → mockResponse.js} +5 -2
  19. package/e2e/utils/{reportUploader.ts → reportUploader.js} +69 -66
  20. package/e2e/utils/{requestRecorderUtil.ts → requestRecorderUtil.js} +69 -97
  21. package/e2e/utils/{screenshotRecorderUtil.ts → screenshotRecorderUtil.js} +61 -82
  22. package/e2e/utils/setupMock.js +18 -0
  23. package/e2e/utils/unmatchedBracesUtil.js +87 -0
  24. package/e2e/utils/uploaders/fileServiceUploader.js +113 -0
  25. package/e2e/utils/uploaders/uploader.js +2 -0
  26. package/e2e/utils/virtualListUtil.js +74 -0
  27. package/package.json +1 -1
  28. package/e2e/constants/common.ts +0 -23
  29. package/e2e/index.ts +0 -30
  30. package/e2e/pages/common/base.page.ts +0 -11
  31. package/e2e/pages/common/constant.ts +0 -2
  32. package/e2e/pages/common/login.page.ts +0 -95
  33. package/e2e/services/locks/beeTemplateReady.signal +0 -1
  34. package/e2e/utils/deletionRegistry.ts +0 -15
  35. package/e2e/utils/expectUtil.ts +0 -19
  36. package/e2e/utils/featureFlagUtil.ts +0 -30
  37. package/e2e/utils/garudaDropdownUtil.ts +0 -60
  38. package/e2e/utils/htmlEditorUtil.ts +0 -56
  39. package/e2e/utils/logCollectorUtil.ts +0 -52
  40. package/e2e/utils/setupMock.ts +0 -29
  41. package/e2e/utils/unmatchedBracesUtil.ts +0 -101
  42. package/e2e/utils/uploaders/fileServiceUploader.ts +0 -84
  43. package/e2e/utils/uploaders/uploader.ts +0 -20
  44. package/e2e/utils/virtualListUtil.ts +0 -115
@@ -0,0 +1,113 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.FileServiceUploader = void 0;
30
+ const fs = __importStar(require("fs"));
31
+ const axios_1 = __importDefault(require("axios"));
32
+ /**
33
+ * Uploads a file to Capillary's internal File Service, mirroring the
34
+ * `uploadData` flow in arya's arya-fileservice-sdk: a raw-binary POST to
35
+ * POST {baseUrl}v2/{namespace}/?fileName={name}&fileNameEncoding=false
36
+ * with the file bytes as the body. The service stores the object (in S3 under
37
+ * the hood) and returns JSON containing file_path / secure_file_path.
38
+ *
39
+ * Config via env vars:
40
+ * FILE_SERVICE_URL - base URL. Defaults to the in-cluster DNS
41
+ * http://file-service.default/ which is ONLY
42
+ * reachable from inside the Capillary cluster.
43
+ * Set this to a reachable host when running
44
+ * outside the cluster.
45
+ * FILE_SERVICE_NAMESPACE - target namespace (must be configured on the
46
+ * file-service side to allow the archive's
47
+ * extension/mime-type and size). Defaults to
48
+ * 'import', which is provisioned on the crm
49
+ * clusters and permits .zip (it stores under a
50
+ * generated UUID key, so use the returned path).
51
+ * FILE_SERVICE_ORG_ID - optional org id (X-CAP-API-AUTH-ORG-ID header)
52
+ * FILE_SERVICE_PUBLIC - "false" to upload as PRIVATE (default PUBLIC)
53
+ *
54
+ * Note: the arya SDK sends no auth header — the service relies on in-cluster
55
+ * network isolation. There is no external/authenticated route here.
56
+ */
57
+ class FileServiceUploader {
58
+ constructor() {
59
+ this.name = 'Capillary File Service';
60
+ this.baseUrl = process.env.FILE_SERVICE_URL || 'http://file-service.default/';
61
+ // Defaults to the 'import' namespace: it is provisioned on the crm clusters
62
+ // (unlike 'default', which does NOT exist server-side and 500s with
63
+ // NoSuchNamespaceException) and permits .zip uploads. Note it stores under a
64
+ // server-generated UUID key, so retrieve via the returned file_path, not the
65
+ // original filename. Override per env with FILE_SERVICE_NAMESPACE.
66
+ this.namespace = process.env.FILE_SERVICE_NAMESPACE || 'import';
67
+ this.orgId = process.env.FILE_SERVICE_ORG_ID || '';
68
+ this.isPublic = process.env.FILE_SERVICE_PUBLIC !== 'false';
69
+ // Hard network timeout so an unresponsive file-service can never hang the
70
+ // run's onComplete hook (which would keep the pod alive forever).
71
+ this.timeoutMs = Number(process.env.FILE_SERVICE_TIMEOUT_MS) || 60000;
72
+ }
73
+ isConfigured() {
74
+ return Boolean(this.baseUrl && this.namespace);
75
+ }
76
+ missingConfigMessage() {
77
+ const missing = [];
78
+ if (!this.baseUrl)
79
+ missing.push('FILE_SERVICE_URL');
80
+ if (!this.namespace)
81
+ missing.push('FILE_SERVICE_NAMESPACE');
82
+ return missing.join(', ');
83
+ }
84
+ async upload(localFilePath, remoteName) {
85
+ var _a;
86
+ const base = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`;
87
+ const url = `${base}v2/${this.namespace}/?fileName=${encodeURI(remoteName)}&fileNameEncoding=false`;
88
+ const headers = { 'Content-Type': 'application/zip' };
89
+ if (this.isPublic)
90
+ headers['X-CAP-FS-ACL'] = 'PUBLIC';
91
+ if (this.orgId)
92
+ headers['X-CAP-API-AUTH-ORG-ID'] = String(this.orgId);
93
+ const body = fs.readFileSync(localFilePath);
94
+ const res = await axios_1.default.post(url, body, {
95
+ headers,
96
+ maxBodyLength: Infinity,
97
+ maxContentLength: Infinity,
98
+ timeout: this.timeoutMs,
99
+ });
100
+ // The file-service returns HTTP 200 even when the upload fails (e.g.
101
+ // NoSuchNamespaceException), carrying the error in the JSON body. So a
102
+ // 200 is NOT proof of success — only a real file path is. Treat a body
103
+ // without one as a failure so the run doesn't silently report success.
104
+ const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
105
+ const location = (data === null || data === void 0 ? void 0 : data.secure_file_path) || (data === null || data === void 0 ? void 0 : data.file_path) || ((_a = data === null || data === void 0 ? void 0 : data.file) === null || _a === void 0 ? void 0 : _a.s3_token);
106
+ if (!location) {
107
+ const body = typeof res.data === 'string' ? res.data : JSON.stringify(res.data);
108
+ throw new Error(`file-service returned no file path (namespace='${this.namespace}'): ${body}`);
109
+ }
110
+ return location;
111
+ }
112
+ }
113
+ exports.FileServiceUploader = FileServiceUploader;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Helpers for antd v6 virtualized lists (`rc-virtual-list`).
4
+ *
5
+ * The list renders only a window of DOM rows at a time. A row that exists in the data
6
+ * model but is outside that window has no DOM node, so `waitForDisplayed` / `click`
7
+ * through WDIO never sees it. These helpers drive the scroll container in the browser
8
+ * until the row is rendered, then click the target directly.
9
+ *
10
+ * Works for both `ant-select-tree` (TreeSelect) and `ant-select-item-option` (Select).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.scrollAndClickVirtualOption = void 0;
14
+ /**
15
+ * Scroll a virtualized antd list inside `containerSelector` until a row whose trimmed
16
+ * textContent equals `label` is rendered, then click it (or its checkbox).
17
+ *
18
+ * Returns true on success, throws on timeout.
19
+ */
20
+ async function scrollAndClickVirtualOption(label, opts = {}) {
21
+ const { containerSelector, clickCheckbox = true, timeout = 15000, interval = 300, } = opts;
22
+ await browser.waitUntil(async () => {
23
+ return await browser.execute((containerSel, targetLabel, useCheckbox) => {
24
+ const root = containerSel
25
+ ? document.querySelector(containerSel) || document
26
+ : document;
27
+ const ROW_SELECTOR = '[role="treeitem"], .ant-select-tree-treenode, .ant-select-item-option';
28
+ const findRow = () => {
29
+ const rows = Array.from(root.querySelectorAll(ROW_SELECTOR));
30
+ for (const r of rows) {
31
+ if ((r.textContent || '').trim() === targetLabel)
32
+ return r;
33
+ }
34
+ return null;
35
+ };
36
+ // If already rendered, click and return.
37
+ let row = findRow();
38
+ if (row) {
39
+ const checkbox = useCheckbox
40
+ ? row.querySelector('.ant-select-tree-checkbox, .ant-tree-checkbox, .ant-select-item-option-state')
41
+ : null;
42
+ (checkbox || row).click();
43
+ return true;
44
+ }
45
+ // Find the scroll container.
46
+ const holder = root.querySelector('.rc-virtual-list-holder, .ant-select-tree-list-holder, .ant-tree-list-holder');
47
+ if (!holder)
48
+ return false;
49
+ // rc-virtual-list uses the holder's scrollTop to drive the translateY of inner.
50
+ // Advance by one viewport (clientHeight) each tick until we either find the row
51
+ // or hit the end.
52
+ const prev = holder.scrollTop;
53
+ holder.scrollTop = Math.min(holder.scrollHeight, prev + Math.max(holder.clientHeight, 120));
54
+ // Dispatch scroll so the virtual list re-renders.
55
+ holder.dispatchEvent(new Event('scroll', { bubbles: true }));
56
+ row = findRow();
57
+ if (row) {
58
+ const checkbox = useCheckbox
59
+ ? row.querySelector('.ant-select-tree-checkbox, .ant-tree-checkbox, .ant-select-item-option-state')
60
+ : null;
61
+ (checkbox || row).click();
62
+ return true;
63
+ }
64
+ // Reached the bottom without finding it? Caller will timeout if so.
65
+ return holder.scrollTop >= holder.scrollHeight - holder.clientHeight ? false : false;
66
+ }, containerSelector !== null && containerSelector !== void 0 ? containerSelector : '', label, clickCheckbox);
67
+ }, {
68
+ timeout,
69
+ interval,
70
+ timeoutMsg: `Virtualized option "${label}" not found within ${timeout}ms (container=${containerSelector !== null && containerSelector !== void 0 ? containerSelector : 'document'})`,
71
+ });
72
+ }
73
+ exports.scrollAndClickVirtualOption = scrollAndClickVirtualOption;
74
+ exports.default = { scrollAndClickVirtualOption };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capillarytech/cap-ui-utils",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "Utility functions shared accross all the modules",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,23 +0,0 @@
1
- // Singapore clusters that require special handling
2
- export const SG_CLUSTERS = ['sgcrm', 'sgcrmasia'];
3
-
4
- export const INFO = "🟡";
5
- export const SUCCESS = "🟢";
6
- export const FAILURE = "🔴";
7
-
8
- export const WAIT_30S = 30000;
9
- export const WAIT_60S = 60000;
10
- export const WAIT_90S = 90000;
11
- export const WAIT_120S = 120000;
12
-
13
- export const common = {
14
- "cluster" : process.env.cluster,
15
- "validatorUrl" : "http://127.0.0.1:4000",
16
- "maxLoginAttempts": 3,
17
- "sgClusters": SG_CLUSTERS,
18
- "isLoyaltyMigration": process.env.module === 'loyalty_migration',
19
- }
20
-
21
- // login.page.ts (shared) imports this by name; re-exported here so the toolkit
22
- // stays free of the data-heavy constants/constants aggregator (org/auth/endpoints).
23
- export const maxLoginAttempts = common.maxLoginAttempts;
package/e2e/index.ts DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * Shared WDIO E2E toolkit — the framework-agnostic, data-free helpers that were
3
- * duplicated byte-for-byte across the product repos (wdio-ui-automation,
4
- * garuda-ui, and every future migrated repo).
5
- *
6
- * Two ways to consume:
7
- * import { elementUtil, loginPage } from '@capillarytech/cap-ui-utils/e2e';
8
- * import elementUtil from '@capillarytech/cap-ui-utils/e2e/utils/elementUtil';
9
- *
10
- * NOT included yet (data-coupled — they read the per-repo `cons` org/auth/endpoint
11
- * data via constants/constants): apiUtil, helperUtil, cookieUtil, navigationUtil,
12
- * appNavigationUtil, requestRecorderService, home.page, testAndPreviewFlow.page.
13
- * These need a small config-injection seam before they can move — see README.
14
- */
15
- export { default as elementUtil } from './utils/elementUtil';
16
- export { default as antdVersion } from './utils/antdVersionUtil';
17
- export { default as expectUtil } from './utils/expectUtil';
18
- export { default as automationBypass } from './utils/automationBypassUtil';
19
- export { default as debugMode } from './utils/debugModeUtil';
20
- export { default as deletionRegistry } from './utils/deletionRegistry';
21
- export { default as dropdownUtil } from './utils/garudaDropdownUtil';
22
- export { default as logCollectorUtil } from './utils/logCollectorUtil';
23
- export { default as reportUploader } from './utils/reportUploader';
24
- export { default as requestRecorderUtil } from './utils/requestRecorderUtil';
25
- export { default as screenshotRecorderUtil } from './utils/screenshotRecorderUtil';
26
- export { default as setupMock } from './utils/setupMock';
27
- export { default as lockService } from './services/lockService';
28
- export { default as BasePage } from './pages/common/base.page';
29
- export { default as loginPage } from './pages/common/login.page';
30
- export * from './constants/common';
@@ -1,11 +0,0 @@
1
- export default class BasePage{
2
- async launchApplication(url){
3
- console.log('Browser Maximize')
4
- browser.maximizeWindow();
5
- console.log('Triggered navigate to', url)
6
- await browser.navigateTo(url)
7
- console.log('Navigated to URL: ', url);
8
-
9
- }
10
- async quitApplication(){await browser.closeWindow()}
11
- }
@@ -1,2 +0,0 @@
1
- export const EMAIL = 'email';
2
- export const WEBPUSH = 'webpush';
@@ -1,95 +0,0 @@
1
- import basePage from "./base.page"
2
- import elementUtil from "../../utils/elementUtil"
3
- import { maxLoginAttempts } from '../../constants/common';
4
- import commands from "@rpii/wdio-commands"
5
-
6
- class LoginPage extends basePage{
7
-
8
- get isV2Login(){return process.env.V2_LOGIN === 'true'}
9
- get inputUserName(){return this.isV2Login ? $('#login-username') : $('#login_user')}
10
- get inputPassword(){return this.isV2Login ? $('#login-password') : $('#login_cred')}
11
- get buttonLogin(){return this.isV2Login ? $('//button[.="Sign in"]') : $('#c-login-btn')}
12
- get continueButton(){return $('//button[.="Continue"]')}
13
- // Post-login readiness marker. The MFE Navigation Host replaced the old
14
- // Dashboard (which rendered ExplorePosts → "Explore features") with NewDashboard,
15
- // so the sidebar shell testid is the reliable signal the home shell has mounted.
16
- // Old text kept as a backward-compatible fallback for pre-MFE environments.
17
- get exploreFeatures(){return $('//*[@data-testid="cap-navigation-spa-sidebar"] | //*[text()="Explore features"]')}
18
-
19
- async doLogin(url, username, password, loginTryCount=0){
20
- try {
21
- await browser.refresh();
22
- await browser.pause(3000);
23
- console.log('Browser launched')
24
- await super.launchApplication(url)
25
- if (loginTryCount > 0) {
26
- let currentUrl = await browser.getUrl();
27
- const isHome = currentUrl.includes("/home/ui");
28
- if (isHome) {
29
- try {
30
- await (await this.exploreFeatures).waitForDisplayed({ timeout: 60000, interval: 5000 });
31
- console.log('-----Logged In-----');
32
- console.log('On retry login flow, /home/ui url and explore features text appeared after 60s probably, hence skipping entering username and password');
33
- return;
34
- } catch(e) {
35
- console.log('proceeding with retry');
36
- }
37
- }
38
- }
39
- for(let i=0; i<=2; i++){
40
- try{
41
- await (await this.inputUserName).waitForDisplayed({ timeout: 60000 });
42
- await this.inputUserName.waitForEnabled({ timeout: 30000, timeoutMsg:"thrown from dologin", interval:1000 })
43
- break;
44
- }catch(error){
45
- console.log('Failed to load login page- Refreshing browser')
46
- await browser.refresh();
47
- }
48
- }
49
- await elementUtil.enterText( await this.inputUserName, username)
50
- if (this.isV2Login) {
51
- await this.continueButton.waitForEnabled({ timeout: 10000 })
52
- await elementUtil.elementClick( await this.continueButton)
53
- await this.inputPassword.waitForDisplayed({ timeout: 30000 })
54
- }
55
- await elementUtil.enterText( await this.inputPassword, password)
56
- await this.buttonLogin.waitForEnabled({ timeout: 10000 })
57
- await elementUtil.elementClick( await this.buttonLogin)
58
- await browser.waitUntil(
59
- async () => {
60
- let currentUrl = await browser.getUrl();
61
- return currentUrl.includes("/home/ui")
62
- },
63
- {
64
- timeout: 60000,
65
- timeoutMsg: 'expected url to include /home/ui',
66
- interval: 1000,
67
- }
68
- );
69
- console.log('-----Browsers current url contains home/ui after login-----')
70
- await (await this.exploreFeatures).waitForDisplayed({ timeout: 60000, interval: 5000 });
71
- console.log('-----Logged In-----')
72
- }
73
- catch (error){
74
- console.log('Throwed error from dologin, attempt #: ', loginTryCount, ', Error: ', error);
75
- commands.logScreenshot("Failed to get the user logged in: "+error)
76
- if (loginTryCount >= (maxLoginAttempts - 1)) {
77
- console.log('Since login retry count exceeds 2. Exiting now.');
78
- throw new Error("Failed to get the user logged in: "+error);
79
- }
80
- console.log('Retrying Login.. ');
81
- await this.doLogin(url, username, password, ++loginTryCount);
82
- }
83
- }
84
-
85
- async redirectHome(url){
86
- try{
87
- await super.launchApplication(url)
88
- }
89
- catch (error) {
90
- commands.logScreenshot("Exception logged: "+error)
91
- throw new Error(error)
92
- }
93
- }
94
- }
95
- export default new LoginPage()
@@ -1 +0,0 @@
1
- ready
@@ -1,15 +0,0 @@
1
- class DeletionRegistry {
2
- private deleted = new Set<string>();
3
-
4
- has(name: string): boolean {
5
- return this.deleted.has(name);
6
- }
7
-
8
- register(name: string): void {
9
- this.deleted.add(name);
10
- console.log(`DeletionRegistry: registered "${name}"`);
11
- }
12
- }
13
-
14
- const deletionRegistry = new DeletionRegistry();
15
- export default deletionRegistry;
@@ -1,19 +0,0 @@
1
-
2
- import * as assert from 'assert'
3
-
4
- class AssertionUtil {
5
-
6
- async assertValue(element:WebdriverIO.Element, textToBeValidated:string){
7
- assert.strictEqual(await element.getText(),textToBeValidated);
8
- }
9
-
10
- async assertElementExistence(element:WebdriverIO.Element){
11
- try {
12
- assert.strictEqual(await element.isDisplayed(), true);
13
- } catch (error) {
14
- throw new Error(error)
15
- }
16
- }
17
- }
18
-
19
- export default new AssertionUtil()
@@ -1,30 +0,0 @@
1
- /**
2
- * Reads feature flags from window.capAuth.accessibleFeatures — the same source
3
- * that Auth.hasFeatureAccess() uses in the app (cap-ui-utils/auth/hasFeatureAccess.js).
4
- *
5
- * Call loadFeatureFlags() once after the app has loaded (e.g. after openAudiencePage),
6
- * then use hasFeatureAccess() anywhere in the test run.
7
- */
8
-
9
- let _accessibleFeatures: string[] = [];
10
-
11
- async function loadFeatureFlags(): Promise<void> {
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(
16
- async () => {
17
- const features = await browser.execute(() => (window as any).capAuth?.accessibleFeatures);
18
- return Array.isArray(features);
19
- },
20
- { timeout: 30000, interval: 1000, timeoutMsg: '[featureFlags] window.capAuth.accessibleFeatures never populated' }
21
- );
22
- _accessibleFeatures = await browser.execute(() => (window as any).capAuth?.accessibleFeatures || []);
23
- console.log(`[featureFlags] loaded ${_accessibleFeatures.length} features`);
24
- }
25
-
26
- function hasFeatureAccess(feature: string): boolean {
27
- return _accessibleFeatures.includes(feature);
28
- }
29
-
30
- export default { loadFeatureFlags, hasFeatureAccess };
@@ -1,60 +0,0 @@
1
- class GarudaDropdownUtil {
2
- treeOptionByText(
3
- text: string,
4
- index: number | 'last' = 1,
5
- altText?: string
6
- ): WebdriverIO.Element {
7
- const textMatch = altText
8
- ? `normalize-space()="${text}" or normalize-space()="${altText}"`
9
- : `normalize-space()="${text}"`;
10
- const position = index === 'last' ? 'last()' : index;
11
- return $(
12
- `(//span[contains(@class,"ant-select-tree-node-content-wrapper")][.//div[${textMatch}]])[${position}]`
13
- );
14
- }
15
-
16
- unifiedOptionByText(text: string): WebdriverIO.Element {
17
- return $(
18
- `//div[contains(@class,"cap-unified-select-popup")]//div[.//div[text()="${text}"] and @role="treeitem"]`
19
- );
20
- }
21
-
22
- listOptionByLabel(label: string, index: number = 1): WebdriverIO.Element {
23
- return $(
24
- `(//ul[@role="listbox"]//li[@role="option" and @label="${label}"])[${index}]`
25
- );
26
- }
27
-
28
- selectOptionByText(text: string, index: number = 1): WebdriverIO.Element {
29
- return $(
30
- `(//div[contains(@class,"ant-select-item-option")][.//div[contains(@class,"ant-select-item-option-content")][normalize-space()="${text}"]])[${index}]`
31
- );
32
- }
33
-
34
- treeOption(index: number | 'last' = 1): WebdriverIO.Element {
35
- const position = index === 'last' ? 'last()' : index;
36
- return $(
37
- `(//span[contains(@class,"ant-select-tree-node-content-wrapper")][normalize-space(.)])[${position}]`
38
- );
39
- }
40
-
41
- listOptionByLabelInPopup(ariaControlId: string, label: string): WebdriverIO.Element {
42
- return $(
43
- `//div[contains(@id,"${ariaControlId}")]//ul//li[@role="option" and @label="${label}"]`
44
- );
45
- }
46
-
47
- menuItemByText(text: string, index: number = 1): WebdriverIO.Element {
48
- return $(
49
- `(//li[contains(@class,"ant-dropdown-menu-item")][.//span[normalize-space()="${text}"]])[${index}]`
50
- );
51
- }
52
-
53
- unifiedSelectConfirmButton(): WebdriverIO.Element {
54
- return $(
55
- `//button[contains(@class,"cap-unified-select-confirm-button")]`
56
- );
57
- }
58
- }
59
-
60
- export default new GarudaDropdownUtil();
@@ -1,56 +0,0 @@
1
- /**
2
- * Helpers for the antd v6 contenteditable HTML editor.
3
- * After bulk HTML injection, the caret remains outside the `<body>` tag, causing label
4
- * insertions at the wrong position. `placeCaretInsideBody` locates the `<body>` position
5
- * across text nodes and updates the Selection range correctly.
6
- *
7
- * Caller passes the WDIO editor element (defined as a page selector), so this util
8
- * does not own selector strings.
9
- */
10
-
11
- export async function placeCaretInsideBody(
12
- editor: WebdriverIO.Element,
13
- ): Promise<boolean> {
14
- return browser.execute((node: HTMLElement) => {
15
- if (!node) return false;
16
-
17
- const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
18
- const nodes: Text[] = [];
19
- const starts: number[] = [];
20
- let combined = '';
21
- for (let n = walker.nextNode(); n; n = walker.nextNode()) {
22
- const t = n as Text;
23
- starts.push(combined.length);
24
- combined += t.nodeValue || '';
25
- nodes.push(t);
26
- }
27
- if (!nodes.length) return false;
28
-
29
- const open = combined.match(/<\s*body\b[^>]*>/i);
30
- if (!open || open.index === undefined) return false;
31
-
32
- let idx = open.index + open[0].length;
33
- while (idx < combined.length && /\s/.test(combined.charAt(idx))) idx++;
34
-
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) { nodeIdx = i; break; }
39
- nodeIdx = i;
40
- }
41
- const target = nodes[nodeIdx];
42
- const offset = Math.max(0, Math.min(idx - starts[nodeIdx], (target.nodeValue || '').length));
43
-
44
- node.focus();
45
- const range = document.createRange();
46
- range.setStart(target, offset);
47
- range.collapse(true);
48
- const selection = window.getSelection();
49
- if (!selection) return false;
50
- selection.removeAllRanges();
51
- selection.addRange(range);
52
- return true;
53
- }, editor as any);
54
- }
55
-
56
- export default { placeCaretInsideBody };
@@ -1,52 +0,0 @@
1
- import supertest from 'supertest'
2
- import commands from "@rpii/wdio-commands"
3
-
4
- class LogCollectorUtil {
5
-
6
- callCount=0;
7
-
8
- async collectLogs(runId, test, error, duration, passed, skipped = false){
9
- this.callCount += 1;
10
- let validationMessage
11
- let status
12
-
13
- if(passed){
14
- status='passed'
15
- validationMessage = ''
16
- } else if(skipped){
17
- status='skipped'
18
- validationMessage = ''
19
- } else {
20
- commands.logScreenshot("Exception logged: "+error)
21
- status='failed'
22
- if(error===undefined){
23
- validationMessage='cannot capture error'
24
- } else {
25
- validationMessage=error.toString().replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
26
- }
27
- }
28
- // this.recordBrowserLogs()
29
- const baseUrl = 'http://127.0.0.1:4000'
30
- const request = supertest(baseUrl)
31
- let exeTime = duration / 1000
32
- let message ={[runId]: {[test.parent]: {[test.title]: {"status": status,
33
- "time": exeTime.toString(), "validataionMessage": validationMessage,
34
- "total": this.callCount}}}}
35
- const messagePayload ={"result" : message}
36
- const response = await request.
37
- post('/logger')
38
- .send(messagePayload)
39
- console.log('API Response',response.body);
40
- console.log('Message', message)
41
- }
42
-
43
- async recordBrowserLogs(){
44
- const logTypes = Promise.resolve(browser.getLogs('browser')).then(function(data){return data})
45
- logTypes.then(data=>(JSON.stringify(data.filter(function getObj(logs) {
46
- if(logs['level']==='SEVERE' || logs['level']==='ERROR'){
47
- commands.logMessage((logs['level']+": "+JSON.stringify(logs)))}
48
- })))
49
- );
50
- }
51
- }
52
- export default new LogCollectorUtil()
@@ -1,29 +0,0 @@
1
- import { beamerApiResp } from "./mockResponse";
2
-
3
- class setupMock {
4
- async mockBeamerAPIForPopup() {
5
- const beamerReq1 = await browser.mock(
6
- "https://backend.getbeamer.com/initialize" + "**",
7
- {
8
- method: "get",
9
- }
10
- );
11
-
12
- beamerReq1.respond(beamerApiResp);
13
-
14
- const beamerReq2 = await browser.mock(
15
- "https://backend.getbeamer.com/numberFeatures" + "**",
16
- {
17
- method: "get",
18
- }
19
- );
20
-
21
- beamerReq2.respond(
22
- [{}],
23
- {
24
- statusCode: 404,
25
- }
26
- );
27
- }
28
- }
29
- export default new setupMock();