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.
@@ -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
- //const capabilities = yaml.load(fs.readFileSync(path.join(path.resolve(process.cwd(), configFile)), 'utf8'));
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(rawCaps) {
82
- console.log("==== Running setupPrerequisites() ====");
19
+ function setupPrerequisites() {
20
+ console.log("inside this fuction first ");
83
21
  if (PLATFORM === 'browserstack') {
84
- process.env.BROWSERSTACK_USERNAME = rawCaps.userName;
85
- rawCaps.accessKey = Buffer.from(rawCaps.accessKey, 'base64').toString('utf-8');
86
- process.env.BROWSERSTACK_ACCESS_KEY = rawCaps.accessKey
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
- console.log('capabilities.platformName:', rawCaps.platformName ?? 'web');
89
- const isMobile = rawCaps.platformName === "android" || rawCaps.platformName === "ios";
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(rawCaps);
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: process.env.BROWSERSTACK_USERNAME,
168
- key: process.env.BROWSERSTACK_ACCESS_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: [finalCaps],
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.17",
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};