diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml index 5398af9..b54bbdf 100644 --- a/.github/workflows/Semgrep.yml +++ b/.github/workflows/Semgrep.yml @@ -36,7 +36,7 @@ jobs: # Fetch project source with GitHub Actions Checkout. - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 # Run the "semgrep ci" command on the command line of the docker image. - - run: semgrep ci --sarif --output=semgrep.sarif + - run: semgrep ci --config auto --sarif --output=semgrep.sarif env: # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. SEMGREP_RULES: p/default # more at semgrep.dev/explore diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml new file mode 100644 index 0000000..7e88c61 --- /dev/null +++ b/.github/workflows/test-scripts.yml @@ -0,0 +1,490 @@ +name: Test Scripts - Mac, Linux and Windows +permissions: + contents: read + +on: + schedule: + # Run daily at 2 AM UTC + - cron: '0 2 * * *' + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + test-mac: + name: Test mac/run.sh on macOS + runs-on: macos-latest + timeout-minutes: 15 + environment: BrowserStack + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Set up Bash + run: | + echo "Bash version:" + bash --version + + - name: Validate shell script syntax + run: | + echo "Validating mac/run.sh syntax..." + bash -n mac/run.sh + echo "✅ mac/run.sh syntax is valid" + + - name: Validate supporting scripts syntax + run: | + echo "Validating supporting scripts..." + bash -n mac/common-utils.sh + bash -n mac/logging-utils.sh + bash -n mac/env-setup-run.sh + bash -n mac/user-interaction.sh + bash -n mac/env-prequisite-checks.sh + echo "✅ All supporting scripts are valid" + + - name: Check if scripts are executable + run: | + chmod +x mac/run.sh + chmod +x mac/common-utils.sh + chmod +x mac/logging-utils.sh + chmod +x mac/env-setup-run.sh + chmod +x mac/user-interaction.sh + chmod +x mac/env-prequisite-checks.sh + echo "✅ All scripts are executable" + + - name: Run ShellCheck on mac scripts + run: | + brew install shellcheck + echo "Running ShellCheck on mac scripts..." + shellcheck -x mac/run.sh || true + shellcheck -x mac/common-utils.sh || true + shellcheck -x mac/logging-utils.sh || true + shellcheck -x mac/env-setup-run.sh || true + shellcheck -x mac/user-interaction.sh || true + shellcheck -x mac/env-prequisite-checks.sh || true + echo "✅ ShellCheck analysis complete" + + - name: Verify required dependencies + run: | + echo "Checking required dependencies..." + command -v bash && echo "✅ bash found" + command -v curl && echo "✅ curl found" + command -v git && echo "✅ git found" + command -v bc && echo "✅ bc found" + echo "All required dependencies are available" + + - name: Test script sourcing (dry run) + run: | + set -e + echo "Testing script sourcing..." + bash -c "source mac/common-utils.sh && echo '✅ common-utils.sh sourced successfully'" + echo "✅ Script sourcing successful" + + - name: Integration Test - Silent Mode Execution + if: success() + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + TURL: https://bstackdemo.com + run: | + echo "Running integration tests in silent mode..." + + # Set default values if secrets are not provided + BROWSERSTACK_USERNAME="${BROWSERSTACK_USERNAME:-test_user}" + BROWSERSTACK_ACCESS_KEY="${BROWSERSTACK_ACCESS_KEY:-test_key}" + + export BROWSERSTACK_USERNAME + export BROWSERSTACK_ACCESS_KEY + export TURL + + # Test configurations + test_configs=( + "web java" + "app java" + "web python" + "app python" + "web nodejs" + "app nodejs" + ) + + for config in "${test_configs[@]}"; do + read -r test_type tech_stack <<< "$config" + echo "================================" + echo "Testing: mac/run.sh --silent $test_type $tech_stack" + echo "================================" + + # Run with timeout and capture exit code (macOS compatible) + bash mac/run.sh --silent "$test_type" "$tech_stack" >> "/tmp/run_test_${test_type}_${tech_stack}.log" 2>&1 & + job_pid=$! + + # Wait with 600 second timeout using sleep loop (macOS compatible) + count=0 + max_wait=600 + while kill -0 "$job_pid" 2>/dev/null && [ $count -lt $max_wait ]; do + sleep 1 + count=$((count + 1)) + done + + # Check if process is still running after timeout + if kill -0 "$job_pid" 2>/dev/null; then + echo "⚠️ mac/run.sh --silent $test_type $tech_stack timed out after 600 seconds" + kill -9 "$job_pid" 2>/dev/null || true + exit_code=124 + else + wait "$job_pid" || exit_code=$? + fi + + if [ -z "$exit_code" ] || [ "$exit_code" -eq 0 ] || [ "$exit_code" -eq 124 ]; then + echo "✅ mac/run.sh --silent $test_type $tech_stack completed (exit code: ${exit_code:-0})" + else + echo "⚠️ mac/run.sh --silent $test_type $tech_stack exited with code: $exit_code" + if [ -f "/tmp/run_test_${test_type}_${tech_stack}.log" ]; then + echo "Log output (last 20 lines):" + tail -n 20 "/tmp/run_test_${test_type}_${tech_stack}.log" + fi + fi + unset exit_code + done + + echo "✅ All integration tests completed" + + - name: Sync BrowserStack logs to workspace + if: always() + run: | + mkdir -p ${{ github.workspace }}/bs-logs + if [ -d ~/.browserstack/NOW/logs ]; then + cp -R ~/.browserstack/NOW/logs/* ${{ github.workspace }}/bs-logs/ || true + else + echo "No logs found in ~/.browserstack/NOW/logs" + fi + - name: Upload BrowserStack Logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: browserstack-logs-macos + path: | + ${{ github.workspace }}/bs-logs + /tmp/run_test_*.log + retention-days: 30 + if-no-files-found: ignore + + # test-windows: + # name: Test win/run.ps1 on Windows + # runs-on: windows-latest + # timeout-minutes: 15 + # environment: BrowserStack + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + # - name: Set up Python 3.13 + # uses: actions/setup-python@v5 + # with: + # python-version: '3.13' + # - name: Check PowerShell version + # run: | + # $PSVersionTable.PSVersion + # Write-Host "✅ PowerShell version check complete" + # + # - name: Validate PowerShell script syntax + # run: | + # Write-Host "Validating win/run.ps1 syntax..." + # $ScriptPath = "win/run.ps1" + # $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $ScriptPath), [ref]$null) + # Write-Host "✅ win/run.ps1 syntax is valid" + # + # - name: Validate supporting PowerShell scripts syntax + # run: | + # Write-Host "Validating supporting PowerShell scripts..." + # $Scripts = @("win/proxy-check.ps1") + # foreach ($Script in $Scripts) { + # $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) + # Write-Host "✅ $Script syntax is valid" + # } + # + # - name: Run PSScriptAnalyzer + # run: | + # Write-Host "Installing PSScriptAnalyzer..." + # Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -ErrorAction SilentlyContinue + # Write-Host "Running PSScriptAnalyzer..." + # Invoke-ScriptAnalyzer -Path "win/run.ps1" -Recurse -ReportSummary || $true + # Write-Host "✅ PSScriptAnalyzer analysis complete" + # + # - name: Check script file encoding + # run: | + # Write-Host "Checking PowerShell script encoding..." + # $ScriptPath = "win/run.ps1" + # $Encoding = (Get-Item $ScriptPath).EncodingInfo + # Write-Host "File encoding: $Encoding" + # Write-Host "✅ Encoding check complete" + # + # - name: Verify required dependencies + # run: | + # Write-Host "Checking required dependencies..." + # if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "✅ curl found" } + # if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "✅ git found" } + # Write-Host "✅ PowerShell dependencies verified" + # + # - name: Integration Test - Silent Mode Execution + # if: success() + # env: + # BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + # BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + # TURL: https://bstackdemo.com + # run: | + # Write-Host "Running integration tests in silent mode..." + # + # # Set default values if secrets are not provided + # $BrowserStackUsername = if ($env:BROWSERSTACK_USERNAME) { $env:BROWSERSTACK_USERNAME } else { "test_user" } + # $BrowserStackAccessKey = if ($env:BROWSERSTACK_ACCESS_KEY) { $env:BROWSERSTACK_ACCESS_KEY } else { "test_key" } + # $TestUrl = $env:TURL + # + # # Export environment variables + # $env:BROWSERSTACK_USERNAME = $BrowserStackUsername + # $env:BROWSERSTACK_ACCESS_KEY = $BrowserStackAccessKey + # $env:TURL = $TestUrl + # + # # Test configurations + # $testConfigs = @( + # @("web", "java"), + # @("app", "java"), + # @("web", "python"), + # @("app", "python"), + # @("web", "nodejs"), + # @("app", "nodejs") + # ) + # + # foreach ($config in $testConfigs) { + # $testType = $config[0] + # $techStack = $config[1] + # + # Write-Host "================================" + # Write-Host "Testing: .\win\run.ps1 --silent $testType $techStack" + # Write-Host "================================" + # + # # Create log file path + # $logPath = "C:\Temp\run_test_${testType}_${techStack}.log" + # New-Item -ItemType Directory -Path "C:\Temp" -Force -ErrorAction SilentlyContinue | Out-Null + # + # # Run with timeout (using job for timeout capability) + # $job = Start-Job -ScriptBlock { + # param($path, $testType, $techStack, $logPath) + # & $path --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append + # } -ArgumentList ".\win\run.ps1", $testType, $techStack, $logPath + # + # # Wait for job with 600 second timeout + # $timeout = New-TimeSpan -Seconds 600 + # $completed = Wait-Job -Job $job -Timeout 600 + # + # if ($completed) { + # $result = Receive-Job -Job $job + # if ($job.State -eq "Completed") { + # Write-Host "✅ .\win\run.ps1 --silent $testType $techStack completed successfully" + # } else { + # Write-Host "⚠️ .\win\run.ps1 --silent $testType $techStack exited with state: $($job.State)" + # if (Test-Path $logPath) { + # Write-Host "Log output (last 20 lines):" + # Get-Content -Path $logPath -Tail 20 + # } + # } + # } else { + # Write-Host "⚠️ .\win\run.ps1 --silent $testType $techStack timed out after 600 seconds" + # Stop-Job -Job $job + # if (Test-Path $logPath) { + # Write-Host "Log output (last 20 lines):" + # Get-Content -Path $logPath -Tail 20 + # } + # } + # + # Remove-Job -Job $job -Force + # } + # + # Write-Host "✅ All integration tests completed" + # + # - name: Upload BrowserStack Logs as Artifacts + # if: always() + # uses: actions/upload-artifact@v4 + # with: + # name: browserstack-logs-windows + # path: | + # C:\Users\runneradmin\.browserstack\NOW\logs\ + # C:\Temp\run_test_*.log + # retention-days: 30 + # if-no-files-found: ignore + + test-linux: + name: Test mac/run.sh on Linux + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: BrowserStack + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Set up Bash + run: | + echo "Bash version:" + bash --version + + - name: Validate shell script syntax + run: | + echo "Validating mac/run.sh syntax..." + bash -n mac/run.sh + echo "✅ mac/run.sh syntax is valid" + + - name: Validate supporting scripts syntax + run: | + echo "Validating supporting scripts..." + bash -n mac/common-utils.sh + bash -n mac/logging-utils.sh + bash -n mac/env-setup-run.sh + bash -n mac/user-interaction.sh + bash -n mac/env-prequisite-checks.sh + echo "✅ All supporting scripts are valid" + + - name: Check if scripts are executable + run: | + chmod +x mac/run.sh + chmod +x mac/common-utils.sh + chmod +x mac/logging-utils.sh + chmod +x mac/env-setup-run.sh + chmod +x mac/user-interaction.sh + chmod +x mac/env-prequisite-checks.sh + echo "✅ All scripts are executable" + + - name: Install ShellCheck + run: | + echo "Installing ShellCheck..." + sudo apt-get update + sudo apt-get install -y shellcheck + shellcheck --version + + - name: Run ShellCheck on mac scripts + run: | + echo "Running ShellCheck on mac scripts..." + shellcheck -x mac/run.sh || true + shellcheck -x mac/common-utils.sh || true + shellcheck -x mac/logging-utils.sh || true + shellcheck -x mac/env-setup-run.sh || true + shellcheck -x mac/user-interaction.sh || true + shellcheck -x mac/env-prequisite-checks.sh || true + echo "✅ ShellCheck analysis complete" + + - name: Verify required dependencies + run: | + echo "Checking required dependencies..." + command -v bash && echo "✅ bash found" + command -v curl && echo "✅ curl found" + command -v git && echo "✅ git found" + command -v bc && echo "✅ bc found" + echo "All required dependencies are available" + + - name: Test script sourcing (dry run) + run: | + set -e + echo "Testing script sourcing..." + bash -c "source mac/common-utils.sh && echo '✅ common-utils.sh sourced successfully'" + echo "✅ Script sourcing successful" + + - name: Integration Test - Silent Mode Execution + if: success() + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + TURL: https://bstackdemo.com + run: | + echo "Running integration tests in silent mode..." + + # Set default values if secrets are not provided + BROWSERSTACK_USERNAME="${BROWSERSTACK_USERNAME:-test_user}" + BROWSERSTACK_ACCESS_KEY="${BROWSERSTACK_ACCESS_KEY:-test_key}" + + export BROWSERSTACK_USERNAME + export BROWSERSTACK_ACCESS_KEY + export TURL + + # Test configurations + test_configs=( + "web java" + "app java" + "web python" + "app python" + "web nodejs" + "app nodejs" + ) + + for config in "${test_configs[@]}"; do + read -r test_type tech_stack <<< "$config" + echo "================================" + echo "Testing: mac/run.sh --silent $test_type $tech_stack" + echo "================================" + + # Run with timeout and capture exit code (using timeout command on Linux) + timeout 600 bash mac/run.sh --silent "$test_type" "$tech_stack" >> "/tmp/run_test_${test_type}_${tech_stack}.log" 2>&1 & + job_pid=$! + wait "$job_pid" || exit_code=$? + + if [ -z "$exit_code" ] || [ "$exit_code" -eq 0 ] || [ "$exit_code" -eq 124 ]; then + echo "✅ mac/run.sh --silent $test_type $tech_stack completed (exit code: ${exit_code:-0})" + else + echo "⚠️ mac/run.sh --silent $test_type $tech_stack exited with code: $exit_code" + if [ -f "/tmp/run_test_${test_type}_${tech_stack}.log" ]; then + echo "Log output (last 20 lines):" + tail -n 20 "/tmp/run_test_${test_type}_${tech_stack}.log" + fi + fi + unset exit_code + done + + echo "✅ All integration tests completed" + + - name: Sync BrowserStack logs to workspace + if: always() + run: | + mkdir -p ${{ github.workspace }}/bs-logs + if [ -d ~/.browserstack/NOW/logs ]; then + cp -R ~/.browserstack/NOW/logs/* ${{ github.workspace }}/bs-logs/ || true + else + echo "No logs found in ~/.browserstack/NOW/logs" + fi + + - name: Upload BrowserStack Logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: browserstack-logs-linux + path: | + ${{ github.workspace }}/bs-logs + /tmp/run_test_*.log + retention-days: 30 + if-no-files-found: ignore + + test-summary: + name: Test Summary + runs-on: ubuntu-latest + needs: [test-mac, test-linux] + if: always() + steps: + - name: Check test results + run: | + echo "=== Test Results Summary ===" + echo "macOS Tests: ${{ needs.test-mac.result }}" + echo "Linux Tests: ${{ needs.test-linux.result }}" + + if [ "${{ needs.test-mac.result }}" = "failure" ] || [ "${{ needs.test-linux.result }}" = "failure" ]; then + echo "❌ Some tests failed" + exit 1 + fi + echo "✅ All tests passed!" + + - name: Notify success + if: success() + run: | + echo "✅ All script validations passed successfully!" + echo "- mac/run.sh and supporting scripts validated on macOS and Linux" + echo "- win/run.ps1 and supporting scripts validated on Windows (temporarily disabled)" diff --git a/mac/common-utils.sh b/mac/common-utils.sh new file mode 100644 index 0000000..dae53f9 --- /dev/null +++ b/mac/common-utils.sh @@ -0,0 +1,395 @@ +#!/bin/bash + +# shellcheck source=/dev/null +source "$(dirname "$0")/device-machine-allocation.sh" + +# # ===== Global Variables ===== +WORKSPACE_DIR="$HOME/.browserstack" +PROJECT_FOLDER="NOW" + +# URL handling +DEFAULT_TEST_URL="https://bstackdemo.com" + +# ===== Log files (per-run) ===== +LOG_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/logs" +NOW_RUN_LOG_FILE="" + +# ===== Global Variables ===== +CX_TEST_URL="$DEFAULT_TEST_URL" + +WEB_PLAN_FETCHED=false +MOBILE_PLAN_FETCHED=false +TEAM_PARALLELS_MAX_ALLOWED_WEB=0 +TEAM_PARALLELS_MAX_ALLOWED_MOBILE=0 + +# App specific globals +APP_PLATFORM="" # ios | android | all + + +# ===== Logging Functions ===== +log_msg_to() { + local message="$1" + local dest_file=$NOW_RUN_LOG_FILE + local ts + ts="$(date +"%Y-%m-%d %H:%M:%S")" + local line="[$ts] $message" + + # print to console + if [[ "$RUN_MODE" == *"--debug"* ]]; then + echo "$line" + fi + + # write to dest file if provided + if [ -n "$dest_file" ]; then + mkdir -p "$(dirname "$dest_file")" + echo "$line" >> "$NOW_RUN_LOG_FILE" + fi +} + +# Spinner function for long-running processes +show_spinner() { + local pid=$1 + # shellcheck disable=SC1003 + local spin='|/-\' + local i=0 + local ts + ts="$(date +"%Y-%m-%d %H:%M:%S")" + while kill -0 "$pid" 2>/dev/null; do + i=$(( (i+1) %4 )) + printf "\r⏳ Processing... %s" "${spin:$i:1}" + sleep 0.1 + done + echo "" + log_info "Run Test command completed." + sleep 5 + #log_msg_to "✅ Done!" +} + +# ===== Workspace Management ===== +setup_workspace() { + log_section "⚙️ Environment & Credentials" + local full_path="$WORKSPACE_DIR/$PROJECT_FOLDER" + if [ ! -d "$full_path" ]; then + mkdir -p "$full_path" + log_info "Created onboarding workspace: $full_path" + else + log_success "Onboarding workspace found at: $full_path" + fi +} + + +# ===== App Upload Management ===== +handle_app_upload() { + local app_platform="" + if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + upload_sample_app + app_platform="android" + export APP_PLATFORM="$app_platform" + log_msg_to "Exported APP_PLATFORM=$APP_PLATFORM" + else + local choice + choice=$(osascript -e ' + display dialog "How would you like to select your app?" ¬ + with title "BrowserStack App Upload" ¬ + with icon note ¬ + buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬ + default button "Upload my App (.apk/.ipa)" + ' 2>/dev/null) + + if [[ "$choice" == *"Use Sample App"* ]]; then + upload_sample_app + app_platform="android" + export APP_PLATFORM="$app_platform" + log_msg_to "Exported APP_PLATFORM=$APP_PLATFORM" + elif [[ "$choice" == *"Upload my App"* ]]; then + upload_custom_app + else + return 1 + fi + fi +} + +upload_sample_app() { + log_msg_to "⬆️ Uploading sample app to BrowserStack..." + local upload_response + upload_response=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "url=https://www.browserstack.com/app-automate/sample-apps/android/WikipediaSample.apk") + + app_url=$(echo "$upload_response" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) + export BROWSERSTACK_APP=$app_url + log_msg_to "Exported BROWSERSTACK_APP=$BROWSERSTACK_APP" + + if [ -z "$app_url" ]; then + log_msg_to "❌ Upload failed. Response: $upload_response" + return 1 + fi + + log_msg_to "✅ App uploaded successfully: $app_url" + return 0 +} + +upload_custom_app() { + local app_platform="" + local file_path + file_path=$(osascript -e 'choose file with prompt "Select your .apk or .ipa file:" of type {"apk", "ipa"}' 2>/dev/null) + + if [ -z "$file_path" ]; then + log_msg_to "❌ No file selected" + return 1 + fi + + # Determine platform from file extension + if [[ "$file_path" == *.ipa ]]; then + app_platform="ios" + elif [[ "$file_path" == *.apk ]]; then + app_platform="android" + else + log_msg_to "❌ Invalid file type. Must be .apk or .ipa" + return 1 + fi + + log_msg_to "⬆️ Uploading app to BrowserStack..." + local upload_response + upload_response=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$file_path") + + local app_url + app_url=$(echo "$upload_response" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) + if [ -z "$app_url" ]; then + log_msg_to "❌ Failed to upload app" + return 1 + fi + + export BROWSERSTACK_APP=$app_url + log_msg_to "✅ App uploaded successfully" + log_msg_to "Exported BROWSERSTACK_APP=$BROWSERSTACK_APP" + + export APP_PLATFORM="$app_platform" + log_msg_to "Exported APP_PLATFORM=$APP_PLATFORM" + return 0 +} + +# ===== Dynamic config generators ===== +generate_web_platforms() { + local max_total_parallels=$1 + local platformsListContentFormat=$2 + local platform="web" + local platformsList="" + export NOW_PLATFORM="$platform" + platformsList=$(pick_terminal_devices "$NOW_PLATFORM" "$max_total_parallels" "$platformsListContentFormat") + echo "$platformsList" +} + +generate_mobile_platforms() { + local max_total_parallels=$1 + local platformsListContentFormat=$2 + local app_platform="$APP_PLATFORM" + local platformsList="" + platformsList=$(pick_terminal_devices "$app_platform" "$max_total_parallels", "$platformsListContentFormat") + echo "$platformsList" +} + + +# ===== Fetch plan details (writes to GLOBAL) ===== +fetch_plan_details() { + local test_type=$1 + + log_section "☁️ Account & Plan Details" + log_info "Fetching BrowserStack plan for $test_type" + local web_unauthorized=false + local mobile_unauthorized=false + + if [[ "$test_type" == "web" || "$test_type" == "both" ]]; then + RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api.browserstack.com/automate/plan.json) + HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1) + RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d') + if [ "$HTTP_CODE_WEB" == "200" ]; then + WEB_PLAN_FETCHED=true + TEAM_PARALLELS_MAX_ALLOWED_WEB=$(echo "$RESPONSE_WEB_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') + export TEAM_PARALLELS_MAX_ALLOWED_WEB="$TEAM_PARALLELS_MAX_ALLOWED_WEB" + log_msg_to "✅ Web Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_WEB" + else + log_msg_to "❌ Web Testing Plan fetch failed ($HTTP_CODE_WEB)" + [ "$HTTP_CODE_WEB" == "401" ] && web_unauthorized=true + fi + fi + + if [[ "$test_type" == "app" || "$test_type" == "both" ]]; then + RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json) + HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1) + RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d') + if [ "$HTTP_CODE_MOBILE" == "200" ]; then + MOBILE_PLAN_FETCHED=true + TEAM_PARALLELS_MAX_ALLOWED_MOBILE=$(echo "$RESPONSE_MOBILE_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') + export TEAM_PARALLELS_MAX_ALLOWED_MOBILE="$TEAM_PARALLELS_MAX_ALLOWED_MOBILE" + log_msg_to "✅ Mobile App Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" + else + log_msg_to "❌ Mobile App Testing Plan fetch failed ($HTTP_CODE_MOBILE)" + [ "$HTTP_CODE_MOBILE" == "401" ] && mobile_unauthorized=true + fi + fi + + log_info "Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" + + if [[ "$test_type" == "web" && "$web_unauthorized" == true ]] || \ + [[ "$test_type" == "app" && "$mobile_unauthorized" == true ]] || \ + [[ "$test_type" == "both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then + log_msg_to "❌ Unauthorized to fetch required plan(s). Exiting." + exit 1 + fi +} + +# Function to check if IP is private +is_private_ip() { + case $1 in + 10.* | 192.168.* | 172.16.* | 172.17.* | 172.18.* | 172.19.* | \ + 172.20.* | 172.21.* | 172.22.* | 172.23.* | 172.24.* | 172.25.* | \ + 172.26.* | 172.27.* | 172.28.* | 172.29.* | 172.30.* | 172.31.* | "") + return 0 ;; # Private + *) + return 1 ;; # Public + esac +} + +is_domain_private() { + domain=${CX_TEST_URL#*://} # remove protocol + domain=${domain%%/*} # remove everything after first "/" + log_msg_to "Website domain: $domain" + export NOW_WEB_DOMAIN="$CX_TEST_URL" + export CX_TEST_URL="$CX_TEST_URL" + + # Resolve domain using Cloudflare DNS + IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1) + + # Determine if domain is private + if is_private_ip "$IP_ADDRESS"; then + is_cx_domain_private=0 + else + is_cx_domain_private=-1 + fi + + log_msg_to "Resolved IPs: $IP_ADDRESS" + + return $is_cx_domain_private +} + + +identify_run_status_java() { + local log_file=$1 + local line="" + # Extract the test summary line + line=$(grep -m 2 -E "[INFO|ERROR].*Tests run" < "$log_file") + # If not found, fail + if [[ -z "$line" ]]; then + log_warn "❌ No test summary line found." + return 1 + fi + + # Extract numbers using regex + tests_run=$(echo "$line" | grep -m 1 -oE "Tests run: [0-9]+" | awk '{print $3}') + failures=$(echo "$line" | grep -m 1 -oE "Failures: [0-9]+" | awk '{print $2}') + errors=$(echo "$line" | grep -m 1 -oE "Errors: [0-9]+" | awk '{print $2}') + skipped=$(echo "$line" | grep -m 1 -oE "Skipped: [0-9]+" | awk '{print $2}') + + # Calculate passed tests + passed=$(( tests_run-(failures+errors+skipped) )) + + # Check condition + if (( passed > 0 )); then + log_success "Success: $passed test(s) passed." + return 0 + else + log_error "Error: No tests passed (Tests run: $tests_run, Failures: $failures, Errors: $errors, Skipped: $skipped)" + return 1 + fi +} + + +identify_run_status_nodejs() { + + local log_file=$1 + log_info "Identifying run status" + local line="" + line=$(grep -m 1 -E "Spec Files:.*passed.*total" < "$log_file") + # If not found, fail + if [[ -z "$line" ]]; then + log_warn "❌ No test summary line found." + return 1 + fi + + # Extract numbers using regex + passed=$(echo "$line" | grep -oE '[0-9]+ passed' | awk '{print $1}') + # Check condition + if (( passed > 0 )); then + log_success "Success: $passed test(s) passed" + return 0 + else + log_error "❌ Error: No tests passed" + return 1 + fi +} + + +identify_run_status_python() { + + local log_file=$1 + log_info "Identifying run status" + + # Extract numbers and sum them + passed_sum=$(grep -oE '[0-9]+ passed' "$log_file" | awk '{sum += $1} END {print sum+0}') + + echo "✅ Total Passed: $passed_sum" + + # If not found, fail + if [[ -z "$passed_sum" ]]; then + log_warn "❌ No test summary line found." + return 1 + fi + + # Check condition + if (( passed_sum > 0 )); then + log_success "Success: $passed_sum test(s) completed" + return 0 + else + log_error "❌ Error: No tests completed" + return 1 + fi +} + +clear_old_logs() { + mkdir -p "$LOG_DIR" + : > "$NOW_RUN_LOG_FILE" + + log_success "Logs cleared and fresh run initiated." +} + + +detect_os() { + local unameOut="" + unameOut="$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]')" + local response="" + case "$unameOut" in + linux*) + # Detect WSL vs normal Linux + if grep -qi "microsoft" /proc/version 2>/dev/null; then + response="wsl" + else + response="linux" + fi + ;; + darwin*) + response="macos" + ;; + msys*|mingw*|cygwin*) + response="windows" + ;; + *) + response="unknown" + ;; + esac + + export NOW_OS=$response +} + diff --git a/mac/detect-os.sh b/mac/detect-os.sh new file mode 100644 index 0000000..30ccd34 --- /dev/null +++ b/mac/detect-os.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +detect_os() { + local unameOut="" + unameOut="$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]')" + local response="" + case "$unameOut" in + linux*) + # Detect WSL vs normal Linux + if grep -qi "microsoft" /proc/version 2>/dev/null; then + response="wsl" + else + response="linux" + fi + ;; + darwin*) + response="macos" + ;; + msys*|mingw*|cygwin*) + response="windows" + ;; + *) + response="unknown" + ;; + esac + echo "OS is: $response" + export NOW_OS=$response +} + +detect_os + diff --git a/mac/device-machine-allocation.sh b/mac/device-machine-allocation.sh new file mode 100644 index 0000000..fbbe280 --- /dev/null +++ b/mac/device-machine-allocation.sh @@ -0,0 +1,169 @@ +#!/bin/bash + +# --------------------------- +# Usage: ./pick_devices.sh +# Example: ./pick_devices.sh android 3 +# --------------------------- + + +# Define array of devices +MOBILE_ALL=( + # Tier 1 + "ios|iPhone 1[234567]*" + "android|Samsung Galaxy S*" + + # Tier 2 + "ios|iPad Air*" + "android|Samsung Galaxy Tab*" + "android|Samsung Galaxy M*" + "android|Google Pixel [56789]*" + "android|Vivo Y*" + "android|Oppo*" + + # Tier 4 + "ios|iPad Pro*" + "android|Samsung Galaxy A*" + "android|Google Pixel 10*" + "android|OnePlus *" + "android|Vivo V*" + "android|Xiaomi *" + "android|Huawei *" +) + +WEB_ALL=( + "Windows|Chrome" + "Windows|Firefox" + "Windows|Edge" + "Windows|Chrome" + "Windows|Chrome" + "OS X|Chrome" + "OS X|Safari" + "OS X|Chrome" + "OS X|Safari" + "OS X|Firefox" + "OS X|Safari" + # Tier 1 + "ios|iPhone 1[234567]*" + "android|Samsung Galaxy S*" + + # Tier 2 + "ios|iPad Air*" + "android|Samsung Galaxy Tab*" + "android|Samsung Galaxy M*" + "android|Google Pixel [56789]*" + "android|Vivo Y*" + "android|Oppo*" + + # Tier 4 + "ios|iPhone SE*" + "ios|iPad Pro*" + "android|Samsung Galaxy A*" + "android|Google Pixel 10*" + "android|OnePlus *" + "android|Vivo V*" + "android|Xiaomi *" + "android|Huawei *" +) + + + +pick_terminal_devices() { + local platformName="$1" + local count=$2 + count="${count%,}" # remove trailing comma if present + local platformsListContentFormat="$3" + + # --------------------------- + # Check for valid input + # --------------------------- + if [[ -z "$platformName" || -z "$count" ]]; then + log_msg_to "Platform name for parallel count is invalid: $0 $platformName $count" + return 1 + fi + + # Validate count is a number + if ! [[ "$count" =~ ^[0-9]+$ ]]; then + log_msg_to "Error: count must be a number." + return 1 + fi + + # --------------------------- + # Filter and store matching entries + # --------------------------- + matching_devices=() + + if [[ "$platformName" == "android" || "$platformName" == "ios" ]]; then + for entry in "${MOBILE_ALL[@]}"; do + prefix="${entry%%|*}" # text before '|' + if [[ "$prefix" == "$platformName" ]]; then + matching_devices+=("$entry") + fi + done + else + for entry in "${WEB_ALL[@]}"; do + matching_devices+=("$entry") + done + fi + + # --------------------------- + # Loop as many times as 'count' + # --------------------------- + local yaml="" + local json="[" + + for ((i = 1; i <= count; i++)); do + index=$(( (i - 1) % ${#matching_devices[@]} )) + entry="${matching_devices[$index]}" + suffixEntry="${entry#*|}" + prefixEntry="${entry%%|*}" + bVersionLiteral="" + mod=$(( i % 4 )) + + if [ $((i % 4)) -ne 0 ]; then + bVersionLiteral="-$mod" + else + bVersionLiteral="" + fi + bVersion="latest$bVersionLiteral" + if [[ "$platformsListContentFormat" == "yaml" ]]; then + if [[ "$prefixEntry" == "android" || "$prefixEntry" == "ios" ]]; then + yaml+=" - platformName: $prefixEntry + deviceName: $suffixEntry +" + else + yaml+=" - osVersion: $prefixEntry + browserName: $suffixEntry + browserVersion: $bVersion +" + fi + + # Add comma-like separator logic here only if needed + if [[ $i -lt $count ]]; then + yaml+=$'\n' + fi + + elif [[ "$platformsListContentFormat" == "json" ]]; then + # JSON mode + if [[ "$prefixEntry" == "android" || "$prefixEntry" == "ios" ]]; then + json+=$'{"platformName": "'"$prefixEntry"'","bstack:options":{"deviceName": "'"$suffixEntry"'"}},' + else + json+=$'{"bstack:options":{ "os": "'"$prefixEntry"'"},"browserName": "'"$suffixEntry"'","browserVersion": "'"$bVersion"'"},' + fi + + # Stop if max reached + if [[ -n "$max_total" && $i -ge $max_total ]]; then + break + fi + fi + done + + # Close JSON array + json="${json%,}]" + + # Output based on requested format + if [[ "$platformsListContentFormat" == "yaml" ]]; then + echo "$yaml" + else + echo "$json" + fi +} \ No newline at end of file diff --git a/mac/env-prequisite-checks.sh b/mac/env-prequisite-checks.sh new file mode 100755 index 0000000..51aa5c2 --- /dev/null +++ b/mac/env-prequisite-checks.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +# URL to test +PROXY_TEST_URL="https://www.browserstack.com/automate/browsers.json" + +# Detect proxy from env (case insensitive) +PROXY="${http_proxy:-${HTTP_PROXY:-${https_proxy:-${HTTPS_PROXY}}}}" + +# Reset output variables +export PROXY_HOST="" +export PROXY_PORT="" + +# Function: parse proxy url to host + port +parse_proxy() { + p="$1" + # strip protocol e.g. http://, https:// + p="${p#http://}" + p="${p#https://}" + # strip credentials if any user:pass@ + p="${p#*@}" + + # extract host and port + export PROXY_HOST="${p%%:*}" + export PROXY_PORT="${p##*:}" +} + +set_proxy_in_env() { + log_section "🌐 Network & Proxy Validation" + base64_encoded_creds=$(printf "%s" "$BROWSERSTACK_USERNAME":"$BROWSERSTACK_ACCESS_KEY" | base64 | tr -d '\n') + + + # If no proxy configured, exit early + if [ -z "$PROXY" ]; then + log_warn "No proxy found. Using direct connection." + export PROXY_HOST="" + export PROXY_PORT="" + return 0 2>/dev/null + fi + + log_msg_to "Proxy detected: $PROXY" + parse_proxy "$PROXY" + + log_msg_to "Testing reachability via proxy..." + + + STATUS_CODE=$(curl -sS -o /dev/null -H "Authorization: Basic ${base64_encoded_creds}" -w "%{http_code}" --proxy "$PROXY" "$PROXY_TEST_URL" 2>/dev/null) + + if [ "${STATUS_CODE#2}" != "$STATUS_CODE" ]; then + log_msg_to "✅ Reachable. HTTP $STATUS_CODE" + log_msg_to "Exporting PROXY_HOST=$PROXY_HOST" + log_msg_to "Exporting PROXY_PORT=$PROXY_PORT" + export PROXY_HOST + export PROXY_PORT + log_success "Connected to BrowserStack from proxy: $PROXY_HOST:$PROXY_PORT" + else + log_warn "⚠️ Could not connect to BrowserStack using proxy. Using direct connection." + log_msg_to "❌ Not reachable (HTTP $STATUS_CODE). Clearing variables." + export PROXY_HOST="" + export PROXY_PORT="" + fi +} + + +# ===== Tech Stack Validation Functions ===== +check_java_installation() { + log_msg_to "🔍 Checking if 'java' command exists..." + if ! command -v java >/dev/null 2>&1; then + log_msg_to "❌ Java command not found in PATH." + return 1 + fi + + log_msg_to "🔍 Checking if Java runs correctly..." + if ! JAVA_VERSION_OUTPUT=$(java -version 2>&1); then + log_msg_to "❌ Java exists but failed to run." + return 1 + fi + + log_success "Java installed and functional\n$JAVA_VERSION_OUTPUT" + #log_msg_to "$JAVA_VERSION_OUTPUT" | while read -r l; do log_msg_to " $l" ; done + return 0 +} + +check_python_installation() { + log_msg_to "🔍 Checking if 'python3' command exists..." + if ! command -v python3 >/dev/null 2>&1; then + log_msg_to "❌ Python3 command not found in PATH." + return 1 + fi + + log_msg_to "🔍 Checking if Python3 runs correctly..." + if ! PYTHON_VERSION_OUTPUT=$(python3 --version 2>&1); then + log_msg_to "❌ Python3 exists but failed to run." + return 1 + fi + + log_success "Python3 default installation: $PYTHON_VERSION_OUTPUT" + return 0 +} + +check_nodejs_installation() { + log_msg_to "🔍 Checking if 'node' command exists..." + if ! command -v node >/dev/null 2>&1; then + log_msg_to "❌ Node.js command not found in PATH." + return 1 + fi + + log_msg_to "🔍 Checking if 'npm' command exists..." + if ! command -v npm >/dev/null 2>&1; then + log_msg_to "❌ npm command not found in PATH." + return 1 + fi + + log_msg_to "🔍 Checking if Node.js runs correctly..." + if ! NODE_VERSION_OUTPUT=$(node -v 2>&1); then + log_msg_to "❌ Node.js exists but failed to run." + return 1 + fi + + log_msg_to "🔍 Checking if npm runs correctly..." + if ! NPM_VERSION_OUTPUT=$(npm -v 2>&1); then + log_msg_to "❌ npm exists but failed to run." + return 1 + fi + + log_success "Node.js installed: $NODE_VERSION_OUTPUT" + log_success "npm installed: $NPM_VERSION_OUTPUT" + return 0 +} + +validate_tech_stack_installed() { + local tech_stack=$1 + + log_section "🧩 System Prerequisites Check" + log_info "Checking prerequisites for $tech_stack" + + case "$tech_stack" in + java) + check_java_installation + ;; + python) + check_python_installation + ;; + nodejs) + check_nodejs_installation + ;; + *) + log_msg_to "❌ Unknown tech stack selected: $tech_stack" + return 1 + ;; + esac + + log_msg_to "✅ Prerequisites validated for $tech_stack" + return 0 +} + + + diff --git a/mac/env-setup-run.sh b/mac/env-setup-run.sh new file mode 100644 index 0000000..34f7915 --- /dev/null +++ b/mac/env-setup-run.sh @@ -0,0 +1,538 @@ +#!/usr/bin/env bash +# shellcheck shell=bash + +setup_environment() { + local setup_type=$1 + local tech_stack=$2 + local max_parallels + + log_section "📦 Project Setup" + + # Set variables based on setup type + if [ "$setup_type" = "web" ]; then + log_msg_to "Team max parallels for web: $TEAM_PARALLELS_MAX_ALLOWED_WEB" "$NOW_RUN_LOG_FILE" + max_parallels=$TEAM_PARALLELS_MAX_ALLOWED_WEB + else + log_msg_to "Team max parallels for mobile: $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "$NOW_RUN_LOG_FILE" + max_parallels=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE + fi + + log_msg_to "Starting ${setup_type} setup for " "$tech_stack" "$NOW_RUN_LOG_FILE" + + local local_flag=false + + # Calculate parallels + local total_parallels + total_parallels=$(echo "$max_parallels" | bc | cut -d'.' -f1) + [ -z "$total_parallels" ] && total_parallels=1 + local parallels_per_platform=$total_parallels + + log_msg_to "[${setup_type} Setup]" "$NOW_RUN_LOG_FILE" + log_msg_to "Total parallels allocated: $total_parallels" "$NOW_RUN_LOG_FILE" + + + case "$tech_stack" in + java) + "setup_${setup_type}_java" "$local_flag" "$parallels_per_platform" "$NOW_RUN_LOG_FILE" + log_section "✅ Results" + identify_run_status_java "$NOW_RUN_LOG_FILE" + check_return_value $? "$NOW_RUN_LOG_FILE" "${setup_type} setup succeeded." "❌ ${setup_type} setup failed. Check $log_file for details" + ;; + python) + "setup_${setup_type}_python" "$local_flag" "$parallels_per_platform" "$NOW_RUN_LOG_FILE" + log_section "✅ Results" + identify_run_status_python "$NOW_RUN_LOG_FILE" + check_return_value $? "$NOW_RUN_LOG_FILE" "${setup_type} setup succeeded." "❌ ${setup_type} setup failed. Check $log_file for details" + ;; + nodejs) + "setup_${setup_type}_nodejs" "$local_flag" "$parallels_per_platform" "$NOW_RUN_LOG_FILE" + log_section "✅ Results" + identify_run_status_nodejs "$NOW_RUN_LOG_FILE" + check_return_value $? "$NOW_RUN_LOG_FILE" "${setup_type} setup succeeded." "❌ ${setup_type} setup failed. Check $log_file for details" + ;; + *) + log_warn "Unknown TECH_STACK: $tech_stack" "$NOW_RUN_LOG_FILE" + return 1 + ;; + esac +} + +setup_web_java() { + local local_flag=$1 + local parallels=$2 + + REPO="now-testng-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + + clone_repository "$REPO" "$TARGET_DIR" + + cd "$TARGET_DIR"|| return 1 + + log_info "Target website: $CX_TEST_URL" + + if is_domain_private; then + local_flag=true + fi + + report_bstack_local_status "$local_flag" + + # === 5️⃣ YAML Setup === + log_msg_to "🧩 Generating YAML config (bstack.yml)" + + + # YAML config path + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_web_platforms "$TEAM_PARALLELS_MAX_ALLOWED_WEB", "yaml") + + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + log_success "Dependencies installed" + + + log_section "Validate Environment Variables" + log_info "BrowserStack Username: $BROWSERSTACK_USERNAME" + log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME" + log_info "Web Application Endpoint: $CX_TEST_URL" + log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL" + log_info "Parallels per platform: $BSTACK_PARALLELS" + log_info "Platforms: \n$BSTACK_PLATFORMS" + + + print_tests_running_log_section "mvn test -P sample-test" + log_msg_to "🚀 Running 'mvn test -P sample-test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" + mvn test -P sample-test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$!|| return 1 + + show_spinner "$cmd_pid" + wait "$cmd_pid" + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +setup_app_java() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="now-testng-appium-app-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + local app_url=$BROWSERSTACK_APP + log_msg_to "APP_PLATFORM: $APP_PLATFORM" >> "$NOW_RUN_LOG_FILE" 2>&1 + + clone_repository "$REPO" "$TARGET_DIR" + + if [[ "$APP_PLATFORM" == "all" || "$APP_PLATFORM" == "android" ]]; then + cd "android/testng-examples" || return 1 + else + cd ios/testng-examples || return 1 + fi + + + # YAML config path + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_mobile_platforms "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "yaml") + + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1; then + log_msg_to "❌ 'mvn clean' FAILED. See $log_file for details." + return 1 # Fail the function if clean fails + fi + log_success "Dependencies installed" + + log_section "Validate Environment Variables" + log_info "BrowserStack Username: $BROWSERSTACK_USERNAME" + log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME" + log_info "Native App Endpoint: $BROWSERSTACK_APP" + log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL" + log_info "Parallels per platform: $BSTACK_PARALLELS" + log_info "Platforms: \n$BSTACK_PLATFORMS" + + log_msg_to "🚀 Running 'mvn test -P sample-test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" + print_tests_running_log_section "mvn test -P sample-test" + mvn test -P sample-test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$!|| return 1 + + show_spinner "$cmd_pid" + wait "$cmd_pid" + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +setup_web_python() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="now-pytest-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + clone_repository "$REPO" "$TARGET_DIR" "" + + detect_setup_python_env + + pip3 install -r requirements.txt >> "$NOW_RUN_LOG_FILE" 2>&1 + log_success "Dependencies installed" + + # Update YAML at root level (browserstack.yml) + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_web_platforms "$TEAM_PARALLELS_MAX_ALLOWED_WEB" "yaml") + export BSTACK_PLATFORMS=$platform_yaml + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 & cmd_pid=$!|| return 1 + show_spinner "$cmd_pid" + wait "$cmd_pid" + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +setup_app_python() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="now-pytest-appium-app-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + clone_repository "$REPO" "$TARGET_DIR" + + detect_setup_python_env + + # Install dependencies + pip install -r requirements.txt >> "$NOW_RUN_LOG_FILE" 2>&1 + log_success "Dependencies installed" + + local app_url=$BROWSERSTACK_APP + local platform_yaml + + export BSTACK_PARALLELS=1 + export BROWSERSTACK_CONFIG_FILE="./android/browserstack.yml" + platform_yaml=$(generate_mobile_platforms "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "yaml") + + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + ) + + deactivate + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +setup_web_nodejs() { + local local_flag=$1 + local parallels=$2 + + REPO="now-webdriverio-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + + clone_repository "$REPO" "$TARGET_DIR" + + + # === 2️⃣ Install Dependencies === + log_msg_to "⚙️ Running 'npm install'" + log_info "Installing dependencies" + npm install >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + log_success "Dependencies installed" + + local caps_json="" + # === 4️⃣ Generate Capabilities JSON === + caps_json=$(generate_web_platforms "$parallels" "json") + export BSTACK_CAPS_JSON=$caps_json + export BSTACK_PARALLELS=$parallels + + if is_domain_private; then + local_flag=true + fi + + export BROWSERSTACK_LOCAL=$local_flag + export BROWSERSTACK_BUILD_NAME="now-$NOW_OS-web-nodejs-wdio" + export BROWSERSTACK_PROJECT_NAME="now-$NOW_OS-web" + + report_bstack_local_status "$local_flag" + + log_section "Validate Environment Variables" + log_info "BrowserStack Username: $BROWSERSTACK_USERNAME" + log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME" + log_info "Web Application Endpoint: $CX_TEST_URL" + log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL" + log_info "Parallels per platform: $BSTACK_PARALLELS" + log_info "Platforms: \n$BSTACK_CAPS_JSON" + + # === 8️⃣ Run Tests === + log_msg_to "🚀 Running 'npm run test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" + print_tests_running_log_section "npm run test" + npm run test >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 & + cmd_pid=$!|| return 1 + + show_spinner "$cmd_pid" + wait "$cmd_pid" + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +setup_app_nodejs() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + local caps_json="" + + log_msg_to "Starting Mobile NodeJS setup with parallels: $parallels" >> "$NOW_RUN_LOG_FILE" 2>&1 + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + REPO="now-webdriverio-appium-app-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + TEST_FOLDER="/test" + + clone_repository $REPO "$TARGET_DIR" "$TEST_FOLDER" + + # === 2️⃣ Install Dependencies === + log_info "Installing dependencies" + log_msg_to "⚙️ Running 'npm install'" + npm install >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + log_success "Dependencies installed" + + caps_json=$(generate_mobile_platforms "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "json") + + + export BSTACK_CAPS_JSON=$caps_json + + local app_url=$BROWSERSTACK_APP + + export BSTACK_PARALLELS=$parallels + export BROWSERSTACK_LOCAL=true + export BROWSERSTACK_APP=$app_url + export BROWSERSTACK_BUILD_NAME="now-$NOW_OS-app-nodejs-wdio" + export BROWSERSTACK_PROJECT_NAME="now-$NOW_OS-app" + + log_section "Validate Environment Variables" + log_info "BrowserStack Username: $BROWSERSTACK_USERNAME" + log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME" + log_info "Native App Endpoint: $BROWSERSTACK_APP" + log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL" + log_info "Parallels per platform: $BSTACK_PARALLELS" + log_info "Platforms: \n$BSTACK_CAPS_JSON" + + # === 8️⃣ Run Tests === + log_msg_to "🚀 Running 'npm run test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" + print_tests_running_log_section "npm run test" + npm run test >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 & + cmd_pid=$!|| return 1 + + show_spinner "$cmd_pid" + wait "$cmd_pid" + + # === 9️⃣ Wrap Up === + log_msg_to "✅ Mobile JS setup and test execution completed successfully." + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" || return 1 + return 0 +} + +clone_repository() { + local repo_git=$1 + local install_folder=$2 + local test_folder=$3 + local git_branch=$4 + + rm -rf "$install_folder" + log_msg_to "📦 Cloning repo $repo_git into $install_folder" + log_info "Cloning repository: $repo_git" + # git clone https://github.com/BrowserStackCE/"$repo_git".git "$install_folder" >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + if [ -z "$git_branch" ]; then + # git_branch is null or empty + git clone "https://github.com/BrowserStackCE/$repo_git.git" \ + "$install_folder" >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + else + # git_branch has a value + git clone -b "$git_branch" "https://github.com/BrowserStackCE/$repo_git.git" \ + "$install_folder" >> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + fi + log_msg_to "✅ Cloned repository: $repo_git into $install_folder" + cd "$install_folder/$test_folder" || return 1 +} + +# ===== Orchestration: decide what to run based on TEST_TYPE and plan fetch ===== +run_setup_wrapper() { + local test_type=$1 + local tech_stack=$2 + log_msg_to "Orchestration: TEST_TYPE=$test_type, WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED, MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED" + + case "$test_type" in + Web) + if [ "$WEB_PLAN_FETCHED" == true ]; then + run_setup "$test_type" "$tech_stack" + else + log_msg_to "⚠️ Skipping Web setup — Web plan not fetched" + fi + ;; + App) + if [ "$MOBILE_PLAN_FETCHED" == true ]; then + run_setup "$test_type" "$tech_stack" + else + log_msg_to "⚠️ Skipping Mobile setup — Mobile plan not fetched" + fi + ;; + *) + log_msg_to "❌ Invalid TEST_TYPE: $test_type" + exit 1 + ;; + esac +} + +check_return_value() { + local return_value=$1 + local log_file=$2 + local success_message=$3 + local failure_message=$4 + + if [ "$return_value" -eq 0 ]; then + log_success "$success_message" "$NOW_RUN_LOG_FILE" + exit 0 + else + log_error "$failure_message" "$NOW_RUN_LOG_FILE" + exit 1 + fi +} + + +report_bstack_local_status() { + if [ "$local_flag" = "true" ]; then + log_msg_to "✅ BrowserStack Local is ENABLED for this run." + log_success "Target website is behind firewall. BrowserStack Local enabled for this run." + else + log_msg_to "✅ BrowserStack Local is DISABLED for this run." + log_success "Target website is publicly resolvable. BrowserStack Local disabled for this run." + fi +} + +print_tests_running_log_section() { + log_section "🚀 Running Tests: $1" + log_info "Executing: Test run command. This could take a few minutes..." + log_info "You can monitor test progress here: 🔗 https://automation.browserstack.com/" +} + + +detect_setup_python_env() { + log_info "Detecting latest Python environment" + + latest_python=$( + { ls -1 /usr/local/bin/python3.[0-9]* /usr/bin/python3.[0-9]* 2>/dev/null || true; } \ + | grep -E 'python3\.[0-9]+$' \ + | sort -V \ + | tail -n 1 + ) + + if [[ -z "$latest_python" ]]; then + log_warn "No specific Python3.x version found. Falling back to system python3." + latest_python=$(command -v python3) + fi + + if [[ -z "$latest_python" ]]; then + log_error "Python3 not found on this system." + exit 1 + fi + + echo "🐍 Switching to: $latest_python" + log_info "Using Python interpreter: $latest_python" + + "$latest_python" -m venv .venv || { + log_error "Failed to create virtual environment." + exit 1 + } + + # shellcheck source=/dev/null + source .venv/bin/activate + log_success "Virtual environment created and activated." +} diff --git a/mac/logging-utils.sh b/mac/logging-utils.sh new file mode 100644 index 0000000..2358b31 --- /dev/null +++ b/mac/logging-utils.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +#set -e + +# ============================================== +# 🎨 COLOR & STYLE DEFINITIONS +# ============================================== +BOLD="\033[1m" +RESET="\033[0m" +GREEN="\033[32m" +YELLOW="\033[33m" +CYAN="\033[36m" +RED="\033[31m" +LIGHT_GRAY='\033[0;37m' + +# ============================================== +# 🪄 LOGGING HELPERS +# ============================================== +log_section() { + echo "" + echo -e "${BOLD}${CYAN}───────────────────────────────────────────────${RESET}" + echo -e "${BOLD}$1${RESET}" + echo -e "${BOLD}${CYAN}───────────────────────────────────────────────${RESET}" +} + +log_info() { echo -e "${LIGHT_GRAY}ℹ️ $1${RESET}"; } +log_success() { echo -e "${GREEN}✅ $1${RESET}"; } +log_warn() { echo -e "${YELLOW}⚠️ $1${RESET}"; } +log_error() { echo -e "${RED}❌ $1${RESET}"; } + diff --git a/mac/proxy-check.sh b/mac/proxy-check.sh deleted file mode 100755 index 4e6ba4b..0000000 --- a/mac/proxy-check.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env sh - -# URL to test -TEST_URL="https://www.browserstack.com/automate/browsers.json" - -# Detect proxy from env (case insensitive) -PROXY="${http_proxy:-${HTTP_PROXY:-${https_proxy:-${HTTPS_PROXY}}}}" - -# Reset output variables -export PROXY_HOST="" -export PROXY_PORT="" - -# Function: parse proxy url to host + port -parse_proxy() { - p="$1" - # strip protocol e.g. http://, https:// - p="${p#http://}" - p="${p#https://}" - # strip credentials if any user:pass@ - p="${p#*[@]}" - - # extract host and port - export PROXY_HOST="${p%%:*}" - export PROXY_PORT="${p##*:}" -} - -base64_encoded_creds=$(printf "%s" $BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY | base64 | tr -d '\n') - - -# If no proxy configured, exit early -if [ -z "$PROXY" ]; then - echo "No proxy found in environment. Clearing proxy host and port variables." - export PROXY_HOST="" - export PROXY_PORT="" - return 0 2>/dev/null || exit 0 -fi - -echo "Proxy detected: $PROXY" -parse_proxy "$PROXY" - -echo "Testing reachability via proxy..." - - -STATUS_CODE=$(curl -sS -o /dev/null -H "Authorization: Basic ${base64_encoded_creds}" -w "%{http_code}" --proxy "$PROXY" "$TEST_URL" 2>/dev/null) - -if [ "${STATUS_CODE#2}" != "$STATUS_CODE" ]; then - echo "✅ Reachable. HTTP $STATUS_CODE" - echo "Exporting PROXY_HOST=$PROXY_HOST" - echo "Exporting PROXY_PORT=$PROXY_PORT" - export PROXY_HOST - export PROXY_PORT -else - echo "❌ Not reachable (HTTP $STATUS_CODE). Clearing variables." - export PROXY_HOST="" - export PROXY_PORT="" -fi diff --git a/mac/run.sh b/mac/run.sh index 8142103..a6a9c9b 100755 --- a/mac/run.sh +++ b/mac/run.sh @@ -1,1253 +1,78 @@ #!/bin/bash set -o pipefail -# ===== Global Variables ===== -WORKSPACE_DIR="$HOME/.browserstack" -PROJECT_FOLDER="NOW" - -BROWSERSTACK_USERNAME="" -BROWSERSTACK_ACCESS_KEY="" -TEST_TYPE="" # Web / App / Both -TECH_STACK="" # Java / Python / JS - -PARALLEL_PERCENTAGE=1.00 - -WEB_PLAN_FETCHED=false -MOBILE_PLAN_FETCHED=false -TEAM_PARALLELS_MAX_ALLOWED_WEB=0 -TEAM_PARALLELS_MAX_ALLOWED_MOBILE=0 - -# URL handling -DEFAULT_TEST_URL="https://bstackdemo.com" -CX_TEST_URL="$DEFAULT_TEST_URL" - -# Global vars -APP_URL="" -APP_PLATFORM="" # ios | android | all - - - -# ===== Example Platform Templates (replace with your full lists if available) ===== -WEB_PLATFORM_TEMPLATES=( - "Windows|10|Chrome" - "Windows|10|Firefox" - "Windows|11|Edge" - "Windows|11|Chrome" - "Windows|8|Chrome" - "OS X|Monterey|Chrome" - "OS X|Ventura|Chrome" - "OS X|Catalina|Firefox" -) - -MOBILE_ALL=( - # Tier 1 - "ios|iPhone 15|17" - "ios|iPhone 15 Pro|17" - "ios|iPhone 16|18" - "android|Samsung Galaxy S25|15" - "android|Samsung Galaxy S24|14" - - # Tier 2 - "ios|iPhone 14 Pro|16" - "ios|iPhone 14|16" - "ios|iPad Air 13 2025|18" - "android|Samsung Galaxy S23|13" - "android|Samsung Galaxy S22|12" - "android|Samsung Galaxy S21|11" - "android|Samsung Galaxy Tab S10 Plus|15" - - # Tier 3 - "ios|iPhone 13 Pro Max|15" - "ios|iPhone 13|15" - "ios|iPhone 12 Pro|14" - "ios|iPhone 12 Pro|17" - "ios|iPhone 12|17" - "ios|iPhone 12|14" - "ios|iPhone 12 Pro Max|16" - "ios|iPhone 13 Pro|15" - "ios|iPhone 13 Mini|15" - "ios|iPhone 16 Pro|18" - "ios|iPad 9th|15" - "ios|iPad Pro 12.9 2020|14" - "ios|iPad Pro 12.9 2020|16" - "ios|iPad 8th|16" - "android|Samsung Galaxy S22 Ultra|12" - "android|Samsung Galaxy S21|12" - "android|Samsung Galaxy S21 Ultra|11" - "android|Samsung Galaxy S20|10" - "android|Samsung Galaxy M32|11" - "android|Samsung Galaxy S10|9" - "android|Samsung Galaxy Tab S8|12" - "android|Google Pixel 9|15" - "android|Google Pixel 6 Pro|13" - "android|Google Pixel 8|14" - "android|Google Pixel 7|13" - "android|Google Pixel 6|12" - "android|Vivo Y21|11" - "android|Vivo Y50|10" - "android|Oppo Reno 6|11" - - # Tier 4 - "ios|iPhone 15 Pro Max|17" - "ios|iPhone 15 Pro Max|26" - "ios|iPhone 15|26" - "ios|iPhone 15 Plus|17" - "ios|iPhone 14 Pro|26" - "ios|iPhone 14|18" - "ios|iPhone 14|26" - "ios|iPhone 13 Pro Max|18" - "ios|iPhone 13|16" - "ios|iPhone 13|17" - "ios|iPhone 13|18" - "ios|iPhone 12 Pro|18" - "ios|iPhone 14 Pro Max|16" - "ios|iPhone 14 Plus|16" - "ios|iPhone 11|13" - "ios|iPhone 8|11" - "ios|iPhone 7|10" - "ios|iPhone 17 Pro Max|26" - "ios|iPhone 17 Pro|26" - "ios|iPhone 17 Air|26" - "ios|iPhone 17|26" - "ios|iPhone 16e|18" - "ios|iPhone 16 Pro Max|18" - "ios|iPhone 16 Plus|18" - "ios|iPhone SE 2020|16" - "ios|iPhone SE 2022|15" - "ios|iPad Air 4|14" - "ios|iPad 9th|18" - "ios|iPad Air 5|26" - "ios|iPad Pro 11 2021|18" - "ios|iPad Pro 13 2024|17" - "ios|iPad Pro 12.9 2021|14" - "ios|iPad Pro 12.9 2021|17" - "ios|iPad Pro 11 2024|17" - "ios|iPad Air 6|17" - "ios|iPad Pro 12.9 2022|16" - "ios|iPad Pro 11 2022|16" - "ios|iPad 10th|16" - "ios|iPad Air 13 2025|26" - "ios|iPad Pro 11 2020|13" - "ios|iPad Pro 11 2020|16" - "ios|iPad 8th|14" - "ios|iPad Mini 2021|15" - "ios|iPad Pro 12.9 2018|12" - "ios|iPad 6th|11" - "android|Samsung Galaxy S23 Ultra|13" - "android|Samsung Galaxy S22 Plus|12" - "android|Samsung Galaxy S21 Plus|11" - "android|Samsung Galaxy S20 Ultra|10" - "android|Samsung Galaxy S25 Ultra|15" - "android|Samsung Galaxy S24 Ultra|14" - "android|Samsung Galaxy M52|11" - "android|Samsung Galaxy A52|11" - "android|Samsung Galaxy A51|10" - "android|Samsung Galaxy A11|10" - "android|Samsung Galaxy A10|9" - "android|Samsung Galaxy Tab A9 Plus|14" - "android|Samsung Galaxy Tab S9|13" - "android|Samsung Galaxy Tab S7|10" - "android|Samsung Galaxy Tab S7|11" - "android|Samsung Galaxy Tab S6|9" - "android|Google Pixel 9|16" - "android|Google Pixel 10 Pro XL|16" - "android|Google Pixel 10 Pro|16" - "android|Google Pixel 10|16" - "android|Google Pixel 9 Pro XL|15" - "android|Google Pixel 9 Pro|15" - "android|Google Pixel 6 Pro|12" - "android|Google Pixel 6 Pro|15" - "android|Google Pixel 8 Pro|14" - "android|Google Pixel 7 Pro|13" - "android|Google Pixel 5|11" - "android|OnePlus 13R|15" - "android|OnePlus 12R|14" - "android|OnePlus 11R|13" - "android|OnePlus 9|11" - "android|OnePlus 8|10" - "android|Motorola Moto G71 5G|11" - "android|Motorola Moto G9 Play|10" - "android|Vivo V21|11" - "android|Oppo A96|11" - "android|Oppo Reno 3 Pro|10" - "android|Xiaomi Redmi Note 11|11" - "android|Xiaomi Redmi Note 9|10" - "android|Huawei P30|9" -) - - -APP_URL="" -APP_PLATFORM="" # ios | android | all - - -# ===== Log files (runtime only; created on first write) ===== -# ===== Log files (per-run) ===== -LOG_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/logs" -GLOBAL="$LOG_DIR/global.log" -WEB_LOG_FILE="$LOG_DIR/web_run_result.log" -MOBILE_LOG_FILE="$LOG_DIR/mobile_run_result.log" - -# Ensure log directory exists -mkdir -p "$LOG_DIR" - -# Clear old logs to start fresh -: > "$GLOBAL_LOG_FILE" -: > "$WEB_LOG_FILE" -: > "$MOBILE_LOG_FILE" - - -# ===== Logging helper (runtime timestamped logging) ===== -# Usage: log_msg_to "message" "$DEST_FILE" (DEST_FILE optional; prints to console always) -log_msg_to() { - local message="$1" - local dest_file="$2" # optional - local ts - ts="$(date +"%Y-%m-%d %H:%M:%S")" - local line="[$ts] $message" - - # print to console - echo "$line" - - # write to dest file if provided - if [ -n "$dest_file" ]; then - mkdir -p "$(dirname "$dest_file")" - echo "$line" >> "$dest_file" - fi -} - -# Spinner function -show_spinner() { - local pid=$1 - local spin='|/-\' - local i=0 - local ts - ts="$(date +"%Y-%m-%d %H:%M:%S")" - while kill -0 "$pid" 2>/dev/null; do - i=$(( (i+1) %4 )) - printf "\r[$ts] ⏳ Processing... ${spin:$i:1}" - sleep 0.1 - done - log_msg_to "✅ Done!" -} - -# ===== Functions: baseline interactions ===== -setup_workspace() { - local full_path="$WORKSPACE_DIR/$PROJECT_FOLDER" - if [ ! -d "$full_path" ]; then - mkdir -p "$full_path" - log_msg_to "✅ Created Onboarding workspace: $full_path" "$GLOBAL_LOG_FILE" - else - log_msg_to "ℹ️ Onboarding Workspace already exists: $full_path" "$GLOBAL_LOG_FILE" - fi -} - -ask_browserstack_credentials() { - # Prompt username - BROWSERSTACK_USERNAME=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Username.\n\nNote: Locate it in your BrowserStack account profile page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ - -e 'text returned of result') - if [ -z "$BROWSERSTACK_USERNAME" ]; then - log_msg_to "❌ Username empty" "$GLOBAL_LOG_FILE" - exit 1 - fi - - # Prompt access key (hidden) - BROWSERSTACK_ACCESS_KEY=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Access Key.\n\nNote: Locate it in your BrowserStack account page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with hidden answer with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ - -e 'text returned of result') - if [ -z "$BROWSERSTACK_ACCESS_KEY" ]; then - log_msg_to "❌ Access Key empty" "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "✅ BrowserStack credentials captured (access key hidden)" "$GLOBAL_LOG_FILE" -} - - -ask_tech_stack() { - TECH_STACK=$(osascript -e 'Tell application "System Events" to display dialog "Select installed tech stack:" buttons {"Java", "Python", "NodeJS"} default button "Java" with title "Testing Framework Technology Stack"' \ - -e 'button returned of result') - log_msg_to "✅ Selected Tech Stack: $TECH_STACK" "$GLOBAL_LOG_FILE" -} - -validate_tech_stack_installed() { - log_msg_to "ℹ️ Checking prerequisites for $TECH_STACK" "$GLOBAL_LOG_FILE" - - case "$TECH_STACK" in - Java) - log_msg_to "🔍 Checking if 'java' command exists..." "$GLOBAL_LOG_FILE" - if ! command -v java >/dev/null 2>&1; then - log_msg_to "❌ Java command not found in PATH." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "🔍 Checking if Java runs correctly..." "$GLOBAL_LOG_FILE" - if ! JAVA_VERSION_OUTPUT=$(java -version 2>&1); then - log_msg_to "❌ Java exists but failed to run." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "✅ Java is installed. Version details:" "$GLOBAL_LOG_FILE" - echo "$JAVA_VERSION_OUTPUT" | while read -r l; do log_msg_to " $l" "$GLOBAL_LOG_FILE"; done - ;; - Python) - log_msg_to "🔍 Checking if 'python3' command exists..." "$GLOBAL_LOG_FILE" - if ! command -v python3 >/dev/null 2>&1; then - log_msg_to "❌ Python3 command not found in PATH." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "🔍 Checking if Python3 runs correctly..." "$GLOBAL_LOG_FILE" - if ! PYTHON_VERSION_OUTPUT=$(python3 --version 2>&1); then - log_msg_to "❌ Python3 exists but failed to run." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "✅ Python3 is installed: $PYTHON_VERSION_OUTPUT" "$GLOBAL_LOG_FILE" - ;; - NodeJS) - log_msg_to "🔍 Checking if 'node' command exists..." "$GLOBAL_LOG_FILE" - if ! command -v node >/dev/null 2>&1; then - log_msg_to "❌ Node.js command not found in PATH." "$GLOBAL_LOG_FILE" - exit 1 - fi - log_msg_to "🔍 Checking if 'npm' command exists..." "$GLOBAL_LOG_FILE" - if ! command -v npm >/dev/null 2>&1; then - log_msg_to "❌ npm command not found in PATH." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "🔍 Checking if Node.js runs correctly..." "$GLOBAL_LOG_FILE" - if ! NODE_VERSION_OUTPUT=$(node -v 2>&1); then - log_msg_to "❌ Node.js exists but failed to run." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "🔍 Checking if npm runs correctly..." "$GLOBAL_LOG_FILE" - if ! NPM_VERSION_OUTPUT=$(npm -v 2>&1); then - log_msg_to "❌ npm exists but failed to run." "$GLOBAL_LOG_FILE" - exit 1 - fi - - log_msg_to "✅ Node.js is installed: $NODE_VERSION_OUTPUT" "$GLOBAL_LOG_FILE" - log_msg_to "✅ npm is installed: $NPM_VERSION_OUTPUT" "$GLOBAL_LOG_FILE" - ;; - *) - log_msg_to "❌ Unknown tech stack selected: $TECH_STACK" "$GLOBAL_LOG_FILE" - exit 1 - ;; - esac - - log_msg_to "✅ Prerequisites validated for $TECH_STACK" "$GLOBAL_LOG_FILE" -} - -# ===== Ask user for test URL via UI prompt ===== -ask_user_for_test_url() { - CX_TEST_URL=$(osascript -e 'Tell application "System Events" to display dialog "Enter the URL you want to test with BrowserStack:\n(Leave blank for default: '"$DEFAULT_TEST_URL"')" default answer "" with title "Test URL Setup" buttons {"OK"} default button "OK"' \ - -e 'text returned of result') - - if [ -n "$CX_TEST_URL" ]; then - log_msg_to "🌐 Using custom test URL: $CX_TEST_URL" "$GLOBAL_LOG_FILE" - else - CX_TEST_URL="$DEFAULT_TEST_URL" - log_msg_to "⚠️ No URL entered. Falling back to default: $CX_TEST_URL" "$GLOBAL_LOG_FILE" - fi -} - -ask_and_upload_app() { - - CHOICE_RESPONSE=$(osascript -e ' - display dialog "How would you like to select your app?" ¬ - with title "BrowserStack App Upload" ¬ - with icon note ¬ - buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬ - default button "Upload my App (.apk/.ipa)" - ' 2>/dev/null) - - if [[ "$CHOICE_RESPONSE" == *"Use Sample App"* ]]; then - log_msg_to "⬆️ Uploading sample app to BrowserStack..." "$GLOBAL_LOG_FILE" - UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ - -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "url=https://www.browserstack.com/app-automate/sample-apps/android/WikipediaSample.apk") - - APP_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) - export BROWSERSTACK_APP=$APP_URL - log_msg_to "Exported BROWSERSTACK_APP=$BROWSERSTACK_APP" "$GLOBAL_LOG_FILE" - - if [ -z "$APP_URL" ]; then - log_msg_to "❌ Upload failed. Response: $UPLOAD_RESPONSE" "$GLOBAL_LOG_FILE" - return 1 - fi - - log_msg_to "✅ App uploaded successfully: $APP_URL" "$GLOBAL_LOG_FILE" - APP_PLATFORM="android" - return 0 - - elif [[ "$CHOICE_RESPONSE" == *"Upload my App"* ]]; then - APP_FILE_PATH=$(osascript -e 'POSIX path of (choose file with prompt "📱 Please select your .apk or .ipa app file")' 2>/dev/null) - - if [ -z "$APP_FILE_PATH" ]; then - log_msg_to "⚠️ File selection canceled. Aborting." "$GLOBAL_LOG_FILE" - return 1 - fi - - if [[ "$APP_FILE_PATH" == *.apk ]]; then - APP_PLATFORM="android" - elif [[ "$APP_FILE_PATH" == *.ipa ]]; then - APP_PLATFORM="ios" - else - log_msg_to "❌ Unsupported file type. Only .apk or .ipa allowed." "$GLOBAL_LOG_FILE" - return 1 - fi - - log_msg_to "⬆️ Uploading $APP_FILE_PATH to BrowserStack..." "$GLOBAL_LOG_FILE" - UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ - -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "file=@$APP_FILE_PATH") - - APP_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) - export BROWSERSTACK_APP=$APP_URL - - if [ -z "$APP_URL" ]; then - log_msg_to "❌ Upload failed. Response: $UPLOAD_RESPONSE" "$GLOBAL_LOG_FILE" - return 1 - fi - - log_msg_to "✅ App uploaded successfully: $APP_URL" "$GLOBAL_LOG_FILE" - return 0 - - else - log_msg_to "🚫 Operation canceled by user." "$GLOBAL_LOG_FILE" - return 1 - fi -} - -ask_test_type() { - TEST_TYPE=$(osascript -e 'Tell application "System Events" to display dialog "Select testing type:" buttons {"Web", "App", "Both"} default button "Web" with title "Testing Type"' \ - -e 'button returned of result') - log_msg_to "✅ Selected Testing Type: $TEST_TYPE" "$GLOBAL_LOG_FILE" - - case "$TEST_TYPE" in - "Web") - ask_user_for_test_url - ;; - "App") - ask_and_upload_app - ;; - "Both") - ask_user_for_test_url - ask_and_upload_app - ;; - esac -} -# ===== Dynamic config generators ===== -generate_web_platforms_yaml() { - local max_total_parallels=$1 - local max - max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - [ -z "$max" ] && max=0 - local yaml="" - local count=0 - - for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do - IFS="|" read -r os osVersion browserName <<< "$template" - for version in latest latest-1 latest-2; do - yaml+=" - os: $os - osVersion: $osVersion - browserName: $browserName - browserVersion: $version -" - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - echo "$yaml" - return - fi - done - done - - echo "$yaml" -} - -generate_mobile_platforms_yaml() { - local max_total_parallels=$1 - local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - - # fallback if bc result is empty or zero - if [ -z "$max" ] || [ "$max" -lt 1 ]; then - max=1 - fi - - local yaml="" - local count=0 - - for template in "${MOBILE_ALL[@]}"; do - IFS="|" read -r platformName deviceName platformVersion <<< "$template" - - if [ -n "$APP_PLATFORM" ]; then - if [[ "$APP_PLATFORM" == "ios" && "$platformName" != "ios" ]]; then - continue - fi - if [[ "$APP_PLATFORM" == "android" && "$platformName" != "android" ]]; then - continue - fi - if [[ "$APP_PLATFORM" == "all" && "$platformName" != "android" ]]; then - continue - fi - fi - - yaml+=" - platformName: $platformName - deviceName: $deviceName - platformVersion: '${platformVersion}.0' -" - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - echo "$yaml" - return - fi - done - - echo "$yaml" - log_msg_to "Generated mobile platforms YAML:" "$MOBILE_LOG_FILE" -} - - -generate_web_caps_json() { - local max_total_parallels=$1 - local max - max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - [ "$max" -lt 1 ] && max=1 # fallback to minimum 1 - - local json="" - local count=0 - - for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do - IFS="|" read -r os osVersion browserName <<< "$template" - for version in latest latest-1 latest-2; do - json+="{ - \"browserName\": \"$browserName\", - \"browserVersion\": \"$version\", - \"bstack:options\": { - \"os\": \"$os\", - \"osVersion\": \"$osVersion\" - } - }," - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - json="${json%,}" # strip trailing comma - echo "$json" - return - fi - done - done - - # Fallback in case not enough combinations - json="${json%,}" - echo "$json" -} - -generate_mobile_caps_json() { - local max_total=$1 - local output_file="$WORKSPACE_DIR/$PROJECT_FOLDER/usage_file.json" - : > "$output_file" # Clear the output file - - local count=0 - - local json="[" - - for template in "${MOBILE_ALL[@]}"; do - IFS="|" read -r platformName deviceName baseVersion <<< "$template" - if [ "$APP_PLATFORM" == "ios" ] && [ "$platformName" != "ios" ]; then - continue - elif [ "$APP_PLATFORM" == "android" ] || [ "$APP_PLATFORM" == "all" ] && [ "$platformName" != "android" ]; then - continue - fi - - json="${json}{ - \"bstack:options\": { - \"deviceName\": \"${deviceName}\", - \"osVersion\": \"${baseVersion}.0\" - } - }," - - count=$((count + 1)) - if [ "$count" -ge "$max_total" ]; then - break - fi - done # End of the loop - json="${json%,}]" - echo "$json" > "$output_file" +# Import utilities +# shellcheck source=/dev/null +source "$(dirname "$0")/common-utils.sh" +# shellcheck source=/dev/null +source "$(dirname "$0")/logging-utils.sh" +# shellcheck source=/dev/null +source "$(dirname "$0")/env-setup-run.sh" +# shellcheck source=/dev/null +source "$(dirname "$0")/user-interaction.sh" +# shellcheck source=/dev/null +source "$(dirname "$0")/env-prequisite-checks.sh" + +# ===== Web wrapper with retry logic (writes runtime logs to $NOW_RUN_LOG_FILE) ===== +# Wrapper functions using the common setup_environment function +run_setup() { + local test_type=$1 + local tech_stack=$2 + setup_environment "$test_type" "$tech_stack" } -# ===== Fetch plan details (writes to GLOBAL) ===== -fetch_plan_details() { - log_msg_to "ℹ️ Fetching BrowserStack Plan Details..." "$GLOBAL_LOG_FILE" - local web_unauthorized=false - local mobile_unauthorized=false - - if [[ "$TEST_TYPE" == "Web" || "$TEST_TYPE" == "Both" ]]; then - RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api.browserstack.com/automate/plan.json) - HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1) - RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d') - if [ "$HTTP_CODE_WEB" == "200" ]; then - WEB_PLAN_FETCHED=true - TEAM_PARALLELS_MAX_ALLOWED_WEB=$(echo "$RESPONSE_WEB_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') - log_msg_to "✅ Web Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_WEB" "$GLOBAL_LOG_FILE" - else - log_msg_to "❌ Web Testing Plan fetch failed ($HTTP_CODE_WEB)" "$GLOBAL_LOG_FILE" - [ "$HTTP_CODE_WEB" == "401" ] && web_unauthorized=true - fi - fi - if [[ "$TEST_TYPE" == "App" || "$TEST_TYPE" == "Both" ]]; then - RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json) - HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1) - RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d') - if [ "$HTTP_CODE_MOBILE" == "200" ]; then - MOBILE_PLAN_FETCHED=true - TEAM_PARALLELS_MAX_ALLOWED_MOBILE=$(echo "$RESPONSE_MOBILE_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') - log_msg_to "✅ Mobile App Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "$GLOBAL_LOG_FILE" - else - log_msg_to "❌ Mobile App Testing Plan fetch failed ($HTTP_CODE_MOBILE)" "$GLOBAL_LOG_FILE" - [ "$HTTP_CODE_MOBILE" == "401" ] && mobile_unauthorized=true - fi - fi - - if [[ "$TEST_TYPE" == "Web" && "$web_unauthorized" == true ]] || \ - [[ "$TEST_TYPE" == "App" && "$mobile_unauthorized" == true ]] || \ - [[ "$TEST_TYPE" == "Both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then - log_msg_to "❌ Unauthorized to fetch required plan(s). Exiting." "$GLOBAL_LOG_FILE" - exit 1 - fi -} +# ===== Main flow (baseline steps then run) ===== -# Function to check if IP is private -is_private_ip() { - case $1 in - 10.* | 192.168.* | 172.16.* | 172.17.* | 172.18.* | 172.19.* | \ - 172.20.* | 172.21.* | 172.22.* | 172.23.* | 172.24.* | 172.25.* | \ - 172.26.* | 172.27.* | 172.28.* | 172.29.* | 172.30.* | 172.31.* | "") - return 0 ;; # Private - *) - return 1 ;; # Public - esac -} +RUN_MODE=$1 # --interactive or --silent / --debug +TT=$2 # Testing Type from env (for silent mode) +TSTACK=$3 # Tech Stack from env (for silent mode) -is_domain_private() { - domain=${CX_TEST_URL#*://} # remove protocol - domain=${domain%%/*} # remove everything after first "/" - log_msg_to "Website domain: $domain" - export NOW_WEB_DOMAIN="$CX_TEST_URL" +log_section "🧭 Setup Summary – BrowserStack NOW" +log_info "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')" -# Resolve domain using Cloudflare DNS -IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1) -# Determine if domain is private -if is_private_ip "$IP_ADDRESS"; then - is_cx_domain_private=0 +log_file="" +if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + TEST_TYPE=$TT + TECH_STACK=$TSTACK + log_file="$LOG_DIR/${TEST_TYPE:unknown}_${TECH_STACK:unknown}_run_result.log" + log_info "Run Mode: ${RUN_MODE:-default}" else - is_cx_domain_private=-1 + get_test_type "$RUN_MODE" + get_tech_stack "$RUN_MODE" + log_file="$LOG_DIR/${TEST_TYPE:unknown}_${TECH_STACK:unknown}_run_result.log" + perform_next_steps_based_on_test_type "$TEST_TYPE" fi -log_msg_to "Resolved IPs: $IP_ADDRESS" - -return $is_cx_domain_private -} - - -setup_web_java() { - local local_flag=$1 - local parallels=$2 - - REPO="now-testng-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" - rm -rf $TARGET_DIR - # === 1️⃣ Clone Repo === - log_msg_to "📦 Cloning repo $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - git clone https://github.com/browserstackCE/$REPO "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true - - - cd "$TARGET_DIR"|| return 1 - - - if is_domain_private; then - local_flag=true - fi - - # === 4️⃣ Local Flag === - if [ "$local_flag" = "true" ]; then - log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - else - log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - fi - - # === 5️⃣ YAML Setup === - log_msg_to "🧩 Generating YAML config (bstack.yml)" "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - - - # YAML config path - export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" - platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") - - cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$WEB_LOG_FILE" 2>&1 || true - - log_msg_to "🚀 Running 'mvn test -P sample-test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - mvn test -P sample-test >> "$WEB_LOG_FILE" 2>&1 & - cmd_pid=$!|| true - - show_spinner "$cmd_pid" - wait "$cmd_pid" - - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - -setup_web_python() { - local local_flag=$1 - local parallels=$2 - local log_file=$3 - - REPO="now-pytest-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - rm -rf $TARGET_DIR - - git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true - log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" - - - cd "$TARGET_DIR" || return 1 - - - # Setup Python venv - if [ ! -d "venv" ]; then - python3 -m venv venv - log_msg_to "✅ Created Python virtual environment" - fi - - # shellcheck disable=SC1091 - source venv/bin/activate - pip3 install -r requirements.txt >> "$WEB_LOG_FILE" 2>&1 - - # Export credentials for pytest to use - export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" - - # Update YAML at root level (browserstack.yml) - export BROWSERSTACK_CONFIG_FILE="browserstack.yml" - platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") - - if is_domain_private; then - local_flag=true - fi - - # === 4️⃣ Local Flag === - if [ "$local_flag" = "true" ]; then - log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - else - log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - fi - - - cat > browserstack.yml <> "$WEB_LOG_FILE" 2>&1 & - cmd_pid=$!|| true - - show_spinner "$cmd_pid" - wait "$cmd_pid" - - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - - -setup_web_nodejs() { - local local_flag=$1 - local parallels=$2 - - REPO="now-webdriverio-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - rm -rf $TARGET_DIR - - mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" - - # === 1️⃣ Clone Repo === - log_msg_to "📦 Cloning repo $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true - - cd "$TARGET_DIR" || return 1 - - # === 2️⃣ Install Dependencies === - log_msg_to "⚙️ Running 'npm install'" "$WEB_LOG_FILE" - npm install >> "$WEB_LOG_FILE" 2>&1 || true - - - # === 4️⃣ Generate Capabilities JSON === - log_msg_to "🧩 Generating browser/OS capabilities" "$WEB_LOG_FILE" - local caps_json - caps_json=$(generate_web_caps_json "$parallels") - - export BSTACK_PARALLELS=$parallels - - export BSTACK_CAPS_JSON=$caps_json - - if is_domain_private; then - local_flag=true - fi - - #log_msg_to local flag status - if [ "$local_flag" = "true" ]; then - log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$WEB_LOG_FILE" - else - log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$WEB_LOG_FILE" - fi - - # === 7️⃣ Export BrowserStack Credentials === - export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" - export BROWSERSTACK_LOCAL=$local_flag - - # === 8️⃣ Run Tests === - log_msg_to "🚀 Running 'npm run test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$WEB_LOG_FILE" - npm run test >> "$WEB_LOG_FILE" 2>&1 || true & - cmd_pid=$!|| true - - show_spinner "$cmd_pid" - wait "$cmd_pid" - - # === 9️⃣ Wrap Up === - log_msg_to "✅ Web JS setup and test execution completed successfully." "$WEB_LOG_FILE" - - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - - -# ===== Web wrapper with retry logic (writes runtime logs to WEB_LOG_FILE) ===== -setup_web() { - log_msg_to "Starting Web setup for $TECH_STACK" "$WEB_LOG_FILE" - - local local_flag=false - local attempt=1 - local success=true - local log_file=$WEB_LOG_FILE - # don't pre-create; file will be created on first write by log_msg_to or command output redirection - - local total_parallels - total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - [ -z "$total_parallels" ] && total_parallels=1 - local parallels_per_platform - parallels_per_platform=$total_parallels - - while [ "$attempt" -le 1 ]; do - log_msg_to "[Web Setup]" "$WEB_LOG_FILE" - case "$TECH_STACK" in - Java) - setup_web_java "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" - if (grep -qiE "BUILD FAILURE" "$WEB_LOG_FILE"); then - success=false - fi - ;; - Python) - setup_web_python "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" - if (grep -qiE "BUILD FAILURE" "$WEB_LOG_FILE"); then ## needs to change based on pytest output - success=false - fi - ;; - +log_info "Log file path: $log_file" +export NOW_RUN_LOG_FILE="$log_file" +setup_workspace +get_browserstack_credentials "$RUN_MODE" - NodeJS) setup_web_nodejs "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" - if (grep -qiE "([1-9][0-9]*) passed, ([1-9]*) failed" "$WEB_LOG_FILE"); then - success=false - fi - ;; +log_section "⚙️ Platform & Tech Stack" +log_info "Platform: ${TEST_TYPE:-N/A}" +log_info "Tech Stack: ${TECH_STACK:-N/A}" - *) log_msg_to "Unknown TECH_STACK: $TECH_STACK" "$WEB_LOG_FILE"; return 1 ;; - esac +validate_tech_stack_installed "$TECH_STACK" +fetch_plan_details "$TEST_TYPE" - if [ "$success" = true ]; then - log_msg_to "✅ Web setup succeeded." "$WEB_LOG_FILE" - break - else - log_msg_to "❌ Web setup failed. Check $WEB_LOG_FILE for details" "$WEB_LOG_FILE" - break +if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + if [[ $TEST_TYPE == "app" ]]; then + get_upload_app + log_success "Sample App uploaded successfully" fi - done -} - - -setup_mobile_python() { - local local_flag=$1 - local parallels=$2 - local log_file=$3 - - REPO="pytest-appium-app-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - rm -rf $TARGET_DIR - - # Clone repo if not present - git clone https://github.com/browserstack/$REPO.git "$TARGET_DIR" - log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" - - cd "$TARGET_DIR" || return 1 - - # Create & activate venv (if not exists) - if [ ! -d "venv" ]; then - python3 -m venv venv - fi - # shellcheck disable=SC1091 - source venv/bin/activate - - # Install dependencies - pip install -r requirements.txt >> "$log_file" 2>&1 - - # Export credentials - export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" - - # Prepare platform-specific YAMLs in android/ and ios/ - local original_platform="$APP_PLATFORM" - - APP_PLATFORM="android" - local platform_yaml_android - platform_yaml_android=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") - cat > android/browserstack.yml < ios/browserstack.yml < android/bstack_sample.py <<'PYEOF' -import pytest - - -@pytest.mark.usefixtures('setWebdriver') -class TestUniversalAppCheck: - - def test_app_health_check(self): - - # 1. Get initial app and device state (no locators) - initial_package = self.driver.current_package - initial_activity = self.driver.current_activity - initial_orientation = self.driver.orientation - - # 2. Log the captured data to BrowserStack using 'annotate' - log_data = f"Initial State: Package='{initial_package}', Activity='{initial_activity}', Orientation='{initial_orientation}'" - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' - ) - - # 3. Perform a locator-free action: change device orientation - self.driver.orientation = 'LANDSCAPE' - - # 4. Perform locator-free assertions - assert self.driver.orientation == 'LANDSCAPE' - - # 5. Log the successful state change - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "Successfully changed orientation to LANDSCAPE", "level": "info"}}' - ) - - # 6. Set the final session status to 'passed' - self.driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "App state verified and orientation changed!"}}' - ) -PYEOF - - cp android/bstack_sample.py ios/bstack_sample.py - - # Decide which directory to run based on APP_PLATFORM (default to android) - local run_dir="android" - if [ "$APP_PLATFORM" = "ios" ]; then - run_dir="ios" - fi - - if is_domain_private; then - local_flag=true - fi - - # Log local flag status - if [ "$local_flag" = "true" ]; then - log_msg_to "✅ BrowserStack Local is ENABLED for this run." - else - log_msg_to "✅ BrowserStack Local is DISABLED for this run." - fi - - # Run pytest with BrowserStack SDK from the chosen platform directory - log_msg_to "🚀 Running 'cd $run_dir && browserstack-sdk pytest -s bstack_sample.py'" - ( - cd "$run_dir" && browserstack-sdk pytest -s bstack_sample.py >> "$log_file" 2>&1 || true - ) - - deactivate - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - -setup_mobile_java() { - local local_flag=$1 - local parallels=$2 - local log_file=$3 - - REPO="now-testng-appium-app-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - rm -rf $TARGET_DIR - - git clone https://github.com/BrowserStackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true - log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" "$MOBILE_LOG_FILE" - cd "$TARGET_DIR" || return 1 - -if [[ "$APP_PLATFORM" == "all" || "$APP_PLATFORM" == "android" ]]; then - cd android/testng-examples -else - cd ios/testng-examples fi +log_msg_to "Plan summary: WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_WEB), MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE)" +log_msg_to "Checking proxy in environment" +set_proxy_in_env - export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" - - # YAML config path - export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" - platform_yaml=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") - - cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$MOBILE_LOG_FILE" 2>&1; then - log_msg_to "❌ 'mvn clean' FAILED. See $log_file for details." "$GLOBAL_LOG_FILE" "$MOBILE_LOG_FILE" - return 1 # Fail the function if clean fails - fi - - log_msg_to "🚀 Running 'mvn test -P sample-test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$GLOBAL_LOG_FILE" "$WEB_LOG_FILE" - mvn test -P sample-test >> "$MOBILE_LOG_FILE" 2>&1 & - cmd_pid=$!|| true - - show_spinner "$cmd_pid" - wait "$cmd_pid" - - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - +log_section "🧹 Getting Ready" +detect_os +log_info "Detected Operating system: $NOW_OS" +log_info "Clearing old logs fron NOW Home Directory inside .browserstack" -setup_mobile_nodejs() { - local local_flag=$1 - local parallels=$2 - local log_file=$3 - - echo "Starting Mobile NodeJS setup with parallels: $parallels" >> "$log_file" 2>&1 - - # cd $WORKSPACE_DIR/$PROJECT_FOLDER || return 1 - - REPO="now-webdriverio-appium-app-browserstack" - TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - - rm -rf $TARGET_DIR - git clone https://github.com/BrowserStackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true - log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$GLOBAL_LOG_FILE" "$MOBILE_LOG_FILE" - cd "$TARGET_DIR"/test || return 1 - - # === 2️⃣ Install Dependencies === - log_msg_to "⚙️ Running 'npm install'" "$MOBILE_LOG_FILE" - npm install >> "$MOBILE_LOG_FILE" 2>&1 || true - - generate_mobile_caps_json "$parallels" - - export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" - export BSTACK_PARALLELS=$parallels - - echo "Parallels for NODEJS Repo: " $BSTACK_PARALLELS - -# === 8️⃣ Run Tests === - log_msg_to "🚀 Running 'npm run test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$WEB_LOG_FILE" - npm run test >> "$MOBILE_LOG_FILE" 2>&1 || true & - cmd_pid=$!|| true - - show_spinner "$cmd_pid" - wait "$cmd_pid" - - # === 9️⃣ Wrap Up === - log_msg_to "✅ Mobile JS setup and test execution completed successfully." "$MOBILE_LOG_FILE" - - cd "$WORKSPACE_DIR/$PROJECT_FOLDER" - return 0 -} - -# ===== Mobile wrapper with retry logic (writes runtime logs to MOBILE_LOG_FILE) ===== -setup_mobile() { - log_msg_to "Starting Mobile setup for $TECH_STACK" "$WEB_LOG_FILE" - - local local_flag=false - local attempt=1 - local success=true - local log_file=$WEB_LOG_FILE - # don't pre-create; file will be created on first write by log_msg_to or command output redirection - - local total_parallels - total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - [ -z "$total_parallels" ] && total_parallels=1 - local parallels_per_platform - parallels_per_platform=$total_parallels - - - while [ "$attempt" -le 1 ]; do - log_msg_to "[Mobile Setup]" "$MOBILE_LOG_FILE" - case "$TECH_STACK" in - Java) - setup_mobile_java "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" - if (grep -qiE "BUILD FAILURE" "$MOBILE_LOG_FILE"); then - success=false - fi - ;; - Python) - setup_mobile_python "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" - if (grep -qiE "BUILD FAILURE" "$MOBILE_LOG_FILE"); then ## needs to change based on pytest output - success=false - fi - ;; - - - NodeJS) setup_mobile_nodejs "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" - if (grep -qiE "([1-9][0-9]*) passed, ([1-9]*) failed" "$MOBILE_LOG_FILE"); then - success=false - fi - ;; - - *) log_msg_to "Unknown TECH_STACK: $TECH_STACK" "$MOBILE_LOG_FILE"; return 1 ;; - esac - - if [ "$success" = true ]; then - log_msg_to "✅ Mobile setup succeeded." "$MOBILE_LOG_FILE" - break - else - log_msg_to "❌ Mobile setup failed. Check $MOBILE_LOG_FILE for details" "$MOBILE_LOG_FILE" - break - fi - done -} - -# ===== Orchestration: decide what to run based on TEST_TYPE and plan fetch ===== -run_setup() { - log_msg_to "Orchestration: TEST_TYPE=$TEST_TYPE, WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED, MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED" "$GLOBAL_LOG_FILE" - - case "$TEST_TYPE" in - Web) - if [ "$WEB_PLAN_FETCHED" == true ]; then - setup_web - else - log_msg_to "⚠️ Skipping Web setup — Web plan not fetched" "$GLOBAL_LOG_FILE" - fi - ;; - App) - if [ "$MOBILE_PLAN_FETCHED" == true ]; then - setup_mobile - else - log_msg_to "⚠️ Skipping Mobile setup — Mobile plan not fetched" "$GLOBAL_LOG_FILE" - fi - ;; - Both) - local ran_any=false - if [ "$WEB_PLAN_FETCHED" == true ]; then - setup_web - ran_any=true - else - log_msg_to "⚠️ Skipping Web setup — Web plan not fetched" "$GLOBAL_LOG_FILE" - fi - if [ "$MOBILE_PLAN_FETCHED" == true ]; then - setup_mobile - ran_any=true - else - log_msg_to "⚠️ Skipping Mobile setup — Mobile plan not fetched" "$GLOBAL_LOG_FILE" - fi - if [ "$ran_any" == false ]; then - log_msg_to "❌ Both Web and Mobile setup were skipped. Exiting." "$GLOBAL_LOG_FILE" - exit 1 - fi - ;; - *) - log_msg_to "❌ Invalid TEST_TYPE: $TEST_TYPE" "$GLOBAL_LOG_FILE" - exit 1 - ;; - esac -} - -# ===== Main flow (baseline steps then run) ===== -setup_workspace -ask_browserstack_credentials -ask_test_type -ask_tech_stack -validate_tech_stack_installed -fetch_plan_details +clear_old_logs -log_msg_to "Plan summary: WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_WEB), MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE)" "$GLOBAL_LOG_FILE" -log_msg_to "Checking proxy in environment" "$GLOBAL_LOG_FILE" -chmod +x ./mac/proxy-check.sh -./mac/proxy-check.sh -log_msg_to "Starting setup run..." "$GLOBAL_LOG_FILE" -run_setup -log_msg_to "Setup run finished." "$GLOBAL_LOG_FILE" +log_info "Starting $TEST_TYPE setup for $TECH_STACK" +run_setup "$TEST_TYPE" "$TECH_STACK" diff --git a/mac/user-interaction.sh b/mac/user-interaction.sh new file mode 100644 index 0000000..ecba65d --- /dev/null +++ b/mac/user-interaction.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# ===== Credential Management ===== +get_browserstack_credentials() { + local run_mode=$1 + local username="" + local access_key="" + if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + username="$BROWSERSTACK_USERNAME" + access_key="$BROWSERSTACK_ACCESS_KEY" + log_info "BrowserStack credentials loaded from environment variables for user: $username" + else + username=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Username.\n\nNote: Locate it in your BrowserStack account profile page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + + if [ -z "$username" ]; then + log_msg_to "❌ Username empty" + return 1 + fi + + access_key=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Access Key.\n\nNote: Locate it in your BrowserStack account page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with hidden answer with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + if [ -z "$access_key" ]; then + log_msg_to "❌ Access Key empty" + return 1 + fi + + export BROWSERSTACK_USERNAME=$username + export BROWSERSTACK_ACCESS_KEY=$access_key + log_info "BrowserStack credentials captured from user: $username" + fi + + return 0 +} + +# ===== Tech Stack Management ===== +get_tech_stack() { + local run_mode=$1 + local tech_stack="" + if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + tech_stack="$TSTACK" + log_msg_to "✅ Selected Tech Stack from environment: $tech_stack" + else + tech_stack=$(osascript -e 'Tell application "System Events" to display dialog "Select installed tech stack:" buttons {"java", "python", "nodejs"} default button "java" with title "Testing Framework Technology Stack"' \ + -e 'button returned of result') + fi + log_msg_to "✅ Selected Tech Stack: $tech_stack" + log_info "Tech Stack: $tech_stack" + + export TECH_STACK="$tech_stack" + log_msg_to "Exported TECH_STACK=$TECH_STACK" +} + + +# ===== URL Management ===== +get_test_url() { + local test_url=$DEFAULT_TEST_URL + + test_url=$(osascript -e 'Tell application "System Events" to display dialog "Enter the URL you want to test with BrowserStack:\n(Leave blank for default: '"$DEFAULT_TEST_URL"')" default answer "" with title "Test URL Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + + if [ -n "$test_url" ]; then + log_msg_to "🌐 Using custom test URL: $test_url" + log_info "🌐 Using custom test URL: $test_url" + else + test_url="$DEFAULT_TEST_URL" + log_msg_to "⚠️ No URL entered. Falling back to default: $test_url" + log_info "No URL entered. Falling back to default: $test_url" + fi + + export CX_TEST_URL="$test_url" + log_msg_to "Exported TEST_URL $CX_TEST_URL" +} + + +get_test_type() { + local test_type="" + if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + test_type=$TT + log_msg_to "✅ Selected Testing Type from environment: $TEST_TYPE" + else + test_type=$(osascript -e 'Tell application "System Events" to display dialog "Select testing type:" buttons {"web", "app"} default button "web" with title "Testing Type"' \ + -e 'button returned of result') + log_msg_to "✅ Selected Testing Type: $TEST_TYPE" + RUN_MODE=$test_type + log_info "Run Mode: ${RUN_MODE:-default}" + fi + export TEST_TYPE="$test_type" + log_msg_to "Exported TEST_TYPE=$TEST_TYPE" +} + +perform_next_steps_based_on_test_type() { + local test_type=$1 + case "$test_type" in + "web") + get_test_url + ;; + "app") + get_upload_app + ;; + "both") + get_test_url + get_upload_app + ;; + esac +} + +get_upload_app() { + log_msg_to "Determining app upload steps." + handle_app_upload +} \ No newline at end of file