froth-webdriverio-framework 7.0.119-dev1.1 → 7.0.119-dev1.11

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.
@@ -1,57 +1,168 @@
1
+ const commonHooks = require('../commonhook');
2
+
1
3
  module.exports = (bsCaps) => {
2
4
  const browserName = (bsCaps.browserName || 'chrome').toLowerCase();
3
5
 
4
- // Map browser names to their WebdriverIO services
5
- const browserServiceMap = {
6
- chrome: 'chromedriver',
7
- firefox: 'geckodriver',
8
- edge: 'edgedriver',
9
- ie: 'iedriver',
10
- 'internet explorer': 'iedriver',
11
- safari: 'safaridriver'
6
+ // Generate unique report directory using BROWSERSTACK_BUILD_NAME
7
+ const buildName = process.env.BROWSERSTACK_BUILD_NAME || 'local-build';
8
+ // Remove spaces and special characters from build name for folder name - use underscores
9
+ const cleanBuildName = buildName.replace(/[^a-zA-Z0-9_]/g, '_');
10
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0]; // YYYY-MM-DD format
11
+ const uniqueReportDir = `./reports/allure/${cleanBuildName}-${timestamp}`;
12
+
13
+ // Normalize browser names for WebDriver
14
+ const browserNameMap = {
15
+ chrome: 'chrome',
16
+ chromium: 'chrome',
17
+ firefox: 'firefox',
18
+ edge: 'MicrosoftEdge',
19
+ msedge: 'MicrosoftEdge',
20
+ 'microsoft edge': 'MicrosoftEdge',
21
+ safari: 'safari',
22
+ ie: 'internet explorer',
23
+ 'internet explorer': 'internet explorer',
24
+ 'internet-explorer': 'internet explorer'
12
25
  };
13
26
 
14
- const service = browserServiceMap[browserName] || 'chromedriver';
27
+ const normalizedBrowserName = browserNameMap[browserName] || 'chrome';
15
28
 
16
- const config = {
17
- services: [service]
18
- };
29
+ // Configure browser-specific capabilities
30
+ let capabilities;
31
+ let service;
19
32
 
20
- // Add browser-specific capabilities
21
- if (browserName === 'chrome') {
22
- config.capabilities = [{
33
+ if (normalizedBrowserName === 'chrome') {
34
+ capabilities = [{
23
35
  browserName: 'chrome',
24
36
  'goog:chromeOptions': {
25
- // args: browserVersion ? [`--version=${browserVersion}`] : []
37
+ args: [
38
+ '--no-sandbox',
39
+ '--disable-dev-shm-usage',
40
+ '--disable-gpu',
41
+ '--disable-web-security',
42
+ '--disable-features=VizDisplayCompositor'
43
+ ]
26
44
  }
27
45
  }];
28
- } else if (browserName === 'firefox') {
29
- config.capabilities = [{
46
+
47
+ // Use wdio-chromedriver-service - auto-downloads ChromeDriver at runtime
48
+ // No admin permission needed - downloads to node_modules/
49
+ service = ['chromedriver', {
50
+ // Automatically download ChromeDriver if not present
51
+ chromedriverOptions: {
52
+ // Check for ChromeDriver updates (set to false for faster startup)
53
+ checkForUpdates: true,
54
+ // Use latest ChromeDriver compatible with installed Chrome
55
+ // If false, uses version specified in package.json chromedriver dependency
56
+ autoDownload: true
57
+ }
58
+ }]
59
+
60
+ } else if (normalizedBrowserName === 'firefox') {
61
+ capabilities = [{
30
62
  browserName: 'firefox',
31
63
  'moz:firefoxOptions': {
32
- // Add Firefox-specific options if needed
64
+ //args: ['-headless'],
65
+ prefs: {
66
+ 'dom.webdriver.enabled': false,
67
+ 'useAutomationExtension': false
68
+ }
33
69
  }
34
70
  }];
35
- } else if (browserName === 'edge') {
36
- config.capabilities = [{
37
- browserName: 'MicrosoftEdge'
38
- }];
39
- } else if (browserName === 'safari') {
40
- config.capabilities = [{
41
- browserName: 'safari'
71
+
72
+ // Use wdio-geckodriver-service for Firefox
73
+ service = ['geckodriver', {
74
+ // Auto-download geckodriver if not present
75
+ geckodriverOptions: {
76
+ // Use the geckodriver from node_modules
77
+ // No need to download separately
78
+ }
42
79
  }];
43
- } else if (browserName === 'ie' || browserName === 'internet explorer') {
44
- config.capabilities = [{
45
- browserName: 'internet explorer'
80
+ } else if (normalizedBrowserName === 'MicrosoftEdge') {
81
+ capabilities = [{
82
+ browserName: 'MicrosoftEdge',
83
+ 'ms:edgeOptions': {
84
+ args: [
85
+ '--no-sandbox',
86
+ '--disable-dev-shm-usage',
87
+ '--disable-gpu'
88
+ ]
89
+ }
46
90
  }];
91
+
92
+ // EdgeDriver is included with Edge browser - no download needed
93
+ service = ['edgedriver'];
94
+
47
95
  } else {
48
96
  // Default to Chrome for unsupported browsers
49
- config.capabilities = [{
50
- browserName: 'chrome'
97
+ capabilities = [{
98
+ browserName: 'chrome',
99
+ 'goog:chromeOptions': {
100
+ args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
101
+ }
102
+ }];
103
+
104
+ service = ['chromedriver', {
105
+ chromedriverOptions: {
106
+ checkForUpdates: true,
107
+ autoDownload: true
108
+ }
51
109
  }];
52
110
  }
53
111
 
54
- console.log(`🌐 Local execution configured for: ${browserName} using service: ${service}`);
112
+ const config = {
113
+ // Common hooks - enable DB status updates (execution + script status)
114
+ ...commonHooks,
115
+
116
+ // Use direct WebDriver connection without Selenium Grid/Hub
117
+ // Opens Chrome/Firefox directly - no selenium-server
118
+ protocol: 'webdriver',
119
+
120
+ // Auto-download drivers service + real-time screenshot service
121
+ services: [
122
+ ...(service.length > 0 ? [service] : []),
123
+ // Real-time screenshot capture (works even if execution stopped mid-way)
124
+ [require('../screenshot-service').ScreenshotService]
125
+ ],
126
+
127
+ capabilities: capabilities,
128
+
129
+ // Allure Reporter Configuration
130
+ reporters: [
131
+ ['allure', {
132
+ outputDir: uniqueReportDir,
133
+ disableWebdriverStepsReporting: false, // Enable step reporting
134
+ disableWebdriverScreenshotsReporting: false, // Enable automatic screenshot capture
135
+ useCucumberStepReporter: false,
136
+ disableMochaHooks: false,
137
+ allureCode: true,
138
+ captureAllureScreenshots: true
139
+ }]
140
+ ],
141
+
142
+ // Auto-generate report after execution completes
143
+ onComplete: async function(exitCode, config, capabilities, results) {
144
+ const { execSync } = require('child_process');
145
+ const nodePath = require('path');
146
+ try {
147
+ // Add a small delay to ensure Allure finishes writing all files
148
+ await new Promise(resolve => setTimeout(resolve, 1000));
149
+
150
+ // Resolve script path relative to framework package (works from any CWD)
151
+ const scriptPath = nodePath.resolve(__dirname, '../../allure-report-utils/generate-allure-report.js');
152
+
153
+ console.log('\n🎯 Generating Allure report...');
154
+ execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: process.cwd() });
155
+ console.log('✅ Report generation complete!');
156
+ } catch (error) {
157
+ console.error('❌ Error generating report:', error.message);
158
+ }
159
+ }
160
+ };
161
+
162
+ console.log(`🌐 Local execution configured for: ${normalizedBrowserName}`);
163
+ console.log(`📦 Browser drivers will auto-download to node_modules/ (no admin permission needed)`);
164
+ console.log(`📊 Allure results will be saved in: ${uniqueReportDir}`);
165
+ console.log(`💡 Report will be generated automatically after execution completes`);
55
166
 
56
167
  return config;
57
- };
168
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Froth Screenshot Service for WebDriverIO
3
+ * Captures screenshots in REAL-TIME:
4
+ * - After each KEY ACTION (click, input, navigation) - no duplicates
5
+ * - After each TEST (afterTest) - final state per test
6
+ * - Works even if execution is stopped mid-way
7
+ * - Screenshots stored ONLY in Allure report (via addAttachment) - no separate folders
8
+ * - Deduplicates identical screenshots to avoid clutter
9
+ */
10
+
11
+ const crypto = require('crypto');
12
+
13
+ class ScreenshotService {
14
+ constructor(options = {}) {
15
+ this.options = options;
16
+ this.actionCounter = 0;
17
+ this.lastScreenshotHash = null;
18
+ this.lastActionTime = 0;
19
+ }
20
+
21
+ // Runs before test execution starts
22
+ async onPrepare() {
23
+ console.log('📸 Screenshot Service initialized - Allure-only capture (no duplicate folders)');
24
+ }
25
+
26
+ // Runs after EACH KEY ACTION - captures screenshot (with deduplication)
27
+ async afterCommand(commandName, args, result, error) {
28
+ // Only capture for local platform
29
+ if (process.env.PLATFORM !== 'local') {
30
+ return;
31
+ }
32
+
33
+ // Skip screenshot commands to avoid recursion
34
+ if (commandName === 'saveScreenshot' || commandName === 'takeScreenshot') {
35
+ return;
36
+ }
37
+
38
+ // ONLY capture these 3 key actions: navigation, click, input
39
+ const captureCommands = ['url', 'click', 'setValue'];
40
+
41
+ if (!captureCommands.includes(commandName)) {
42
+ return;
43
+ }
44
+
45
+ // Throttle: skip if last screenshot was less than 500ms ago (rapid commands)
46
+ const now = Date.now();
47
+ if (now - this.lastActionTime < 500) {
48
+ return;
49
+ }
50
+ this.lastActionTime = now;
51
+
52
+ try {
53
+ const allure = require('@wdio/allure-reporter').default;
54
+
55
+ // Take screenshot to memory (buffer) - no separate file
56
+ const screenshotBase64 = await browser.takeScreenshot();
57
+ const imageBuffer = Buffer.from(screenshotBase64, 'base64');
58
+
59
+ // Generate hash to detect duplicates
60
+ const hash = crypto.createHash('md5').update(imageBuffer).digest('hex');
61
+
62
+ // Skip if identical to last screenshot (duplicate)
63
+ if (hash === this.lastScreenshotHash) {
64
+ return;
65
+ }
66
+ this.lastScreenshotHash = hash;
67
+
68
+ this.actionCounter++;
69
+ const actionDesc = this.formatActionDescription(commandName, args);
70
+
71
+ // Attach to Allure IMMEDIATELY (Allure stores it in its report directory)
72
+ allure.addAttachment(`📸 ${commandName} ${actionDesc}`.trim(), imageBuffer, 'image/png');
73
+ } catch (err) {
74
+ // Silent fail to avoid breaking tests
75
+ }
76
+ }
77
+
78
+ // Format action description for screenshot name
79
+ formatActionDescription(commandName, args) {
80
+ try {
81
+ if (commandName === 'url' && args && args[0]) {
82
+ const url = args[0].toString();
83
+ return `→ ${url.substring(0, 40)}`;
84
+ }
85
+ if (commandName === 'setValue' && args && args[1] !== undefined) {
86
+ return `(input "${args[1].toString().substring(0, 25)}")`;
87
+ }
88
+ if (commandName === 'click' && args && args[0]) {
89
+ return `(${args[0].toString().substring(0, 40)})`;
90
+ }
91
+ return '';
92
+ } catch (e) {
93
+ return '';
94
+ }
95
+ }
96
+
97
+ // Runs after each test - captures final test screenshot
98
+ async afterTest(test, context, { error, result, passed, retries }) {
99
+ if (process.env.PLATFORM !== 'local') {
100
+ return;
101
+ }
102
+
103
+ try {
104
+ const allure = require('@wdio/allure-reporter').default;
105
+
106
+ // Take screenshot to buffer - no separate file
107
+ const screenshotBase64 = await browser.takeScreenshot();
108
+ const imageBuffer = Buffer.from(screenshotBase64, 'base64');
109
+
110
+ // Check for duplicate
111
+ const hash = crypto.createHash('md5').update(imageBuffer).digest('hex');
112
+ if (hash === this.lastScreenshotHash) {
113
+ // Reset for next test and skip
114
+ this.actionCounter = 0;
115
+ this.lastScreenshotHash = null;
116
+ return;
117
+ }
118
+
119
+ const attachmentName = passed ? '✅ Test Passed' : '❌ Test Failed';
120
+ allure.addAttachment(attachmentName, imageBuffer, 'image/png');
121
+ console.log(`📸 [Real-time] Test screenshot attached: ${passed ? 'success' : 'failure'}`);
122
+
123
+ // Reset for next test
124
+ this.actionCounter = 0;
125
+ this.lastScreenshotHash = null;
126
+ } catch (error) {
127
+ console.error('❌ [Real-time] Error capturing test screenshot:', error.message);
128
+ }
129
+ }
130
+ }
131
+
132
+ // Export both as class and factory function
133
+ module.exports = {
134
+ ScreenshotService,
135
+ default: () => new ScreenshotService()
136
+ };
@@ -63,5 +63,116 @@ if (PLATFORM === 'browserstack' || PLATFORM === 'browserstacklocal') {
63
63
  }
64
64
 
65
65
 
66
- exports.config = deepmerge(baseConfig, envConfig(bsCaps));
66
+ // Debug logging
67
+ console.log('=== CONFIG MERGE DEBUG ===');
68
+ console.log('PLATFORM value:', PLATFORM);
69
+ console.log('baseConfig.afterEach exists:', !!baseConfig.afterEach);
70
+ console.log('envConfig type:', typeof envConfig);
71
+
72
+ // Get the env config but don't merge yet
73
+ const envConfigResult = envConfig(bsCaps);
74
+ console.log('envConfigResult.afterEach exists:', !!envConfigResult.afterEach);
75
+
76
+ // Merge the configs
77
+ const finalConfig = deepmerge(baseConfig, envConfigResult);
78
+
79
+ // Always preserve base config hooks for local platform
80
+ if (PLATFORM === 'local' && baseConfig.afterEach) {
81
+ console.log('Preserving baseConfig.afterEach for local platform');
82
+ finalConfig.afterEach = baseConfig.afterEach;
83
+ } else if (finalConfig.afterEach) {
84
+ console.log('Using envConfig afterEach');
85
+ } else {
86
+ console.log('No afterEach hook found in any config');
87
+ }
88
+
89
+ console.log('finalConfig.afterEach type:', typeof finalConfig.afterEach);
90
+ console.log('=== END CONFIG MERGE DEBUG ===');
91
+
92
+ // Add onComplete hook to attach screenshots to test results
93
+ const originalOnComplete = finalConfig.onComplete;
94
+ finalConfig.onComplete = async function(exitCode, config, capabilities, results) {
95
+ console.log('🎯 Attaching screenshots to test results...');
96
+
97
+ try {
98
+ const fs = require('fs');
99
+ const path = require('path');
100
+
101
+ // Find the most recent Allure results directory (where screenshots are saved)
102
+ const reportsBaseDir = './reports/allure';
103
+ if (fs.existsSync(reportsBaseDir)) {
104
+ const dirs = fs.readdirSync(reportsBaseDir)
105
+ .map(file => path.join(reportsBaseDir, file))
106
+ .filter(file => fs.statSync(file).isDirectory())
107
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
108
+
109
+ if (dirs.length > 0) {
110
+ const latestDir = dirs[0];
111
+ console.log(`📁 Report directory: ${latestDir}`);
112
+
113
+ // Find screenshots saved directly in the report directory
114
+ const screenshots = fs.readdirSync(latestDir)
115
+ .filter(file => file.endsWith('.png'))
116
+ .map(file => path.join(latestDir, file));
117
+
118
+ console.log(`📸 Found ${screenshots.length} screenshots in report folder`);
119
+
120
+ // Find test result files
121
+ const resultFiles = fs.readdirSync(latestDir)
122
+ .filter(file => file.endsWith('-result.json'));
123
+
124
+ console.log(`📋 Found ${resultFiles.length} test result files`);
125
+
126
+ // Attach a screenshot to each test result
127
+ resultFiles.forEach((resultFile, index) => {
128
+ try {
129
+ const resultPath = path.join(latestDir, resultFile);
130
+ const resultData = JSON.parse(fs.readFileSync(resultPath, 'utf8'));
131
+
132
+ // Add screenshot attachment
133
+ if (screenshots.length > 0) {
134
+ const screenshotIndex = Math.min(index, screenshots.length - 1);
135
+ const screenshotPath = screenshots[screenshotIndex];
136
+ const screenshotBuffer = fs.readFileSync(screenshotPath);
137
+
138
+ // Create attachment in the Allure directory
139
+ const attachmentName = `screenshot-${index}.png`;
140
+ const attachmentPath = path.join(latestDir, attachmentName);
141
+
142
+ fs.writeFileSync(attachmentPath, screenshotBuffer);
143
+
144
+ // Add attachment reference to test result
145
+ if (!resultData.attachments) {
146
+ resultData.attachments = [];
147
+ }
148
+
149
+ resultData.attachments.push({
150
+ name: 'Screenshot',
151
+ source: attachmentName,
152
+ type: 'image/png'
153
+ });
154
+
155
+ // Write updated result file
156
+ fs.writeFileSync(resultPath, JSON.stringify(resultData, null, 2));
157
+ console.log(`✅ Attached screenshot to: ${resultFile}`);
158
+ }
159
+ } catch (error) {
160
+ console.error(`❌ Error processing ${resultFile}:`, error.message);
161
+ }
162
+ });
163
+
164
+ console.log(`✅ Attached ${resultFiles.length} screenshots to test results`);
165
+ }
166
+ }
167
+ } catch (error) {
168
+ console.error('❌ Error in onComplete screenshot attachment:', error.message);
169
+ }
170
+
171
+ // Call original onComplete if it exists
172
+ if (typeof originalOnComplete === 'function') {
173
+ await originalOnComplete(exitCode, config, capabilities, results);
174
+ }
175
+ };
176
+
177
+ exports.config = finalConfig;
67
178
 
package/package.json CHANGED
@@ -1,51 +1,63 @@
1
- { "name": "froth-webdriverio-framework",
2
- "version": "7.0.119-dev1.1",
3
- "readme": "WebdriverIO Integration",
4
- "description": "WebdriverIO and BrowserStack App Automate",
5
- "license": "MIT",
6
-
7
- "scripts": {
8
- "lint": "eslint ."
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "https://github.com/RoboticoDigitalProjects/froth-webdriverio.git"
13
- },
14
- "keywords": [
15
- "webdriverio",
16
- "browserstack",
17
- "appium"
18
- ],
19
- "dependencies": {
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",
26
- "@wdio/spec-reporter": "^9.20.0",
27
- "appium": "^3.1.2",
28
- "appium-uiautomator2-driver": "^6.7.8",
29
- "assert": "^2.1.0",
30
- "axios": "1.14.0",
31
- "browserstack-local": "^1.5.8",
32
- "chai": "^6.2.2",
33
- "crypto-js": "^4.2.0",
34
- "deepmerge": "^4.3.1",
35
- "ejs": "^4.0.1",
36
- "expect-webdriverio": "^4.13.0",
37
- "express": "^5.2.1",
38
- "form-data": "^4.0.5",
39
- "fs": "^0.0.1-security",
40
- "js-yaml": "^4.1.1",
41
- "mysql2": "^3.16.0",
42
- "node-fetch": "^3.3.2",
43
- "node-localstorage": "^3.0.5",
44
- "randexp": "^0.5.3",
45
- "ts-node": "^10.9.2",
46
- "typescript": "^5.9.3",
47
-
48
- "vm": "^0.1.0"
49
-
50
- }
1
+ {
2
+ "name": "froth-webdriverio-framework",
3
+ "version": "7.0.119-dev1.11",
4
+ "readme": "WebdriverIO Integration",
5
+ "description": "WebdriverIO and BrowserStack App Automate",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "froth-report": "allure-report-utils/froth-report.js"
9
+ },
10
+ "scripts": {
11
+ "lint": "eslint .",
12
+ "test": "npm run test:allure && npm run report:generate",
13
+ "test:allure": "wdio run froth_configs/local/web.config.js",
14
+ "report:generate": "node allure-report-utils/generate-allure-report.js",
15
+ "report:open": "node allure-report-utils/open-allure-report.js"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/RoboticoDigitalProjects/froth-webdriverio.git"
20
+ },
21
+ "keywords": [
22
+ "webdriverio",
23
+ "browserstack",
24
+ "appium"
25
+ ],
26
+ "dependencies": {
27
+ "@wdio/appium-service": "9.23.0",
28
+ "@wdio/browserstack-service": "9.23.0",
29
+ "@wdio/cli": "9.23.0",
30
+ "@wdio/local-runner": "9.23.0",
31
+ "@wdio/mocha-framework": "9.23.0",
32
+ "@wdio/selenium-standalone-service": "^8.14.0",
33
+ "@wdio/spec-reporter": "^9.20.0",
34
+ "@wdio/allure-reporter": "^9.20.0",
35
+ "allure-commandline": "^2.30.0",
36
+ "appium": "^3.1.2",
37
+ "appium-uiautomator2-driver": "^6.7.8",
38
+ "assert": "^2.1.0",
39
+ "axios": "1.14.0",
40
+ "browserstack-local": "^1.5.8",
41
+ "chai": "^6.2.2",
42
+ "chromedriver": "^149.0.4",
43
+ "crypto-js": "^4.2.0",
44
+ "deepmerge": "^4.3.1",
45
+ "ejs": "^4.0.1",
46
+ "expect-webdriverio": "^4.13.0",
47
+ "express": "^5.2.1",
48
+ "form-data": "^4.0.5",
49
+ "fs": "^0.0.1-security",
50
+ "geckodriver": "^4.5.0",
51
+ "js-yaml": "^4.1.1",
52
+ "mysql2": "^3.16.0",
53
+ "node-fetch": "^3.3.2",
54
+ "node-localstorage": "^3.0.5",
55
+ "randexp": "^0.5.3",
56
+ "ts-node": "^10.9.2",
57
+ "typescript": "^5.9.3",
58
+ "vm": "^0.1.0",
59
+ "wdio-chromedriver-service": "^8.1.1",
60
+ "wdio-geckodriver-service": "^5.0.2"
61
+ }
62
+
51
63
  }