froth-webdriverio-framework 7.0.118 → 7.0.119-dev1.0

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.
@@ -64,8 +64,10 @@ async function getBSSessionDetails(sessionType, bsUsername, bsPassword) {
64
64
 
65
65
  BUFFER.setItem("REPORT_URL", publicUrl);
66
66
  BUFFER.setItem("FROTH_TOTAL_DURATION", duration);
67
+ BUFFER.setItem("BS_BUILD_HASH_ID", hashId);
67
68
 
68
69
  console.log(`Public URL: ${publicUrl}`);
70
+ console.log(`Build Hash ID: ${hashId}`);
69
71
  } else if (response.status === 401) {
70
72
  console.error("Unauthorized: token expired to login Browserstack");
71
73
  } else {
@@ -160,11 +162,78 @@ async function escapeForBrowserStack(message) {
160
162
  .replace(/\r/g, "\\r");
161
163
  }
162
164
 
165
+ /* ===================== BROWSERSTACK REPORT API ===================== */
166
+
167
+ /**
168
+ * Updates BrowserStack report details in Froth TestOps database
169
+ * @param {string} frothUrl - Froth TestOps API base URL
170
+ * @param {string} token - Authentication token
171
+ * @param {string} bsBuildId - BrowserStack build ID (e.g., "frothtestops-coderepo_1286_20260211")
172
+ * @param {number} executionId - Test execution ID
173
+ * @param {string} status - BrowserStack status: "in_progress" (updates public URL only) or "completed"
174
+ */
175
+ async function updateBrowserStackReport(frothUrl, token, bsBuildId, executionId, status) {
176
+ if (!frothUrl || !token || !bsBuildId || !executionId || !status) {
177
+ console.error('❌ Missing required parameters for updateBrowserStackReport');
178
+ console.error(`frothUrl: ${frothUrl}, token: ${!!token}, bsBuildId: ${bsBuildId}, executionId: ${executionId}, status: ${status}`);
179
+ return null;
180
+ }
181
+
182
+ const validStatuses = ['in_progress', 'completed'];
183
+ if (!validStatuses.includes(status)) {
184
+ console.error(`❌ Invalid browserstack_status: ${status}. Must be one of: ${validStatuses.join(', ')}`);
185
+ return null;
186
+ }
163
187
 
188
+ const url = `${frothUrl}/api/download-browserstack-report/`;
189
+ console.log(`📤 Updating BrowserStack report [${status}]:`, url);
164
190
 
191
+ const payload = {
192
+ BS_BUILD_ID: bsBuildId,
193
+ execution_id: executionId,
194
+ browserstack_status: status
195
+ };
196
+
197
+ try {
198
+ const response = await fetch(url, {
199
+ method: 'POST',
200
+ headers: {
201
+ 'Authorization': `Bearer ${token}`,
202
+ 'Content-Type': 'application/json'
203
+ },
204
+ body: JSON.stringify(payload)
205
+ });
206
+
207
+ const contentType = response.headers.get('content-type') || '';
208
+ let data;
209
+ if (contentType.includes('application/json')) {
210
+ data = await response.json();
211
+ } else {
212
+ data = await response.text();
213
+ }
214
+
215
+ console.log('📥 Response in updateBrowserStackReport:', data);
216
+
217
+ if (response.ok) {
218
+ console.log(`✅ BrowserStack report updated successfully [${status}]`);
219
+ return data;
220
+ } else if (response.status === 401) {
221
+ console.error('🔒 Unauthorized (401) - Token may be expired');
222
+ return null;
223
+ } else {
224
+ console.error(`❌ Failed to update BrowserStack report [${response.status}]:`, data);
225
+ return null;
226
+ }
227
+
228
+ } catch (error) {
229
+ console.error('❌ updateBrowserStackReport error:', error.message);
230
+ return null;
231
+ }
232
+ }
165
233
 
166
234
  module.exports = {
167
235
  getBSSessionDetails,
168
236
  getBSBuildDetails,
169
237
  amend2Browserstack,
238
+ updateBrowserStackReport,
170
239
  };
@@ -17,7 +17,9 @@ function isValidId(id) {
17
17
  async function handleResponse(response, context) {
18
18
 
19
19
  if (response.ok) {
20
- return response.json();
20
+ const data = await response.json();
21
+ console.log('📥 Response in', context, ':', data);
22
+ return data;
21
23
  }
22
24
 
23
25
  if (response.status === 401) {
@@ -139,12 +141,12 @@ async function updateExecuitonDetails(frothUrl, token, id, resultdetails) {
139
141
  formData.append('excution_status', resultdetails.excution_status);
140
142
  }
141
143
 
142
- if (
143
- resultdetails.excution_time &&
144
- resultdetails.excution_time !== 'NaN:NaN:NaN'
145
- ) {
146
- formData.append('excution_time', resultdetails.excution_time);
147
- }
144
+ // if (
145
+ // resultdetails.excution_time &&
146
+ // resultdetails.excution_time !== 'NaN:NaN:NaN'
147
+ // ) {
148
+ // formData.append('excution_time', resultdetails.excution_time);
149
+ // }
148
150
 
149
151
  if (resultdetails.comments === null)
150
152
  console.log("Comments is null")
@@ -170,9 +172,7 @@ async function updateExecuitonDetails(frothUrl, token, id, resultdetails) {
170
172
  data = await response.text();
171
173
  }
172
174
 
173
- // console.log('📤 API Response Status:', response.status);
174
- // console.log('📤 API Response Headers:', Array.from(response.headers.entries()));
175
- // console.log('📤 API Response Body:', data);
175
+ console.log('📥 Response from updateExecuitonDetails:', data);
176
176
 
177
177
  if (!response.ok) {
178
178
  console.error(`❌ Failed to update execution details, status ${response.status}`);
@@ -1,5 +1,5 @@
1
1
  // Function to verify text in Android app
2
- import assert from 'assert';
2
+ const assert = require('assert');
3
3
  const amendToBrowserstack = require("../froth_api_calls/browsersatckSessionInfo").amend2Browserstack;
4
4
 
5
5
  async function assertText(elementSelector, expectedText) {
@@ -75,7 +75,70 @@ async function validateDbFields(actualData, expectedData, context = 'DB Validati
75
75
  }
76
76
 
77
77
  // -----------------------------
78
- // AUTO-DETECT TIMESTAMP VALIDATION
78
+ // NULLABLE VALIDATION (allow null/empty)
79
+ // -----------------------------
80
+ if (typeof expectedConfig === "object" && expectedConfig.type === "nullable") {
81
+ const isNullOrEmpty = actual === null || actual === undefined || actual === "" || actual === "null";
82
+ status = isNullOrEmpty ? "PASS" : "PASS"; // Always pass - just validates field exists
83
+ results.push({
84
+ key,
85
+ actual,
86
+ expected: "(nullable - any value allowed)",
87
+ status
88
+ });
89
+ continue;
90
+ }
91
+
92
+ // -----------------------------
93
+ // NUMBER WITH TOLERANCE
94
+ // -----------------------------
95
+ if (typeof expectedConfig === "object" && expectedConfig.type === "number") {
96
+ expected = expectedConfig.value;
97
+ const tolerance = expectedConfig.tolerance || 0;
98
+
99
+ const actualNum = parseFloat(actual);
100
+ const expectedNum = parseFloat(expected);
101
+
102
+ if (isNaN(actualNum) || isNaN(expectedNum)) {
103
+ status = "FAIL";
104
+ } else {
105
+ const diff = Math.abs(actualNum - expectedNum);
106
+ status = diff <= tolerance ? "PASS" : "FAIL";
107
+ }
108
+
109
+ results.push({
110
+ key,
111
+ actual,
112
+ expected: `${expected} (±${tolerance})`,
113
+ status
114
+ });
115
+
116
+ if (status === "FAIL") allPassed = false;
117
+ continue;
118
+ }
119
+
120
+ // -----------------------------
121
+ // PARTIAL STRING MATCH
122
+ // -----------------------------
123
+ if (typeof expectedConfig === "object" && expectedConfig.type === "partial") {
124
+ const substring = expectedConfig.contains;
125
+ const actualStr = String(actual || "");
126
+
127
+ status = actualStr.includes(substring) ? "PASS" : "FAIL";
128
+
129
+ results.push({
130
+ key,
131
+ actual,
132
+ expected: `(contains "${substring}")`,
133
+ status
134
+ });
135
+
136
+ if (status === "FAIL") allPassed = false;
137
+ continue;
138
+ }
139
+
140
+ // -----------------------------
141
+ // TIMESTAMP VALIDATION
79
142
  // -----------------------------
80
143
  if (typeof expectedConfig === "object" && expectedConfig.type === "timestamp") {
81
144
  expected = expectedConfig.value;
@@ -124,13 +187,13 @@ async function validateDbFields(actualData, expectedData, context = 'DB Validati
124
187
  // -----------------------------
125
188
  // BROWSERSTACK LOGGING
126
189
  // -----------------------------
127
- // const chunkSize = 800;
128
- // const combined = results
129
- // .map(r => `${r.key} => Expected: ${r.expected}, Actual: ${r.actual}, Status: ${r.status}`)
130
- // .join(" | ");
190
+ // const chunkSize = 800;
191
+ const combined = results
192
+ .map(r => `${r.key} => Expected: ${r.expected}, Actual: ${r.actual}, Status: ${r.status}`)
193
+ .join(" | ");
131
194
 
132
- // const chunks = combined.match(new RegExp(`.{1,${chunkSize}}`, "g")) || [];
133
- // for (const c of chunks) await amendBrowserStackLog(c, "info");
195
+ // const chunks = combined.match(new RegExp(`.{1,${chunkSize}}`, "g")) || [];
196
+ // for (const c of chunks) await amendBrowserStackLog(c, "info");
134
197
 
135
198
  await amendBrowserStackLog(combined, "info");
136
199
  // -----------------------------
@@ -147,4 +210,10 @@ async function validateDbFields(actualData, expectedData, context = 'DB Validati
147
210
  module.exports = {
148
211
  parseTimestamp,
149
212
  validateDbFields
150
- };
213
+ };
214
+
215
+ // async function main(){
216
+
217
+ // }
218
+
219
+ // main()
@@ -4,6 +4,9 @@ console.log('====>SUITE_FILE:', SUITE_FILE);
4
4
  const { LocalStorage } = require('node-localstorage');
5
5
  global.BUFFER = new LocalStorage('./buffer_storage');
6
6
 
7
+ // Make Util globally available across all test files
8
+ global.Util = require('../froth_common_actions/Utils');
9
+
7
10
  module.exports = {
8
11
  specs: require(SUITE_FILE).tests,
9
12
  exclude: [],
@@ -1,7 +1,7 @@
1
1
 
2
2
  const setAllDetails = require('./setallDatailinBuffer');
3
3
  const exeDetails = require('../froth_api_calls/getexecutionDetails');
4
- const { getBSSessionDetails } = require('../froth_api_calls/browsersatckSessionInfo');
4
+ const { getBSSessionDetails, updateBrowserStackReport } = require('../froth_api_calls/browsersatckSessionInfo');
5
5
  let globalErrorHandled = false;
6
6
  let suiteStartTime = 0;
7
7
  let totalTestDuration = 0;
@@ -14,7 +14,7 @@ let executionUpdated = false;
14
14
  const resultdetails = {
15
15
  comments: [],
16
16
  excution_status: null,
17
- excution_time: null
17
+ // excution_time: null
18
18
  };
19
19
 
20
20
  /* ----------------- GLOBAL ERROR HANDLERS ----------------- */
@@ -187,37 +187,49 @@ const commonHooks = {
187
187
  suiteStartTime = Date.now();
188
188
 
189
189
  if (process.env.PLATFORM === 'browserstack' || process.env.PLATFORM === 'browserstacklocal') {
190
- await getBSSessionDetails(
191
- process.env.BS_SESSION_TYPE,
192
- process.env.BROWSERSTACK_USERNAME,
193
- process.env.BROWSERSTACK_ACCESS_KEY
194
- );
190
+ // await getBSSessionDetails(
191
+ // process.env.BS_SESSION_TYPE,
192
+ // process.env.BROWSERSTACK_USERNAME,
193
+ // process.env.BROWSERSTACK_ACCESS_KEY
194
+ // );
195
+
196
+ // Update BrowserStack report with in_progress status
197
+ const buildName = process.env.FROTH_TESTOPS_BUILD_NAME;
198
+ if (buildName) {
199
+ await updateBrowserStackReport(
200
+ BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
201
+ BUFFER.getItem('FROTH_LOGIN_TOKEN'),
202
+ buildName,
203
+ BUFFER.getItem('FROTH_EXECUTION_ID'),
204
+ 'in_progress'
205
+ );
206
+ }
195
207
  }
196
208
 
197
- await exeDetails.update_CICDRUNID_ReportUrl(
198
- BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
199
- BUFFER.getItem('FROTH_LOGIN_TOKEN'),
200
- BUFFER.getItem('FROTH_EXECUTION_ID')
201
- );
209
+ // await exeDetails.update_CICDRUNID_ReportUrl(
210
+ // BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
211
+ // BUFFER.getItem('FROTH_LOGIN_TOKEN'),
212
+ // BUFFER.getItem('FROTH_EXECUTION_ID')
213
+ // );
202
214
  },
203
215
  /* ========== BEFORE TEST ========== */
204
216
  beforeTest: async (test) => {
205
217
  console.log(`▶️ START TEST: ${test.title}`);
206
218
 
207
- // Get suite details and script info
208
- const fileName = path.basename(test.file);
209
- const suiteDetails = JSON.parse(BUFFER.getItem('FROTHE_SUITE_DETAILS'));
210
- const script = suiteDetails.find(s => s.scriptName === fileName.replace('.js', ''));
211
-
212
- if (script) {
213
- await exeDetails.updateScriptExecutionStatus(
214
- BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
215
- BUFFER.getItem('FROTH_LOGIN_TOKEN'),
216
- script.scriptId,
217
- script.platform.toLowerCase(),
218
- "IN PROGRESS"
219
- );
220
- }
219
+ // Update script status to IN_PROGRESS
220
+ // const fileName = path.basename(test.file);
221
+ // const suiteDetails = JSON.parse(BUFFER.getItem('FROTHE_SUITE_DETAILS'));
222
+ // const script = suiteDetails.find(s => s.scriptName === fileName.replace('.js', ''));
223
+
224
+ // if (script) {
225
+ // await exeDetails.updateScriptExecutionStatus(
226
+ // BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
227
+ // BUFFER.getItem('FROTH_LOGIN_TOKEN'),
228
+ // script.scriptId,
229
+ // script.platform.toLowerCase(),
230
+ // 'IN_PROGRESS'
231
+ // );
232
+ // }
221
233
  },
222
234
 
223
235
  /* ========== AFTER TEST ========== */
@@ -273,7 +285,7 @@ const commonHooks = {
273
285
  console.log('⏱ ==== Execution time calculation ==== ');
274
286
  const endTime = Date.now();
275
287
  const totalTime = endTime - suiteStartTime; // Full WDIO+CI elapsed time
276
- resultdetails.excution_time = await msToTime(totalTime);
288
+ // resultdetails.excution_time = await msToTime(totalTime);
277
289
 
278
290
  console.log("Exit code after session===>" + exitCode)
279
291
  if (exitCode === undefined || exitCode === null) {
@@ -284,9 +296,24 @@ const commonHooks = {
284
296
  console.log('Comments being sent:', resultdetails.comments);
285
297
  //resultdetails.comments.push(BUFFER.getItem('ALL_COMMENTS'))
286
298
  console.log('⏱ Final execution time (ms):', totalTime);
287
- console.log('⏱ Final execution time (hh:mm:ss):', resultdetails.excution_time);
299
+ // console.log('⏱ Final execution time (hh:mm:ss):', resultdetails.excution_time);
288
300
  await safeUpdateExecution();
289
301
 
302
+ // Update BrowserStack report with completed status
303
+ if (process.env.PLATFORM === 'browserstack' || process.env.PLATFORM === 'browserstacklocal') {
304
+ const buildName = process.env.FROTH_TESTOPS_BUILD_NAME;
305
+ if (buildName) {
306
+ await updateBrowserStackReport(
307
+ BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
308
+ BUFFER.getItem('FROTH_LOGIN_TOKEN'),
309
+ buildName,
310
+ BUFFER.getItem('FROTH_EXECUTION_ID'),
311
+ 'completed'
312
+ );
313
+ }
314
+
315
+ }
316
+
290
317
 
291
318
  },
292
319
 
@@ -306,28 +333,14 @@ const commonHooks = {
306
333
  );
307
334
  }
308
335
 
309
- // Mark all scripts as SKIPPED if error causes all scripts to be skipped
310
- try {
311
- const suiteDetails = JSON.parse(BUFFER.getItem('FROTHE_SUITE_DETAILS'));
312
- for (const script of suiteDetails) {
313
- await exeDetails.updateScriptExecutionStatus(
314
- BUFFER.getItem('ORGANISATION_DOMAIN_URL'),
315
- BUFFER.getItem('FROTH_LOGIN_TOKEN'),
316
- script.scriptId,
317
- script.platform.toLowerCase(),
318
- 'SKIPPED'
319
- );
320
- }
321
- } catch (e) {
322
- console.error('Failed to mark scripts as SKIPPED:', e.message);
323
- }
324
-
325
336
  await safeUpdateExecution();
326
337
  },
327
338
  /* ========== ON COMPLETE ========== */
328
339
  onComplete: async (exitCode, _, __, results) => {
329
340
  console.log('==== ON COMPLETE ====');
330
341
  console.log(`Total: ${results.total || 0}, Passed: ${results.passed || 0}, Failed: ${results.failed || 0}`);
342
+ //console.log("Exit code onComplete===>" + exitCode)
343
+
331
344
  if (exitCode === undefined || exitCode === null) {
332
345
  exitCode = 1 // treat as failure
333
346
  }
@@ -385,7 +398,7 @@ async function validateSyntax(specs) {
385
398
  let syntaxFailed = false;
386
399
 
387
400
  for (const fileUrl of specs) {
388
- console.log("file path ", fileUrl)
401
+ console.log("file path ",fileUrl)
389
402
  const filePath = url.fileURLToPath(fileUrl);
390
403
  const code = fs.readFileSync(filePath, 'utf8');
391
404
 
@@ -1,8 +1,58 @@
1
- module.exports = () => ({
2
- services: ['chromedriver'],
1
+ module.exports = (bsCaps) => {
2
+ const browserName = (bsCaps.browserName || 'chrome').toLowerCase();
3
+ //const browserVersion = bsCaps.browserVersion;
3
4
 
5
+ // Map browser names to their WebdriverIO services
6
+ const browserServiceMap = {
7
+ chrome: 'chromedriver',
8
+ firefox: 'geckodriver',
9
+ edge: 'edgedriver',
10
+ ie: 'iedriver',
11
+ 'internet explorer': 'iedriver',
12
+ safari: 'safaridriver'
13
+ };
4
14
 
5
- capabilities: [{
6
- browserName: 'chrome'
7
- }]
8
- });
15
+ const service = browserServiceMap[browserName] || 'chromedriver';
16
+
17
+ const config = {
18
+ services: [service]
19
+ };
20
+
21
+ // Add browser-specific capabilities
22
+ if (browserName === 'chrome') {
23
+ config.capabilities = [{
24
+ browserName: 'chrome',
25
+ 'goog:chromeOptions': {
26
+ // args: browserVersion ? [`--version=${browserVersion}`] : []
27
+ }
28
+ }];
29
+ } else if (browserName === 'firefox') {
30
+ config.capabilities = [{
31
+ browserName: 'firefox',
32
+ 'moz:firefoxOptions': {
33
+ // Add Firefox-specific options if needed
34
+ }
35
+ }];
36
+ } else if (browserName === 'edge') {
37
+ config.capabilities = [{
38
+ browserName: 'MicrosoftEdge'
39
+ }];
40
+ } else if (browserName === 'safari') {
41
+ config.capabilities = [{
42
+ browserName: 'safari'
43
+ }];
44
+ } else if (browserName === 'ie' || browserName === 'internet explorer') {
45
+ config.capabilities = [{
46
+ browserName: 'internet explorer'
47
+ }];
48
+ } else {
49
+ // Default to Chrome for unsupported browsers
50
+ config.capabilities = [{
51
+ browserName: 'chrome'
52
+ }];
53
+ }
54
+
55
+ console.log(`🌐 Local execution configured for: ${browserName}${browserVersion ? ` (v${browserVersion})` : ''} using service: ${service}`);
56
+
57
+ return config;
58
+ };
@@ -22,7 +22,7 @@ try {
22
22
  process.env.BROWSERSTACK_ACCESS_KEY = Buffer.from(bsCaps.accessKey, 'base64').toString('utf-8')
23
23
  // Merge chrome-specific options if applicable
24
24
 
25
- } catch (e) {
25
+ } catch (err) {
26
26
  console.error('❌ Failed to load capability YAML:', err.message);
27
27
  process.exit(1);
28
28
  }
package/package.json CHANGED
@@ -1,19 +1,10 @@
1
- {
2
- "name": "froth-webdriverio-framework",
3
- "version": "7.0.118",
1
+ { "name": "froth-webdriverio-framework",
2
+ "version": "7.0.119-dev1.0",
4
3
  "readme": "WebdriverIO Integration",
5
4
  "description": "WebdriverIO and BrowserStack App Automate",
6
5
  "license": "MIT",
7
6
 
8
7
  "scripts": {
9
- "wdio:ios": "npx wdio ./ios.conf.js",
10
- "wdio:android": "npx wdio ./android.conf.js",
11
- "wdio:web": "npx wdio ./web.conf.js",
12
- "wdio:webbs": "npx wdio ./web.conf.bs.js",
13
- "wdio:ios:local": "npx wdio ./ios.local.conf.js",
14
- "android:generate-allure-report": "allure generate ./mobile/androidReports/allure-results --clean && allure open",
15
- "ios:generate-allure-report": "allure generate ./mobile/iosReports/allure-results --clean && allure open",
16
- "web:generate-allure-report": "allure generate ./webReports/allure-results --clean && allure open",
17
8
  "lint": "eslint ."
18
9
  },
19
10
  "repository": {
@@ -26,16 +17,17 @@
26
17
  "appium"
27
18
  ],
28
19
  "dependencies": {
29
- "@wdio/appium-service": "^9.23.0",
30
- "@wdio/browserstack-service": "^9.23.0",
31
- "@wdio/cli": "^9.23.0",
32
- "@wdio/local-runner": "^9.23.0",
33
- "@wdio/mocha-framework": "^9.23.0",
20
+
21
+ "@wdio/appium-service": "9.23.0",
22
+ "@wdio/browserstack-service": "9.23.0",
23
+ "@wdio/cli": "9.23.0",
24
+ "@wdio/local-runner": "9.23.0",
25
+ "@wdio/mocha-framework": "9.23.0",
34
26
  "@wdio/spec-reporter": "^9.20.0",
35
27
  "appium": "^3.1.2",
36
28
  "appium-uiautomator2-driver": "^6.7.8",
37
29
  "assert": "^2.1.0",
38
- "axios": "^1.13.2",
30
+ "axios": "1.14.0",
39
31
  "browserstack-local": "^1.5.8",
40
32
  "chai": "^6.2.2",
41
33
  "crypto-js": "^4.2.0",
@@ -52,6 +44,8 @@
52
44
  "randexp": "^0.5.3",
53
45
  "ts-node": "^10.9.2",
54
46
  "typescript": "^5.9.3",
47
+
55
48
  "vm": "^0.1.0"
49
+
56
50
  }
57
- }
51
+ }
File without changes
File without changes