From 850f9a91f190105d08822aaba760a0247af89611 Mon Sep 17 00:00:00 2001 From: Rohit Shinde Date: Mon, 1 Sep 2025 18:41:48 +0530 Subject: [PATCH 1/4] Add BrowserStack pipeline and session artifact capture --- .github/workflows/browserstack.yml | 69 +++++++++++++++++++++++ backend/package.json | 3 +- backend/scripts/run-browserstack-tests.js | 43 ++++++++++++++ backend/src/test-runner/test_executor.js | 4 ++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/browserstack.yml create mode 100755 backend/scripts/run-browserstack-tests.js diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml new file mode 100644 index 0000000..7d4f765 --- /dev/null +++ b/.github/workflows/browserstack.yml @@ -0,0 +1,69 @@ +name: BrowserStack Tests + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: android + device: 'Samsung Galaxy S23' + os_version: '13.0' + app: ./path/to/android-app.apk + - platform: ios + device: 'iPhone 15' + os_version: '17' + app: ./path/to/ios-app.ipa + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + TEST_PATH: tests/sample_login.json + AI_SERVICE: gemini + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: backend/package-lock.json + - name: Install backend dependencies + working-directory: backend + run: npm ci + - name: Run BrowserStack tests + id: run-tests + working-directory: backend + env: + APP_PATH: ${{ matrix.app }} + PLATFORM: ${{ matrix.platform }} + DEVICE_NAME: ${{ matrix.device }} + OS_VERSION: ${{ matrix.os_version }} + run: npm run test:browserstack + - name: Collect BrowserStack artifacts + if: ${{ steps.run-tests.outputs.session_id }} + working-directory: backend + env: + SESSION_ID: ${{ steps.run-tests.outputs.session_id }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + run: | + mkdir -p artifacts + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + "https://api.browserstack.com/app-automate/sessions/$SESSION_ID.json" -o session.json + VIDEO_URL=$(jq -r '.automation_session.video_url' session.json) + LOG_URL=$(jq -r '.automation_session.appium_logs_url' session.json) + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" "$VIDEO_URL" -o artifacts/${SESSION_ID}-video.mp4 + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" "$LOG_URL" -o artifacts/${SESSION_ID}-appium.log + echo "### BrowserStack Session $SESSION_ID" >> $GITHUB_STEP_SUMMARY + echo "- [Video]($VIDEO_URL)" >> $GITHUB_STEP_SUMMARY + echo "- [Appium Log]($LOG_URL)" >> $GITHUB_STEP_SUMMARY + - uses: actions/upload-artifact@v4 + if: ${{ steps.run-tests.outputs.session_id }} + with: + name: browserstack-${{ matrix.platform }}-${{ matrix.os_version }} + path: backend/artifacts diff --git a/backend/package.json b/backend/package.json index 7c8bdf4..21c30cc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,8 @@ "main": "src/app.js", "scripts": { "start": "node src/app.js", - "dev": "nodemon src/app.js" + "dev": "nodemon src/app.js", + "test:browserstack": "node scripts/run-browserstack-tests.js" }, "keywords": [ "appium", diff --git a/backend/scripts/run-browserstack-tests.js b/backend/scripts/run-browserstack-tests.js new file mode 100755 index 0000000..8b2724a --- /dev/null +++ b/backend/scripts/run-browserstack-tests.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const { executeTest } = require('../src/test-runner/test_executor'); + +async function main() { + const appPath = process.env.APP_PATH; + const testsPath = process.env.TEST_PATH; + const platform = process.env.PLATFORM || 'android'; + const deviceName = process.env.DEVICE_NAME || ''; + const osVersion = process.env.OS_VERSION || ''; + const aiService = process.env.AI_SERVICE || 'gemini'; + + if (!appPath || !testsPath) { + console.error('APP_PATH and TEST_PATH environment variables are required'); + process.exit(1); + } + + const rawStepsText = fs.readFileSync(path.resolve(testsPath), 'utf8'); + + const sessionId = await executeTest( + path.resolve(appPath), + rawStepsText, + { to: () => ({ emit: () => {} }) }, + 'cli', + aiService, + 'browserstack', + platform, + deviceName, + osVersion, + ); + + console.log(`BrowserStack session ID: ${sessionId}`); + + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `session_id=${sessionId}\n`); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/backend/src/test-runner/test_executor.js b/backend/src/test-runner/test_executor.js index f101914..0e93a0e 100644 --- a/backend/src/test-runner/test_executor.js +++ b/backend/src/test-runner/test_executor.js @@ -292,6 +292,7 @@ async function executeTest( platformVersion = '', ) { let browser; + let sessionId = null; // Normalise the platform once so that helper functions can use it. This // variable persists through the lifetime of the test execution. const targetPlatform = (platform || 'android').toLowerCase(); @@ -377,7 +378,9 @@ async function executeTest( console.log('Attempting to start remote session...'); browser = await remote({ ...appiumOptions, capabilities }); + sessionId = browser.sessionId; console.log('Remote session started successfully.'); + console.log(`BrowserStack session ID: ${sessionId}`); // --- NEW: Identify the app and prepare its specific cache --- // Use appPackage for Android or bundleId for iOS when caching selectors. @@ -606,6 +609,7 @@ async function executeTest( console.error('Failed to delete uploaded app file:', fileCleanupError); } } + return sessionId; } /** From d9cdb06d5b116c8395076f3208cc3b2e61dc888d Mon Sep 17 00:00:00 2001 From: Rohit Shinde Date: Mon, 1 Sep 2025 19:13:49 +0530 Subject: [PATCH 2/4] Update gemini and deepseek env variables --- .github/workflows/browserstack.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml index 7d4f765..f6cb077 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -23,6 +23,8 @@ jobs: env: BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} TEST_PATH: tests/sample_login.json AI_SERVICE: gemini steps: From e9bc7bb233bcf08d1ae3dad90768b28d434da987 Mon Sep 17 00:00:00 2001 From: Rohit Shinde Date: Mon, 1 Sep 2025 19:17:35 +0530 Subject: [PATCH 3/4] Update sample_login.json --- tests/sample_login.json | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/sample_login.json b/tests/sample_login.json index 93dbafb..0e8bba1 100644 --- a/tests/sample_login.json +++ b/tests/sample_login.json @@ -3,10 +3,14 @@ "tags": ["smoke", "login"], "steps": [ "Launch the app", - "Wait for the login screen to appear", - "Enter valid username", - "Enter valid password", - "Tap on the Login button", - "Verify successful login" + "Wait for app to load the initial page", + "Click on ‘LOG IN’ button", + "Wait for app to load the login page", + "Type ‘Pankaj1.aug@gmail.com’ into ‘Email’", + "Type ‘Pankaj@123’ into ‘Password’", + "Click on ‘LOG IN’ button", + "Wait for app to load the home page", + "Verify that Toggle with options ‘Delivery’ and ‘Pick up’ is available on top of home page", + "Verify that 'search' textbox is visible with placeholder text as ‘What can we get you?’ Is available on home page" ] -} \ No newline at end of file +} From 2bbc926b1ab15856ce5e37f4716e24d4b3682fe0 Mon Sep 17 00:00:00 2001 From: "rohit.shinde" Date: Sat, 6 Sep 2025 21:20:59 +0530 Subject: [PATCH 4/4] Update config.js --- backend/src/config.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/config.js b/backend/src/config.js index 51c451f..6dc8501 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -43,7 +43,8 @@ config.testsDir = resolveRelativePath(process.env.TESTS_DIR, defaultTestsDir); // Maximum upload size (in megabytes). Values from process.env are // coerced to integers; invalid values fall back to 50MB. config.maxUploadMb = (() => { - const raw = process.env.MAX_UPLOAD_MB; + //const raw = process.env.MAX_UPLOAD_MB; + const raw = 100; // default to 100MB const parsed = parseInt(raw, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : 50; })(); @@ -53,8 +54,8 @@ config.maxUploadMb = (() => { config.allowedOrigin = process.env.ALLOWED_ORIGIN || '*'; // Should uploaded APKs be cleaned up when a test completes? Accept -// 'true' or 'false' strings; any other value is treated as false. -config.cleanUploadsAfterTest = (process.env.CLEAN_UPLOADS_AFTER_TEST || 'false').toLowerCase() === 'true'; +// 'true' or 'false' strings; any other value is treated as true. +config.cleanUploadsAfterTest = (process.env.CLEAN_UPLOADS_AFTER_TEST || 'true').toLowerCase() === 'false'; // AI service keys. These are required for the NLP service to run. config.geminiApiKey = process.env.GEMINI_API_KEY;