froth-webdriverio-framework 6.0.17 → 6.0.18
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/froth_api_calls/browsersatckSessionInfo.js +6 -15
- package/froth_api_calls/getexecutionDetails.js +38 -123
- package/froth_common_actions/Utils.js +6 -38
- package/froth_common_actions/alert.js +6 -21
- package/froth_common_actions/apicall.js +14 -30
- package/froth_common_actions/assert.js +14 -26
- package/froth_common_actions/dropDown.js +7 -18
- package/froth_common_actions/jwt.js +12 -12
- package/froth_common_actions/random.js +29 -187
- package/froth_configs/commonconfig.js +33 -182
- package/froth_configs/setallDatailinBuffer.js +1 -1
- package/froth_configs/wdio.common.conf.js +21 -151
- package/package.json +3 -4
- package/workflows/pipeline.yml +63 -0
- package/froth_common_actions/captureNavigationTime.js +0 -53
- package/froth_common_actions/emailParsing.js +0 -161
- package/froth_common_actions/logData.js +0 -73
- package/froth_common_actions/swicthWindowTab.js +0 -36
|
@@ -7,175 +7,43 @@ console.log('=====wdios common config===== ');
|
|
|
7
7
|
|
|
8
8
|
// Select platform at runtime
|
|
9
9
|
const PLATFORM = process.env.PLATFORM || 'browserstack';
|
|
10
|
+
const configFile = process.env.YML_NAME || '../ymls/browserstack/android/android_caps.yml';
|
|
10
11
|
console.log('====>PLATFORM:', PLATFORM);
|
|
11
|
-
|
|
12
|
-
const configFile = process.env.YML_NAME;
|
|
13
|
-
|
|
14
|
-
const resultdetails = {
|
|
15
|
-
comments: [],
|
|
16
|
-
excution_status: null, // Pass/Fail
|
|
17
|
-
excution_time: null, // Execution time in milliseconds
|
|
18
|
-
};
|
|
19
|
-
|
|
20
12
|
// Load YAML file
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let rawCaps;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
rawCaps = yaml.load(fs.readFileSync(path.join(path.resolve(process.cwd(), configFile)), 'utf8'));
|
|
27
|
-
// Merge chrome-specific options if applicable
|
|
28
|
-
console.log("==== RAW YAML ====", rawCaps);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} catch (e) {
|
|
32
|
-
const errorMsg = `The capability file does not exist in the currently configured repository at path: ${path.resolve(process.cwd(), configFile)}: ${e.message},Please update the Capability by editing the file`;
|
|
33
|
-
console.error(errorMsg);
|
|
34
|
-
resultdetails.comments.push(errorMsg);
|
|
35
|
-
resultdetails.excution_status = 'FAIL';
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
exeDetails.updateExecuitonDetails(
|
|
39
|
-
process.env.ORGANISATION_DOMAIN_URL,
|
|
40
|
-
process.env.API_TOKEN,
|
|
41
|
-
process.env.EXECUTION_ID,
|
|
42
|
-
resultdetails
|
|
43
|
-
);
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
console.log("30 seconds passed.");
|
|
46
|
-
// You can call your API or exit the process here
|
|
47
|
-
}, 30000);
|
|
48
|
-
console.log('Execution details updated successfully.');
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error('Failed to update execution details:', err.message);
|
|
51
|
-
}
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log('====>capabilities:', rawCaps);
|
|
13
|
+
const capabilities = yaml.load(fs.readFileSync(path.join(path.resolve(process.cwd(), configFile)), 'utf8'));
|
|
14
|
+
console.log('====>capabilities:', capabilities);
|
|
56
15
|
const SUITE_FILE = path.resolve(process.cwd(), process.env.SUITE);
|
|
57
16
|
console.log('====>SUITE_FILE:', SUITE_FILE);
|
|
58
17
|
|
|
59
|
-
if (!SUITE_FILE || !fs.existsSync(SUITE_FILE)) {
|
|
60
|
-
let errorMsg = `The suite file does not exist in the currently configured repository at path: ${SUITE_FILE}. Please update the SUITE by editing the file`;
|
|
61
|
-
console.error(errorMsg);
|
|
62
|
-
resultdetails.comments.push(errorMsg);
|
|
63
|
-
resultdetails.excution_status = 'FAIL';
|
|
64
|
-
try {
|
|
65
|
-
exeDetails.updateExecuitonDetails(
|
|
66
|
-
process.env.ORGANISATION_DOMAIN_URL,
|
|
67
|
-
process.env.API_TOKEN,
|
|
68
|
-
process.env.EXECUTION_ID,
|
|
69
|
-
resultdetails
|
|
70
|
-
);
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
console.log("30 seconds passed.");
|
|
73
|
-
}, 30000);
|
|
74
|
-
console.log('Execution details updated successfully.');
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.error('Failed to update execution details:', err.message);
|
|
77
|
-
}
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
18
|
|
|
81
|
-
function setupPrerequisites(
|
|
82
|
-
console.log("
|
|
19
|
+
function setupPrerequisites() {
|
|
20
|
+
console.log("inside this fuction first ");
|
|
83
21
|
if (PLATFORM === 'browserstack') {
|
|
84
|
-
process.env.BROWSERSTACK_USERNAME =
|
|
85
|
-
|
|
86
|
-
process.env.BROWSERSTACK_ACCESS_KEY =
|
|
22
|
+
process.env.BROWSERSTACK_USERNAME = capabilities.userName;
|
|
23
|
+
capabilities.accessKey = Buffer.from(capabilities.accessKey, 'base64').toString('utf-8');
|
|
24
|
+
process.env.BROWSERSTACK_ACCESS_KEY = capabilities.accessKey
|
|
25
|
+
|
|
26
|
+
console.log('capabilities.platformName:', capabilities.platformName ?? 'web');
|
|
87
27
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
process.env.BS_SESSION_TYPE = isMobile ? 'app-automate' : 'automate';
|
|
91
|
-
|
|
92
|
-
rawCaps.buildName = process.env.FROTH_TESTOPS_BUILD_NAME;
|
|
93
|
-
console.log("SessionType=====>:", process.env.BS_SESSION_TYPE);
|
|
28
|
+
process.env.BS_SESSION_TYPE = capabilities.platformName === 'android' || capabilities.platformName === 'ios' ? 'app-automate' : 'automate';
|
|
29
|
+
capabilities.buildName = process.env.FROTH_TESTOPS_BUILD_NAME;
|
|
94
30
|
|
|
95
31
|
}
|
|
96
|
-
|
|
32
|
+
|
|
97
33
|
}
|
|
98
34
|
|
|
99
|
-
setupPrerequisites(
|
|
100
|
-
// ---------------------------------------------
|
|
101
|
-
// NORMALIZER — WEB + MOBILE (AUTODETECTION)
|
|
102
|
-
// ---------------------------------------------
|
|
103
|
-
function normalizeCapabilities(raw) {
|
|
104
|
-
const isMobile =
|
|
105
|
-
raw.platformName === "android" || raw.platformName === "ios";
|
|
106
|
-
const isWeb = !!raw.browserName;
|
|
107
|
-
|
|
108
|
-
// --------------------------
|
|
109
|
-
// 1. MOBILE – App Automate
|
|
110
|
-
// --------------------------
|
|
111
|
-
if (isMobile) {
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
platformName: raw.platformName,
|
|
115
|
-
"appium:platformVersion": raw.platformVersion,
|
|
116
|
-
"appium:deviceName": raw.deviceName,
|
|
117
|
-
"appium:deviceOrientation": raw.deviceOrientation,
|
|
118
|
-
"bstack:options": {
|
|
119
|
-
userName: raw.userName,
|
|
120
|
-
accessKey: Buffer.from(raw.accessKey, "base64").toString("utf8"),
|
|
121
|
-
debug: raw.debug,
|
|
122
|
-
networkLogs: raw.networkLogs,
|
|
123
|
-
interactiveDebugging: raw.interactiveDebugging,
|
|
124
|
-
buildIdentifier: raw.buildIdentifier,
|
|
125
|
-
// appiumVersion: "2.0",
|
|
126
|
-
projectName: process.env.BS_PROJECT_NAME || "Automation",
|
|
127
|
-
buildName: process.env.FROTH_TESTOPS_BUILD_NAME || "Mobile Build",
|
|
128
|
-
local: false
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (isWeb) {
|
|
133
|
-
|
|
134
|
-
// --------------------------
|
|
135
|
-
// 2. WEB – Browser Automate
|
|
136
|
-
// --------------------------
|
|
35
|
+
const specificConfig = setupPrerequisites();
|
|
137
36
|
|
|
138
|
-
return {
|
|
139
|
-
browserName: raw.browserName,
|
|
140
|
-
browserVersion: raw.browserVersion || "latest",
|
|
141
|
-
"bstack:options": {
|
|
142
|
-
os: raw.os || "Windows",
|
|
143
|
-
osVersion: raw.osVersion || "11",
|
|
144
|
-
userName: raw.userName,
|
|
145
|
-
accessKey: Buffer.from(raw.accessKey, "base64").toString("utf8"),
|
|
146
|
-
debug: raw.debug !== false,
|
|
147
|
-
networkLogs: raw.networkLogs !== false,
|
|
148
|
-
// sessionType: "automate",
|
|
149
|
-
projectName: process.env.BS_PROJECT_NAME || "Automation",
|
|
150
|
-
buildName: process.env.FROTH_TESTOPS_BUILD_NAME || "Web Build",
|
|
151
|
-
local: false
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
throw new Error("❌ YAML does not look like web or mobile capability");
|
|
157
|
-
}
|
|
158
|
-
const finalCaps =
|
|
159
|
-
PLATFORM === "browserstack" ? normalizeCapabilities(rawCaps) : rawCaps;
|
|
160
|
-
|
|
161
|
-
console.log("==== FINAL CAPS SENT TO BROWSERSTACK ====");
|
|
162
|
-
console.log(JSON.stringify(finalCaps, null, 2));
|
|
163
37
|
|
|
164
38
|
exports.config = deepmerge(commonconfig,
|
|
165
39
|
|
|
166
40
|
{
|
|
167
|
-
user:
|
|
168
|
-
key:
|
|
41
|
+
user: process.env.BROWSERSTACK_USERNAME,
|
|
42
|
+
key: process.env.BROWSERSTACK_ACCESS_KEY ,
|
|
169
43
|
// debug: true,
|
|
170
44
|
// execArgv: ['--inspect-brk'],
|
|
171
45
|
services: PLATFORM === 'browserstack'
|
|
172
|
-
? [
|
|
173
|
-
['browserstack', {
|
|
174
|
-
browserstackLocal: false, // Prevent starting Local binary
|
|
175
|
-
opts: { forcelocal: false } // Ensure no fallback Local is triggered
|
|
176
|
-
// forcedStop: true // Kill any accidental extra process
|
|
177
|
-
}]
|
|
178
|
-
]
|
|
46
|
+
? ['browserstack']
|
|
179
47
|
: PLATFORM === 'saucelabs'
|
|
180
48
|
? ['sauce']
|
|
181
49
|
: [],
|
|
@@ -184,7 +52,7 @@ exports.config = deepmerge(commonconfig,
|
|
|
184
52
|
specs: require(SUITE_FILE).tests,
|
|
185
53
|
|
|
186
54
|
maxInstances: 1,
|
|
187
|
-
capabilities: [
|
|
55
|
+
capabilities: [capabilities],
|
|
188
56
|
|
|
189
57
|
logLevel: 'info',
|
|
190
58
|
coloredLogs: true,
|
|
@@ -203,4 +71,6 @@ exports.config = deepmerge(commonconfig,
|
|
|
203
71
|
ui: 'bdd',
|
|
204
72
|
timeout: 300000
|
|
205
73
|
}
|
|
206
|
-
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "froth-webdriverio-framework",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.18",
|
|
4
|
+
|
|
4
5
|
"readme": "WebdriverIO Integration",
|
|
5
6
|
"description": "WebdriverIO and BrowserStack App Automate",
|
|
6
7
|
"license": "MIT",
|
|
@@ -35,15 +36,13 @@
|
|
|
35
36
|
"appium-uiautomator2-driver": "^4.0.1",
|
|
36
37
|
"assert": "^2.1.0",
|
|
37
38
|
"axios": "^1.7.7",
|
|
39
|
+
"browserstack-local": "^1.5.5",
|
|
38
40
|
"chai": "^5.1.1",
|
|
39
41
|
"crypto-js": "^4.2.0",
|
|
40
42
|
"deepmerge": "^4.3.1",
|
|
41
|
-
"express": "^5.1.0",
|
|
42
43
|
"form-data": "^4.0.0",
|
|
43
44
|
"fs": "^0.0.1-security",
|
|
44
|
-
"imap-simple": "^5.1.0",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
|
-
"mailparser": "^3.9.0",
|
|
47
46
|
"mysql2": "^3.10.2",
|
|
48
47
|
"node-fetch": "^3.3.2",
|
|
49
48
|
"node-localstorage": "^3.0.5",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
definitions:
|
|
2
|
+
steps:
|
|
3
|
+
- step: &manual-step
|
|
4
|
+
name: "Run WebDriverIO Tests"
|
|
5
|
+
image: node:20
|
|
6
|
+
size: 2x
|
|
7
|
+
script:
|
|
8
|
+
- echo "📥 Checkout code"
|
|
9
|
+
- git clone $BITBUCKET_GIT_HTTP_ORIGIN . || echo "Repo already cloned"
|
|
10
|
+
|
|
11
|
+
- echo "⬇️ Install framework dependency"
|
|
12
|
+
- npm install froth-webdriverio-framework@prod
|
|
13
|
+
|
|
14
|
+
- echo "⚙️ Generate unique build number"
|
|
15
|
+
- if [ -f generate-build-name.js ]; then node generate-build-name.js; else echo "Skipping build name generation"; fi
|
|
16
|
+
|
|
17
|
+
- echo "📡 Sending webhook to Django"
|
|
18
|
+
- |
|
|
19
|
+
RESPONSE=$(curl -s -o response.txt -w "%{http_code}" -X POST "https://$organisation_url/api/github-webhook-workflow/" \
|
|
20
|
+
-H "Content-Type: application/json" \
|
|
21
|
+
-d "{\"execution_id\": \"$execution_id\", \"run_number\": \"$BITBUCKET_BUILD_NUMBER\"}")
|
|
22
|
+
cat response.txt
|
|
23
|
+
echo "Webhook HTTP status: $RESPONSE"
|
|
24
|
+
if [ "$RESPONSE" -ne 200 ]; then
|
|
25
|
+
echo "❌ Webhook failed"
|
|
26
|
+
exit 1
|
|
27
|
+
else
|
|
28
|
+
echo "✅ Webhook succeeded"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
- echo "🧪 Run WDIO tests"
|
|
32
|
+
- |
|
|
33
|
+
export ORGANISATION_DOMAIN_URL="$organisation_url"
|
|
34
|
+
export EXECUTION_ID="$execution_id"
|
|
35
|
+
export FROTH_TESTOPS_BUILD_NAME="build-$BITBUCKET_BUILD_NUMBER"
|
|
36
|
+
export CICD_RUN_ID="$BITBUCKET_BUILD_NUMBER"
|
|
37
|
+
export YML_NAME="$yml_path"
|
|
38
|
+
export PLATFORM="$platform"
|
|
39
|
+
export SUITE="$filepath"
|
|
40
|
+
export API_TOKEN="$api_token"
|
|
41
|
+
|
|
42
|
+
echo "Running npx wdio..."
|
|
43
|
+
npx wdio ./node_modules/froth-webdriverio-framework/froth_configs/wdio.common.conf.js
|
|
44
|
+
|
|
45
|
+
pipelines:
|
|
46
|
+
custom:
|
|
47
|
+
manual-execution:
|
|
48
|
+
- variables:
|
|
49
|
+
- name: organisation_url
|
|
50
|
+
default: "devapi.frothtestops.com"
|
|
51
|
+
- name: filepath
|
|
52
|
+
default: "default_username"
|
|
53
|
+
- name: execution_id
|
|
54
|
+
default: "0"
|
|
55
|
+
- name: api_token
|
|
56
|
+
default: "cscscs"
|
|
57
|
+
- name: execution_agent
|
|
58
|
+
default: "ubuntu"
|
|
59
|
+
- name: platform
|
|
60
|
+
default: "ubuntu"
|
|
61
|
+
- name: yml_path
|
|
62
|
+
default: "cascasca"
|
|
63
|
+
- step: *manual-step
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const amendToBrowserstack = require("../froth_api_calls/browsersatckSessionInfo").amend2Browserstack;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Capture Navigation / Page Load time
|
|
6
|
-
* @param {string} pageName - (Optional) Custom name for the page
|
|
7
|
-
*/
|
|
8
|
-
async function captureLoadNavigationTime(pageName = '') {
|
|
9
|
-
let pageLoadTime;
|
|
10
|
-
try {
|
|
11
|
-
// Try modern API first
|
|
12
|
-
let perfEntries = await browser.execute(() => {
|
|
13
|
-
if (performance.getEntriesByType) {
|
|
14
|
-
const [nav] = performance.getEntriesByType('navigation');
|
|
15
|
-
return nav ? nav.toJSON() : null;
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (perfEntries && perfEntries.loadEventEnd) {
|
|
22
|
-
pageLoadTime = perfEntries.loadEventEnd;
|
|
23
|
-
} else {
|
|
24
|
-
// Fallback to old API
|
|
25
|
-
const perfTiming = await browser.execute(() => JSON.stringify(window.performance.timing));
|
|
26
|
-
const timing = JSON.parse(perfTiming);
|
|
27
|
-
pageLoadTime = timing.loadEventEnd - timing.navigationStart;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// If no custom name passed, use the page title
|
|
31
|
-
let title = pageName || await browser.getTitle();
|
|
32
|
-
|
|
33
|
-
// Clean title: remove special characters, keep words separated by space
|
|
34
|
-
title = title
|
|
35
|
-
.replace(/[^a-zA-Z0-9]+/g, ' ') // replace non-alphanumeric with space
|
|
36
|
-
.replace(/\s+/g, ' ') // collapse multiple spaces
|
|
37
|
-
.trim(); // remove leading/trailing spaces
|
|
38
|
-
|
|
39
|
-
pageLoadTime = perfEntries.loadEventEnd; // Already relative to startTime
|
|
40
|
-
pageLoadTime = (pageLoadTime / 1000).toFixed(2); // Convert ms to seconds, keep 2 decimals
|
|
41
|
-
await amendToBrowserstack(`⏱ Page Load Time for ${title}: ${pageLoadTime} seconds`, "info");
|
|
42
|
-
|
|
43
|
-
console.log(`⏱ Page Load Time for ${title}: ${pageLoadTime} seconds`);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('Error capturing navigation timing:', error);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return pageLoadTime;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
module.exports = { captureLoadNavigationTime };
|
|
53
|
-
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
2
|
-
|
|
3
|
-
const Imap = require('imap');
|
|
4
|
-
const { simpleParser } = require('mailparser');
|
|
5
|
-
const cheerio = require('cheerio');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Connects to an IMAP inbox, parses emails, and extracts all unique links.
|
|
9
|
-
* @param {Object} config - IMAP config { user, password, host, port, tls }
|
|
10
|
-
* @param {Object} [filter] - Optional filter { subject, from }
|
|
11
|
-
* @returns {Promise<Array<{link: string, text: string}>>}
|
|
12
|
-
*/
|
|
13
|
-
async function getEmailLinks(username,app_password, subjectdetails,fromemail) {
|
|
14
|
-
const config = {
|
|
15
|
-
user: username,
|
|
16
|
-
password: app_password,
|
|
17
|
-
host: 'imap.gmail.com',
|
|
18
|
-
port: 993,
|
|
19
|
-
tls: true
|
|
20
|
-
};
|
|
21
|
-
const filter = {
|
|
22
|
-
subject: subjectdetails,
|
|
23
|
-
from: fromemail
|
|
24
|
-
};
|
|
25
|
-
const imap = new Imap(config);
|
|
26
|
-
|
|
27
|
-
function openInbox(cb) {
|
|
28
|
-
imap.openBox('INBOX', true, cb);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
let results = new Map();
|
|
33
|
-
let allLinks = [];
|
|
34
|
-
let parseDone;
|
|
35
|
-
const parsePromise = new Promise(res => { parseDone = res; });
|
|
36
|
-
|
|
37
|
-
imap.once('ready', function () {
|
|
38
|
-
openInbox(function (err, box) {
|
|
39
|
-
if (err) return cleanup(err);
|
|
40
|
-
|
|
41
|
-
// Build search criteria
|
|
42
|
-
let searchCriteria = ['ALL'];
|
|
43
|
-
if (filter.subject) searchCriteria.push(['HEADER', 'SUBJECT', filter.subject]);
|
|
44
|
-
if (filter.from) searchCriteria.push(['FROM', filter.from]);
|
|
45
|
-
|
|
46
|
-
imap.search(searchCriteria, function (err, uids) {
|
|
47
|
-
if (err) return cleanup(err);
|
|
48
|
-
if (!uids.length) return cleanup(null, []);
|
|
49
|
-
|
|
50
|
-
// Sort UIDs descending to get the latest email first
|
|
51
|
-
uids = uids.sort((a, b) => b - a);
|
|
52
|
-
// Only fetch the latest email
|
|
53
|
-
const latestUid = uids[0];
|
|
54
|
-
const fetch = imap.fetch([latestUid], { bodies: '' });
|
|
55
|
-
fetch.on('message', function (msg) {
|
|
56
|
-
let buffer = '';
|
|
57
|
-
msg.on('body', function (stream) {
|
|
58
|
-
stream.on('data', function (chunk) {
|
|
59
|
-
buffer += chunk.toString('utf8');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
msg.once('end', async function () {
|
|
63
|
-
try {
|
|
64
|
-
const parsed = await simpleParser(buffer);
|
|
65
|
-
let links = [];
|
|
66
|
-
|
|
67
|
-
// Extract from HTML with element context
|
|
68
|
-
if (parsed.html) {
|
|
69
|
-
const $ = cheerio.load(parsed.html);
|
|
70
|
-
$('a[href], button[href], [role="button"][href], img[src], area[href]').each((_, el) => {
|
|
71
|
-
let link = null;
|
|
72
|
-
let text = '';
|
|
73
|
-
let elementType = el.tagName;
|
|
74
|
-
if (elementType === 'a' || elementType === 'area') {
|
|
75
|
-
link = $(el).attr('href');
|
|
76
|
-
text = $(el).text().trim() || link;
|
|
77
|
-
} else if (elementType === 'img') {
|
|
78
|
-
link = $(el).attr('src');
|
|
79
|
-
text = $(el).attr('alt') || link;
|
|
80
|
-
} else if (elementType === 'button' || $(el).attr('role') === 'button') {
|
|
81
|
-
link = $(el).attr('href');
|
|
82
|
-
text = $(el).text().trim() || link;
|
|
83
|
-
}
|
|
84
|
-
if (link) {
|
|
85
|
-
links.push({ link, text, element: elementType });
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Extract from plaintext
|
|
91
|
-
if (parsed.text) {
|
|
92
|
-
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
93
|
-
let match;
|
|
94
|
-
while ((match = urlRegex.exec(parsed.text))) {
|
|
95
|
-
links.push({ link: match[1], text: match[1], element: 'text' });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Deduplicate by link and element context
|
|
100
|
-
for (const l of links) {
|
|
101
|
-
const key = l.link + '|' + l.element + '|' + l.text;
|
|
102
|
-
if (!results.has(key)) {
|
|
103
|
-
results.set(key, { link: l.link, text: l.text, element: l.element });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
allLinks = Array.from(results.values());
|
|
107
|
-
parseDone();
|
|
108
|
-
} catch (e) {
|
|
109
|
-
// Log and continue
|
|
110
|
-
console.error('Parse error:', e);
|
|
111
|
-
parseDone();
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
fetch.once('error', cleanup);
|
|
116
|
-
fetch.once('end', async () => {
|
|
117
|
-
await parsePromise;
|
|
118
|
-
cleanup(null, allLinks);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
imap.once('error', cleanup);
|
|
125
|
-
|
|
126
|
-
function cleanup(err, data) {
|
|
127
|
-
imap.end();
|
|
128
|
-
if (err) reject(err);
|
|
129
|
-
else resolve(data);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
imap.connect();
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function findLinks(links,searchText) {
|
|
137
|
-
const findLinkByText = (links, searchText) => {
|
|
138
|
-
return links.find(l => l.text && l.text.toLowerCase().includes(searchText.toLowerCase()));
|
|
139
|
-
};
|
|
140
|
-
// Example usage:
|
|
141
|
-
const found = findLinkByText(links, searchText);
|
|
142
|
-
return found ? found.link : null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Example main function for direct execution
|
|
147
|
-
// if (require.main === module) {
|
|
148
|
-
// (async () => {
|
|
149
|
-
|
|
150
|
-
// const links = await extractEmailLinks('gfntesting44@gmail.com','wtrd qfee dicr hpba', "GFN Testing, review your Google Account settings","no-reply@google.com");
|
|
151
|
-
// console.log('Extracted links in main:', links);
|
|
152
|
-
// const foundLink = await findLinks(links,"Privacy Policy");
|
|
153
|
-
// console.log('Found Link: ', foundLink);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// })();
|
|
157
|
-
|
|
158
|
-
// }
|
|
159
|
-
|
|
160
|
-
module.exports = { getEmailLinks ,findLinks}
|
|
161
|
-
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
const amendToBrowserstack = require("../froth_api_calls/browsersatckSessionInfo").amend2Browserstack;
|
|
2
|
-
|
|
3
|
-
async function logJsonData2Table(jsonData) {
|
|
4
|
-
try {
|
|
5
|
-
console.log("Logging JSON data to table format in BrowserStack:");
|
|
6
|
-
console.log(JSON.stringify(jsonData, null, 2));
|
|
7
|
-
|
|
8
|
-
if (!Array.isArray(jsonData) || jsonData.length === 0) {
|
|
9
|
-
await amendToBrowserstack("No data to log", "info");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// --- Extract keys dynamically (columns) ---
|
|
14
|
-
const keys = Object.keys(jsonData[0]);
|
|
15
|
-
|
|
16
|
-
// --- Calculate max width for each column ---
|
|
17
|
-
const colWidths = {};
|
|
18
|
-
keys.forEach(key => {
|
|
19
|
-
colWidths[key] = Math.max(
|
|
20
|
-
key.length,
|
|
21
|
-
...jsonData.map(row => String(row[key] ?? "").length)
|
|
22
|
-
) + 2;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// --- Build line separator ---
|
|
26
|
-
const line = "-".repeat(
|
|
27
|
-
keys.reduce((sum, key) => sum + colWidths[key], 0) + keys.length + 1
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
// --- Build header ---
|
|
31
|
-
let table = line + "\n";
|
|
32
|
-
table += "|" + keys.map(key => ` ${key.padEnd(colWidths[key])}`).join("|") + "|\n";
|
|
33
|
-
table += line + "\n";
|
|
34
|
-
|
|
35
|
-
// --- Build rows ---
|
|
36
|
-
jsonData.forEach(row => {
|
|
37
|
-
table += "|" + keys.map(key => ` ${String(row[key] ?? "").padEnd(colWidths[key])}`).join("|") + "|\n";
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
table += line;
|
|
41
|
-
console.log(table);
|
|
42
|
-
// --- Send to BrowserStack ---
|
|
43
|
-
await amendToBrowserstack(table, "info");
|
|
44
|
-
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Error occurred while logging JSON data to table:', error);
|
|
47
|
-
// let annotationMessage = `Error occurred while logging JSON data to table: ${error.message}.`;
|
|
48
|
-
// await amendToBrowserstack(annotationMessage, "error");
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
module.exports = {
|
|
52
|
-
logJsonData2Table
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// // Main function to execute the API call
|
|
56
|
-
// async function main() {
|
|
57
|
-
// try {
|
|
58
|
-
// /// BUFFER.setItem("EXECUTION_SESSIONID","297666e2fd4195de98d7da3b359669072ff41a2a");
|
|
59
|
-
// const pageTimes = "[{ name: 'Homepage', time: 120 },{ name: 'Service Account Page', time: 250 },{ name: 'Search Results', time: 180 }]";
|
|
60
|
-
|
|
61
|
-
// await logJsonData2Table(pageTimes, "info");
|
|
62
|
-
// // const data = [
|
|
63
|
-
// // { id: 1, score: 95.5, passed: true },
|
|
64
|
-
// // { id: 2, score: 67.2, passed: false },
|
|
65
|
-
// // { id: 3, score: null, passed: "N/A" }
|
|
66
|
-
// // ];
|
|
67
|
-
// // await logJsonData2Table(data, "info");
|
|
68
|
-
// } catch (error) {
|
|
69
|
-
// console.error('Error in main function:', error);
|
|
70
|
-
// }
|
|
71
|
-
// }
|
|
72
|
-
|
|
73
|
-
// main();
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Switches to a browser window/tab by its title using WebdriverIO.
|
|
3
|
-
* @param {string} title - The title of the window/tab to switch to.
|
|
4
|
-
*/
|
|
5
|
-
async function switch2WindowByTitle(title) {
|
|
6
|
-
const handles = await browser.getWindowHandles();
|
|
7
|
-
for (const handle of handles) {
|
|
8
|
-
await browser.switchToWindow(handle);
|
|
9
|
-
const currentTitle = await browser.getTitle();
|
|
10
|
-
if (currentTitle === title) {
|
|
11
|
-
return true; // Switched successfully
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
throw new Error(`No window with title "${title}" found.`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Switches to a browser window/tab by its index using WebdriverIO.
|
|
19
|
-
* @param {number} index - The index of the window/tab to switch to (0-based).
|
|
20
|
-
*/
|
|
21
|
-
async function switch2WindowByIndex(index) {
|
|
22
|
-
const handles = await browser.getWindowHandles();
|
|
23
|
-
console.log(`Total open windows: ${handles.length}`);
|
|
24
|
-
console.log(`Switching to window at index: ${index}`);
|
|
25
|
-
console.log(`Available handles: ${handles.join(', ')}`);
|
|
26
|
-
if (index < 0 || index >= handles.length) {
|
|
27
|
-
throw new Error(`Invalid window index: ${index}. There are only ${handles.length} windows open.`);
|
|
28
|
-
}
|
|
29
|
-
await browser.switchToWindow(handles[index]);
|
|
30
|
-
return true; // Switched successfully
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Example usage in a test
|
|
35
|
-
// await switchToWindowByTitle('Your Window Title');
|
|
36
|
-
module.exports = {switch2WindowByTitle,switch2WindowByIndex};
|