diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 504607c..26677fb 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -14,7 +14,7 @@ on: jobs: test-mac: name: Test mac/run.sh on macOS - # if: false + if: false runs-on: macos-latest timeout-minutes: 15 environment: BrowserStack @@ -34,26 +34,27 @@ jobs: run: | echo "Validating mac/run.sh syntax..." bash -n mac/run.sh + bash -n common/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 + bash -n common/mac/common-utils.sh + bash -n common/mac/logging-utils.sh + bash -n common/mac/env-setup-run.sh + bash -n common/mac/user-interaction.sh + bash -n common/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 + chmod +x common/mac/common-utils.sh + chmod +x common/mac/logging-utils.sh + chmod +x common/mac/env-setup-run.sh + chmod +x common/mac/user-interaction.sh + chmod +x common/mac/env-prequisite-checks.sh echo "✅ All scripts are executable" - name: Run ShellCheck on mac scripts @@ -61,11 +62,12 @@ jobs: 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 + shellcheck -x common/mac/run.sh || true + shellcheck -x common/mac/common-utils.sh || true + shellcheck -x common/mac/logging-utils.sh || true + shellcheck -x common/mac/env-setup-run.sh || true + shellcheck -x common/mac/user-interaction.sh || true + shellcheck -x common/mac/env-prequisite-checks.sh || true echo "✅ ShellCheck analysis complete" - name: Verify required dependencies @@ -81,7 +83,7 @@ jobs: run: | set -e echo "Testing script sourcing..." - bash -c "source mac/common-utils.sh && echo '✅ common-utils.sh sourced successfully'" + bash -c "source common/mac/common-utils.sh && echo '✅ common-utils.sh sourced successfully'" echo "✅ Script sourcing successful" - name: Integration Test - Silent Mode Execution @@ -198,29 +200,31 @@ jobs: - name: Check PowerShell version run: | $PSVersionTable.PSVersion - Write-Host "✅ PowerShell version check complete" + 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 -Raw $ScriptPath), [ref]$null) - Write-Host "✅ win/run.ps1 syntax is valid" + $CommonScriptPath = "common/win/run.ps1" + $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $CommonScriptPath), [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/common-utils.ps1", - "win/logging-utils.ps1", - "win/env-prequisite-checks.ps1", - "win/user-interaction.ps1", - "win/env-setup-run.ps1", - "win/device-machine-allocation.ps1" + "common/win/common-utils.ps1", + "common/win/logging-utils.ps1", + "common/win/env-prequisite-checks.ps1", + "common/win/user-interaction.ps1", + "common/win/env-setup-run.ps1", + "common/win/device-machine-allocation.ps1" ) foreach ($Script in $Scripts) { $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $Script), [ref]$null) - Write-Host "✅ $Script syntax is valid" + Write-Host "$Script syntax is valid" } - name: Run PSScriptAnalyzer @@ -232,7 +236,7 @@ jobs: Write-Host "Running PSScriptAnalyzer..." Invoke-ScriptAnalyzer -Path "win" -Recurse -ReportSummary -ErrorAction Continue - Write-Host "✅ PSScriptAnalyzer analysis complete (continuing even if issues are found)" + Write-Host "PSScriptAnalyzer analysis complete (continuing even if issues are found)" - name: Check script file encoding run: | @@ -250,14 +254,14 @@ jobs: } Write-Host "Detected encoding (heuristic): $encoding" - Write-Host "✅ Encoding check complete" + 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" } else { Write-Host "⚠️ curl not found" } - if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "✅ git found" } else { Write-Host "⚠️ git not found" } - Write-Host "✅ PowerShell dependencies verified" + if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "curl found" } else { Write-Host "⚠️ curl not found" } + if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "git found" } else { Write-Host "⚠️ git not found" } + Write-Host "PowerShell dependencies verified" - name: Integration Test - Silent Mode Execution if: success() @@ -280,31 +284,21 @@ jobs: # Absolute path is safer in CI $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" - $testConfigs = @( - @("web", "java"), - @("app", "java"), - @("web", "python"), - @("app", "python"), - @("web", "nodejs"), - @("app", "nodejs") - ) - - $overallFailed = $false $logRoot = Join-Path $env:TEMP "now-tests" New-Item -ItemType Directory -Force -Path $logRoot | Out-Null - $logPath = Join-Path $logRoot "run_test_app_nodejs.log" + $logPath = Join-Path $logRoot "run_test_web_python.log" Write-Host "================================" - Write-Host "Testing: $scriptPath --silent app nodejs" + Write-Host "Testing: $scriptPath --silent web python" Write-Host "================================" - & $scriptPath --silent app nodejs 2>&1 | Tee-Object -FilePath $logPath -Append + & $scriptPath --silent web python 2>&1 | Tee-Object -FilePath $logPath -Append $exitCode = $LASTEXITCODE if ($exitCode -eq 0) { - Write-Host "✅ app / nodejs completed (exit code: $exitCode)" + Write-Host "web / python completed (exit code: $exitCode)" } else { - Write-Host "⚠️ app / nodejs exited with code: $exitCode" + Write-Host "web / python exited with code: $exitCode" if (Test-Path $logPath) { Write-Host "Log output (last 20 lines):" Get-Content -Path $logPath -Tail 20 @@ -312,41 +306,67 @@ jobs: exit 1 } - # $logRoot = Join-Path $env:TEMP "now-tests" - # New-Item -ItemType Directory -Force -Path $logRoot | Out-Null + $testConfigs = @( + @("web", "python"), + @("app", "python"), + @("web", "nodejs"), + @("app", "nodejs") + ) + + $overallFailed = $false + + $logRoot = Join-Path $env:TEMP "now-tests" + New-Item -ItemType Directory -Force -Path $logRoot | Out-Null - # foreach ($config in $testConfigs) { - # $testType = $config[0] - # $techStack = $config[1] + foreach ($config in $testConfigs) { + $testType = $config[0] + $techStack = $config[1] - # Write-Host "================================" - # Write-Host "Testing: $scriptPath --silent $testType $techStack" - # Write-Host "================================" + Write-Host "================================" + Write-Host "Testing: $scriptPath --silent $testType $techStack" + Write-Host "================================" - # $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" + $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" + $logPathErr = Join-Path $logRoot "run_test_${testType}_${techStack}.err" - # & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append - # $exitCode = $LASTEXITCODE + # Run with timeout (600 seconds) + $p = Start-Process -FilePath "pwsh" -ArgumentList "-File", $scriptPath, "--silent", $testType, $techStack -RedirectStandardOutput $logPath -RedirectStandardError $logPathErr -PassThru -NoNewWindow + + # Wait for 600 seconds (600000 ms) + $exited = $p.WaitForExit(600000) + + if (-not $exited) { + Write-Host "$testType / $techStack timed out after 600 seconds" + $p.Kill() + $exitCode = 124 + } else { + $exitCode = $p.ExitCode + } - # if ($exitCode -eq 0) { - # Write-Host "✅ $testType / $techStack completed (exit code: $exitCode)" - # } else { - # Write-Host "⚠️ $testType / $techStack exited with code: $exitCode" - # $overallFailed = $true + if ($exitCode -eq 0) { + Write-Host "$testType / $techStack completed (exit code: $exitCode)" + } else { + Write-Host "$testType / $techStack exited with code: $exitCode" + $overallFailed = $true - # if (Test-Path $logPath) { - # Write-Host "Log output (last 20 lines):" - # Get-Content -Path $logPath -Tail 20 - # } - # } - # } + if (Test-Path $logPath) { + Write-Host "Log output (last 20 lines):" + Get-Content -Path $logPath -Tail 20 + } + } + + if (Test-Path $logPathErr) { + Write-Host "Error output (last 20 lines):" + Get-Content -Path $logPathErr -Tail 20 + } + } if ($overallFailed) { Write-Error "One or more configurations failed." exit 1 } - Write-Host "✅ All integration tests completed successfully" + Write-Host "All integration tests completed successfully" - name: Sync BrowserStack logs to workspace (Windows) if: always() @@ -380,7 +400,7 @@ jobs: test-linux: name: Test mac/run.sh on Linux - # if: false + if: false runs-on: ubuntu-latest timeout-minutes: 15 environment: BrowserStack @@ -400,27 +420,28 @@ jobs: run: | echo "Validating mac/run.sh syntax..." bash -n mac/run.sh - echo "✅ mac/run.sh syntax is valid" + bash -n common/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" + bash -n common/mac/common-utils.sh + bash -n common/mac/logging-utils.sh + bash -n common/mac/env-setup-run.sh + bash -n common/mac/user-interaction.sh + bash -n common/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" + chmod +x common/mac/common-utils.sh + chmod +x common/mac/logging-utils.sh + chmod +x common/mac/env-setup-run.sh + chmod +x common/mac/user-interaction.sh + chmod +x common/mac/env-prequisite-checks.sh + echo "All scripts are executable" - name: Install ShellCheck run: | @@ -433,28 +454,29 @@ jobs: 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" + shellcheck -x common/mac/run.sh || true + shellcheck -x common/mac/common-utils.sh || true + shellcheck -x common/mac/logging-utils.sh || true + shellcheck -x common/mac/env-setup-run.sh || true + shellcheck -x common/mac/user-interaction.sh || true + shellcheck -x common/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" + 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" + bash -c "source common/mac/common-utils.sh && echo 'common-utils.sh sourced successfully'" + echo "Script sourcing successful" - name: Integration Test - Silent Mode Execution if: success() diff --git a/common/config/devices.txt b/common/config/devices.txt new file mode 100644 index 0000000..fb21c7c --- /dev/null +++ b/common/config/devices.txt @@ -0,0 +1,23 @@ +# Format: TYPE|PLATFORM|DEVICE_OR_BROWSER +# Web +WEB|Windows|Chrome +WEB|Windows|Firefox +WEB|Windows|Edge +MOBILE|ios|iPhone 1[234567]* +MOBILE|android|Samsung Galaxy S* +WEB|OS X|Chrome +WEB|OS X|Safari +WEB|OS X|Firefox +MOBILE|ios|iPad Air* +MOBILE|android|Samsung Galaxy Tab* +MOBILE|android|Samsung Galaxy M* +MOBILE|android|Google Pixel [56789]* +MOBILE|android|Vivo Y* +MOBILE|android|Oppo* +MOBILE|ios|iPad Pro* +MOBILE|android|Samsung Galaxy A* +MOBILE|android|Google Pixel 10* +MOBILE|android|OnePlus * +MOBILE|android|Vivo V* +MOBILE|android|Xiaomi * +MOBILE|android|Huawei * diff --git a/common/config/repos.txt b/common/config/repos.txt new file mode 100644 index 0000000..f57e9e6 --- /dev/null +++ b/common/config/repos.txt @@ -0,0 +1,6 @@ +web_java|now-testng-browserstack| +app_java|now-testng-appium-app-browserstack| +web_python|now-pytest-browserstack| +app_python|now-pytest-appium-app-browserstack| +web_nodejs|now-webdriverio-browserstack| +app_nodejs|now-webdriverio-appium-app-browserstack| diff --git a/mac/common-utils.sh b/common/mac/common-utils.sh similarity index 83% rename from mac/common-utils.sh rename to common/mac/common-utils.sh index e41f4f8..bda1845 100644 --- a/mac/common-utils.sh +++ b/common/mac/common-utils.sh @@ -1,11 +1,14 @@ #!/bin/bash +# shellcheck source=/dev/null # shellcheck source=/dev/null source "$(dirname "$0")/device-machine-allocation.sh" # # ===== Global Variables ===== WORKSPACE_DIR="$HOME/.browserstack" PROJECT_FOLDER="NOW" +GUI_SCRIPT="$(dirname "${BASH_SOURCE[0]}")/windows-gui.ps1" + # URL handling DEFAULT_TEST_URL="https://bstackdemo.com" @@ -56,18 +59,18 @@ show_spinner() { 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}" + printf "\rProcessing... %s" "${spin:$i:1}" sleep 0.1 done echo "" log_info "Run Test command completed." sleep 5 - #log_msg_to "✅ Done!" + #log_msg_to "Done!" } # ===== Workspace Management ===== setup_workspace() { - log_section "⚙️ Environment & Credentials" + log_section "Environment & Credentials" local full_path="$WORKSPACE_DIR/$PROJECT_FOLDER" if [ ! -d "$full_path" ]; then mkdir -p "$full_path" @@ -81,6 +84,11 @@ setup_workspace() { # ===== App Upload Management ===== handle_app_upload() { local app_platform="" + if [[ -n "$CLI_APP_PATH" ]]; then + upload_custom_app "$CLI_APP_PATH" + return + fi + if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then upload_sample_app app_platform="android" @@ -96,6 +104,8 @@ handle_app_upload() { buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬ default button "Upload my App (.apk/.ipa)" ' 2>/dev/null) + elif [[ "$NOW_OS" == "windows" ]]; then + choice=$(powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "ClickChoice" -Title "BrowserStack App Upload" -Prompt "How would you like to select your app?" -Choices "Use Sample App,Upload my App (.apk/.ipa),Cancel" -DefaultChoice "Upload my App (.apk/.ipa)" | tr -d '\r') else echo "How would you like to select your app?" select opt in "Use Sample App" "Upload my App (.apk/.ipa)" "Cancel"; do @@ -122,7 +132,7 @@ handle_app_upload() { } upload_sample_app() { - log_msg_to "⬆️ Uploading sample app to BrowserStack..." + 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" \ @@ -134,24 +144,27 @@ upload_sample_app() { log_info "Uploaded app URL: $app_url" if [ -z "$app_url" ]; then - log_msg_to "❌ Upload failed. Response: $upload_response" + log_msg_to "Upload failed. Response: $upload_response" return 1 fi - log_msg_to "✅ App uploaded successfully: $app_url" + log_msg_to "App uploaded successfully: $app_url" return 0 } upload_custom_app() { local app_platform="" - local file_path + local file_path="$1" - # Convert to POSIX path + if [ -z "$file_path" ]; then + # Convert to POSIX path # Convert to POSIX path if [[ "$NOW_OS" == "macos" ]]; then file_path=$(osascript -e \ 'POSIX path of (choose file with prompt "Select your .apk or .ipa file:" of type {"apk", "ipa"})' \ 2>/dev/null) + elif [[ "$NOW_OS" == "windows" ]]; then + file_path=$(powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "OpenFileDialog" -Title "Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" | tr -d '\r') else echo "Please enter the full path to your .apk or .ipa file:" read -r file_path @@ -159,12 +172,13 @@ upload_custom_app() { file_path="${file_path%\"}" file_path="${file_path#\"}" fi + fi # Trim whitespace file_path="${file_path%"${file_path##*[![:space:]]}"}" if [ -z "$file_path" ]; then - log_msg_to "❌ No file selected" + log_msg_to "No file selected" return 1 fi @@ -175,11 +189,11 @@ upload_custom_app() { elif [[ "$file_path" == *.apk ]]; then app_platform="android" else - log_msg_to "❌ Invalid file type. Must be .apk or .ipa" + log_msg_to "Invalid file type. Must be .apk or .ipa" return 1 fi - log_msg_to "⬆️ Uploading app to BrowserStack..." + 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" \ @@ -188,12 +202,12 @@ upload_custom_app() { 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" + log_msg_to "Failed to upload app" return 1 fi export BROWSERSTACK_APP=$app_url - log_msg_to "✅ App uploaded successfully" + log_msg_to "App uploaded successfully" log_info "Uploaded app URL: $app_url" log_msg_to "Exported BROWSERSTACK_APP=$BROWSERSTACK_APP" @@ -217,7 +231,7 @@ generate_mobile_platforms() { local platformsListContentFormat=$2 local app_platform="$APP_PLATFORM" local platformsList="" - platformsList=$(pick_terminal_devices "$app_platform" "$max_total_parallels", "$platformsListContentFormat") + platformsList=$(pick_terminal_devices "$app_platform" "$max_total_parallels" "$platformsListContentFormat") echo "$platformsList" } @@ -226,7 +240,7 @@ generate_mobile_platforms() { fetch_plan_details() { local test_type=$1 - log_section "☁️ Account & Plan Details" + log_section "Account & Plan Details" log_info "Fetching BrowserStack plan for $test_type" local web_unauthorized=false local mobile_unauthorized=false @@ -239,9 +253,9 @@ fetch_plan_details() { 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" + 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)" + log_msg_to "Web Testing Plan fetch failed ($HTTP_CODE_WEB)" [ "$HTTP_CODE_WEB" == "401" ] && web_unauthorized=true fi fi @@ -254,9 +268,9 @@ fetch_plan_details() { 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" + 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)" + log_msg_to "Mobile App Testing Plan fetch failed ($HTTP_CODE_MOBILE)" [ "$HTTP_CODE_MOBILE" == "401" ] && mobile_unauthorized=true fi fi @@ -265,7 +279,7 @@ fetch_plan_details() { if [[ "$test_type" == "web" && "$web_unauthorized" == true ]] || \ [[ "$test_type" == "app" && "$mobile_unauthorized" == true ]]; then - log_msg_to "❌ Unauthorized to fetch required plan(s). Exiting." + log_msg_to "Unauthorized to fetch required plan(s). Exiting." exit 1 fi @@ -277,7 +291,7 @@ fetch_plan_details() { TEAM_PARALLELS_MAX_ALLOWED_MOBILE=5 export TEAM_PARALLELS_MAX_ALLOWED_MOBILE=5 fi - log_info "Resetting Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" + log_info "Silent mode: Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" fi } @@ -340,6 +354,15 @@ resolve_ip() { echo "$ip" } +report_bstack_local_status() { + local local_flag=$1 + if [ "$local_flag" == "true" ]; then + log_info "BrowserStack Local: ENABLED" + else + log_info "BrowserStack Local: DISABLED" + fi +} + identify_run_status_java() { local log_file=$1 @@ -348,7 +371,7 @@ identify_run_status_java() { 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." + log_warn "No test summary line found." return 1 fi @@ -380,7 +403,7 @@ identify_run_status_nodejs() { 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." + log_warn "No test summary line found." return 1 fi @@ -391,7 +414,7 @@ identify_run_status_nodejs() { log_success "Success: $passed test(s) passed" return 0 else - log_error "❌ Error: No tests passed" + log_error "Error: No tests passed" return 1 fi } @@ -405,11 +428,11 @@ identify_run_status_python() { # 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" + echo "Total Passed: $passed_sum" # If not found, fail if [[ -z "$passed_sum" ]]; then - log_warn "❌ No test summary line found." + log_warn "No test summary line found." return 1 fi @@ -418,7 +441,7 @@ identify_run_status_python() { log_success "Success: $passed_sum test(s) completed" return 0 else - log_error "❌ Error: No tests completed" + log_error "Error: No tests completed" return 1 fi } diff --git a/common/mac/device-machine-allocation.sh b/common/mac/device-machine-allocation.sh new file mode 100644 index 0000000..afc353f --- /dev/null +++ b/common/mac/device-machine-allocation.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +CONFIG_FILE="$(dirname "$0")/../config/devices.txt" + +get_devices() { + local type=$1 + local devices=() + while IFS= read -r line; do + # Skip comments and empty lines + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + + if [[ "$type" == "MOBILE" ]]; then + if [[ "$line" == MOBILE* ]]; then + devices+=("${line#MOBILE|}") + fi + else + if [[ "$line" == WEB* ]]; then + devices+=("${line#WEB|}") + elif [[ "$line" == MOBILE* ]]; then + # Web also includes mobile devices in the original script logic + devices+=("${line#MOBILE|}") + fi + fi + done < "$CONFIG_FILE" + echo "${devices[@]}" +} + +pick_terminal_devices() { + local platformName="$1" + local count=$2 + count="${count%,}" + local platformsListContentFormat="$3" + + if [[ -z "$platformName" || -z "$count" ]]; then + return 1 + fi + + # Read devices into array + local matching_devices=() + local raw_devices + + if [[ "$platformName" == "android" || "$platformName" == "ios" ]]; then + # Filter specifically for the requested platform from the MOBILE list + while IFS= read -r line; do + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + if [[ "$line" == MOBILE* ]]; then + entry="${line#MOBILE|}" + prefix="${entry%%|*}" + if [[ "$prefix" == "$platformName" ]]; then + matching_devices+=("$entry") + fi + fi + done < "$CONFIG_FILE" + else + # For Web, use everything (WEB + MOBILE) as per original logic + while IFS= read -r line; do + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + if [[ "$line" == WEB* ]]; then + matching_devices+=("${line#WEB|}") + elif [[ "$line" == MOBILE* ]]; then + matching_devices+=("${line#MOBILE|}") + fi + done < "$CONFIG_FILE" + fi + + if [ ${#matching_devices[@]} -eq 0 ]; then + return 0 + fi + + local yaml="" + local json="[" + + for ((i = 1; i <= count; i++)); do + index=$(( (i - 1) % ${#matching_devices[@]} )) + entry="${matching_devices[$index]}" + suffixEntry="${entry#*|}" + prefixEntry="${entry%%|*}" + + mod=$(( i % 4 )) + local hardcodedBVersion=140 + local bVersionLiteral="" + + if [ $((i % 4)) -ne 0 ]; then + bVersionLiteral="-$mod" + 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: $(( hardcodedBVersion-i )) +" + fi + if [[ $i -lt $count ]]; then + yaml+=$'\n' + fi + + elif [[ "$platformsListContentFormat" == "json" ]]; then + if [[ "$prefixEntry" == "android" || "$prefixEntry" == "ios" ]]; then + json+=$'{"platformName": "'"$prefixEntry"'","bstack:options":{"deviceName": "'"$suffixEntry"'"}},' + else + json+=$'{"bstack:options":{ "os": "'"$prefixEntry"'"},"browserName": "'"$suffixEntry"'","browserVersion": "'"$bVersion"'"},' + fi + fi + done + + json="${json%,}]" + + if [[ "$platformsListContentFormat" == "yaml" ]]; then + echo "$yaml" + else + echo "$json" + fi +} diff --git a/mac/env-prequisite-checks.sh b/common/mac/env-prequisite-checks.sh similarity index 69% rename from mac/env-prequisite-checks.sh rename to common/mac/env-prequisite-checks.sh index 51aa5c2..781f54d 100755 --- a/mac/env-prequisite-checks.sh +++ b/common/mac/env-prequisite-checks.sh @@ -25,7 +25,7 @@ parse_proxy() { } set_proxy_in_env() { - log_section "🌐 Network & Proxy Validation" + log_section "Network & Proxy Validation" base64_encoded_creds=$(printf "%s" "$BROWSERSTACK_USERNAME":"$BROWSERSTACK_ACCESS_KEY" | base64 | tr -d '\n') @@ -46,15 +46,15 @@ set_proxy_in_env() { 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 "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." + 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 @@ -63,15 +63,15 @@ set_proxy_in_env() { # ===== Tech Stack Validation Functions ===== check_java_installation() { - log_msg_to "🔍 Checking if 'java' command exists..." + 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." + log_msg_to "Java command not found in PATH." return 1 fi - log_msg_to "🔍 Checking if Java runs correctly..." + 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." + log_msg_to "Java exists but failed to run." return 1 fi @@ -81,15 +81,15 @@ check_java_installation() { } check_python_installation() { - log_msg_to "🔍 Checking if 'python3' command exists..." + 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." + log_msg_to "Python3 command not found in PATH." return 1 fi - log_msg_to "🔍 Checking if Python3 runs correctly..." + 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." + log_msg_to "Python3 exists but failed to run." return 1 fi @@ -98,27 +98,27 @@ check_python_installation() { } check_nodejs_installation() { - log_msg_to "🔍 Checking if 'node' command exists..." + 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." + log_msg_to "Node.js command not found in PATH." return 1 fi - log_msg_to "🔍 Checking if 'npm' command exists..." + 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." + log_msg_to "npm command not found in PATH." return 1 fi - log_msg_to "🔍 Checking if Node.js runs correctly..." + 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." + log_msg_to "Node.js exists but failed to run." return 1 fi - log_msg_to "🔍 Checking if npm runs correctly..." + 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." + log_msg_to "npm exists but failed to run." return 1 fi @@ -130,7 +130,7 @@ check_nodejs_installation() { validate_tech_stack_installed() { local tech_stack=$1 - log_section "🧩 System Prerequisites Check" + log_section "System Prerequisites Check" log_info "Checking prerequisites for $tech_stack" case "$tech_stack" in @@ -144,12 +144,12 @@ validate_tech_stack_installed() { check_nodejs_installation ;; *) - log_msg_to "❌ Unknown tech stack selected: $tech_stack" + log_msg_to "Unknown tech stack selected: $tech_stack" return 1 ;; esac - log_msg_to "✅ Prerequisites validated for $tech_stack" + log_msg_to "Prerequisites validated for $tech_stack" return 0 } diff --git a/common/mac/env-setup-run.sh b/common/mac/env-setup-run.sh new file mode 100644 index 0000000..4a73d59 --- /dev/null +++ b/common/mac/env-setup-run.sh @@ -0,0 +1,289 @@ +#!/usr/bin/env bash +# shellcheck shell=bash + +REPO_CONFIG="$(dirname "$0")/../config/repos.txt" + +get_repo_name() { + local key="$1" + local repo="" + while IFS= read -r line; do + if [[ "$line" == "$key|"* ]]; then + repo="${line#*|}" + echo "$repo" + return + fi + done < "$REPO_CONFIG" +} + +setup_environment() { + local setup_type=$1 + local tech_stack=$2 + local max_parallels + + log_section "Project Setup" + + if [ "$setup_type" = "web" ]; then + max_parallels=$TEAM_PARALLELS_MAX_ALLOWED_WEB + else + max_parallels=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE + fi + + log_msg_to "Starting ${setup_type} setup for $tech_stack" "$NOW_RUN_LOG_FILE" + + local total_parallels=$max_parallels + [ -z "$total_parallels" ] && total_parallels=1 + + local repo_key="${setup_type}_${tech_stack}" + local repo_name=$(get_repo_name "$repo_key") + + if [ -z "$repo_name" ]; then + log_error "Unknown combination: $repo_key" + return 1 + fi + + local target_dir="$WORKSPACE_DIR/$PROJECT_FOLDER/$repo_name" + clone_repository "$repo_name" "$target_dir" + + # Dispatch to specific setup function + "setup_${setup_type}_${tech_stack}" "$target_dir" "$total_parallels" + + local ret=$? + + log_section "Results" + log_info "${setup_type} setup completed with exit code: $ret" + local status=1 + #if [ $ret -eq 0 ]; then + "identify_run_status_${tech_stack}" "$NOW_RUN_LOG_FILE" + status=$? + #fi + + if [ $status -eq 0 ]; then + log_success "${setup_type} setup succeeded." + else + log_error "${setup_type} setup failed." + exit 1 + fi +} + +setup_web_java() { + local cwd=$1 + local parallels=$2 + cd "$cwd" || return 1 + + if is_domain_private; then local_flag=true; else local_flag=false; fi + report_bstack_local_status "$local_flag" + + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_web_platforms "$parallels" "yaml") + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + + + + + log_section "BrowserStack SDK Test Run Execution" + mvn test -P sample-test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +setup_app_java() { + local root_dir=$1 + local parallels=$2 + + if [[ "$APP_PLATFORM" == "all" || "$APP_PLATFORM" == "android" ]]; then + cd "$root_dir/android/testng-examples" || return 1 + else + cd "$root_dir/ios/testng-examples" || return 1 + fi + + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_mobile_platforms "$parallels" "yaml") + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 || return 1 + + log_section "BrowserStack SDK Test Run Execution" + mvn test -P sample-test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +setup_web_python() { + local cwd=$1 + local parallels=$2 + cd "$cwd" || return 1 + + detect_setup_python_env + pip3 install --only-binary grpcio -r requirements.txt >> "$NOW_RUN_LOG_FILE" 2>&1 + + export BROWSERSTACK_CONFIG_FILE="./browserstack.yml" + platform_yaml=$(generate_web_platforms "$parallels" "yaml") + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +setup_app_python() { + local cwd=$1 + local parallels=$2 + cd "$cwd" || return 1 + + detect_setup_python_env + pip install --only-binary grpcio -r requirements.txt >> "$NOW_RUN_LOG_FILE" 2>&1 + + local run_dir="android" + if [ "$APP_PLATFORM" = "ios" ]; then run_dir="ios"; fi + + export BROWSERSTACK_CONFIG_FILE="./$run_dir/browserstack.yml" + platform_yaml=$(generate_mobile_platforms "$parallels" "yaml") + cat >> "$BROWSERSTACK_CONFIG_FILE" <> "$NOW_RUN_LOG_FILE" 2>&1 + ) & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +setup_web_nodejs() { + local cwd=$1 + local parallels=$2 + cd "$cwd" || return 1 + + npm install >> "$NOW_RUN_LOG_FILE" 2>&1 + + 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; else local_flag=false; fi + export BROWSERSTACK_LOCAL=$local_flag + export BROWSERSTACK_BUILD_NAME="now-$NOW_OS-web-nodejs-wdio" + export BROWSERSTACK_PROJECT_NAME="now-$NOW_OS-web" + + print_env_vars + + log_section "BrowserStack SDK Test Run Execution" + npm run test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +setup_app_nodejs() { + local root_dir=$1 + local parallels=$2 + # App nodejs clones into root, but tests are in root/test? + # Original script: clone_repository $REPO "$TARGET_DIR" "$TEST_FOLDER" where TEST_FOLDER="/test" + # clone_repository does cd "$install_folder/$test_folder" + # So we should cd to test folder. + cd "$root_dir/test" || return 1 + + npm install >> "$NOW_RUN_LOG_FILE" 2>&1 + + caps_json=$(generate_mobile_platforms "$parallels" "json") + export BSTACK_CAPS_JSON=$caps_json + export BSTACK_PARALLELS=$parallels + export BROWSERSTACK_LOCAL=true + export BROWSERSTACK_APP=$BROWSERSTACK_APP + export BROWSERSTACK_BUILD_NAME="now-$NOW_OS-app-nodejs-wdio" + export BROWSERSTACK_PROJECT_NAME="now-$NOW_OS-app" + + print_env_vars + + log_section "BrowserStack SDK Test Run Execution" + npm run test >> "$NOW_RUN_LOG_FILE" 2>&1 & + cmd_pid=$! + show_spinner "$cmd_pid" + wait "$cmd_pid" + return $? +} + +clone_repository() { + local repo_git=$1 + local install_folder=$2 + rm -rf "$install_folder" + log_info "Cloning $repo_git..." + git clone "https://github.com/BrowserStackCE/$repo_git.git" "$install_folder" >> "$NOW_RUN_LOG_FILE" 2>&1 +} + +detect_setup_python_env() { + log_info "Setting up Python environment..." + python3 -m venv .venv + if [ -f ".venv/bin/activate" ]; then + source .venv/bin/activate + else + source .venv/Scripts/activate + fi +} + +print_env_vars() { + log_section "Validate Environment Variables and Platforms" + log_info "BrowserStack Username: $BROWSERSTACK_USERNAME" + log_info "BrowserStack Project Name: $BROWSERSTACK_PROJECT_NAME" + log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME" + if [ $TEST_TYPE == "app" ]; then + log_info "Native App Endpoint: $BROWSERSTACK_APP" + fi + log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL" + log_info "Parallels per platform: $BSTACK_PARALLELS" + log_info "Platforms: \n$BSTACK_PLATFORMS" +} diff --git a/common/mac/logging-utils.sh b/common/mac/logging-utils.sh new file mode 100644 index 0000000..078b72b --- /dev/null +++ b/common/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/common/mac/run.sh b/common/mac/run.sh new file mode 100755 index 0000000..62a23ef --- /dev/null +++ b/common/mac/run.sh @@ -0,0 +1,91 @@ +#!/bin/bash +set -o pipefail + +# 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" +} + + +# ===== Main flow (baseline steps then run) ===== + +detect_os + + +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) +CLI_TEST_URL=$4 +CLI_APP_PATH=$5 +CLI_APP_PLATFORM=$6 + +export CLI_TEST_URL +export CLI_APP_PATH +export CLI_APP_PLATFORM + +log_section "Setup Summary - BrowserStack NOW" +log_info "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')" + + +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 + 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_info "Log file path: $log_file" +export NOW_RUN_LOG_FILE="$log_file" +setup_workspace +get_browserstack_credentials "$RUN_MODE" + +log_section "Platform & Tech Stack" +log_info "Platform: ${TEST_TYPE:-N/A}" +log_info "Tech Stack: ${TECH_STACK:-N/A}" + +validate_tech_stack_installed "$TECH_STACK" +fetch_plan_details "$TEST_TYPE" + +if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then + if [[ $TEST_TYPE == "app" ]]; then + get_upload_app + log_success "Sample App uploaded successfully" + fi +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 + +log_section "Getting Ready" +log_info "Detected Operating system: $NOW_OS" +log_info "Clearing old logs fron NOW Home Directory inside .browserstack" + +clear_old_logs + +log_info "Starting $TEST_TYPE setup for $TECH_STACK" +setup_environment "$TEST_TYPE" "$TECH_STACK" + diff --git a/mac/user-interaction.sh b/common/mac/user-interaction.sh similarity index 63% rename from mac/user-interaction.sh rename to common/mac/user-interaction.sh index d671e50..d190919 100644 --- a/mac/user-interaction.sh +++ b/common/mac/user-interaction.sh @@ -1,5 +1,37 @@ #!/bin/bash +GUI_SCRIPT="$(dirname "$0")/../win/windows-gui.ps1" + +windows_input_box() { + local title="$1" + local prompt="$2" + local default="$3" + powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "InputBox" -Title "$title" -Prompt "$prompt" -DefaultText "$default" | tr -d '\r' +} + +windows_password_box() { + local title="$1" + local prompt="$2" + powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "PasswordBox" -Title "$title" -Prompt "$prompt" | tr -d '\r' +} + +windows_click_choice() { + local title="$1" + local prompt="$2" + local default="$3" + shift 3 + local choices_str + choices_str=$(printf "%s," "$@") + choices_str="${choices_str%,}" + powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "ClickChoice" -Title "$title" -Prompt "$prompt" -DefaultChoice "$default" -Choices "$choices_str" | tr -d '\r' +} + +windows_open_file_dialog() { + local title="$1" + local filter="$2" + powershell.exe -ExecutionPolicy Bypass -File "$GUI_SCRIPT" -Command "OpenFileDialog" -Title "$title" -Filter "$filter" | tr -d '\r' +} + # ===== Credential Management ===== get_browserstack_credentials() { local run_mode=$1 @@ -13,6 +45,8 @@ get_browserstack_credentials() { if [[ "$NOW_OS" == "macos" ]]; then 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') + elif [[ "$NOW_OS" == "windows" ]]; then + username=$(windows_input_box "BrowserStack Setup" "Enter your BrowserStack Username:\n\nLocate it on https://www.browserstack.com/accounts/profile/details" "") else echo "Please enter your BrowserStack Username." echo "Note: Locate it in your BrowserStack account profile page: https://www.browserstack.com/accounts/profile/details" @@ -20,13 +54,15 @@ get_browserstack_credentials() { fi if [ -z "$username" ]; then - log_msg_to "❌ Username empty" + log_msg_to "Username empty" return 1 fi if [[ "$NOW_OS" == "macos" ]]; then 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') + elif [[ "$NOW_OS" == "windows" ]]; then + access_key=$(windows_password_box "BrowserStack Setup" "Enter your BrowserStack Access Key:\n\nLocate it on https://www.browserstack.com/accounts/profile/details") else echo "Please enter your BrowserStack Access Key." echo "Note: Locate it in your BrowserStack account page: https://www.browserstack.com/accounts/profile/details" @@ -34,7 +70,7 @@ get_browserstack_credentials() { echo "" # Newline after secret input fi if [ -z "$access_key" ]; then - log_msg_to "❌ Access Key empty" + log_msg_to "Access Key empty" return 1 fi @@ -52,11 +88,15 @@ get_tech_stack() { local tech_stack="" if [[ "$run_mode" == *"--silent"* || "$run_mode" == *"--debug"* ]]; then tech_stack="$TSTACK" - log_msg_to "✅ Selected Tech Stack from environment: $tech_stack" + log_msg_to "Selected Tech Stack from environment: $tech_stack" else if [[ "$NOW_OS" == "macos" ]]; then 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') + elif [[ "$NOW_OS" == "windows" ]]; then + tech_stack=$(windows_click_choice "Tech Stack" "Select your installed language / framework:" "Java" "Java" "Python" "NodeJS") + # Convert to lowercase to match expected values + tech_stack=$(echo "$tech_stack" | tr '[:upper:]' '[:lower:]') else echo "Select installed tech stack:" select opt in "java" "python" "nodejs"; do @@ -67,7 +107,7 @@ get_tech_stack() { done fi fi - log_msg_to "✅ Selected Tech Stack: $tech_stack" + log_msg_to "Selected Tech Stack: $tech_stack" log_info "Tech Stack: $tech_stack" export TECH_STACK="$tech_stack" @@ -79,21 +119,29 @@ get_tech_stack() { get_test_url() { local test_url=$DEFAULT_TEST_URL + if [ -n "$CLI_TEST_URL" ]; then + test_url="$CLI_TEST_URL" + log_msg_to "Using custom test URL from CLI: $test_url" + else if [[ "$NOW_OS" == "macos" ]]; then 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') + elif [[ "$NOW_OS" == "windows" ]]; then + test_url=$(windows_input_box "Test URL Setup" "Enter the URL you want to test with BrowserStack:\n(Leave blank for default: $DEFAULT_TEST_URL)" "") else echo "Enter the URL you want to test with BrowserStack:" echo "(Leave blank for default: $DEFAULT_TEST_URL)" read -r test_url fi + fi + if [ -n "$test_url" ]; then - log_msg_to "🌐 Using custom test URL: $test_url" - log_info "🌐 Using custom test URL: $test_url" + 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_msg_to "No URL entered. Falling back to default: $test_url" log_info "No URL entered. Falling back to default: $test_url" fi @@ -106,11 +154,14 @@ 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" + log_msg_to "Selected Testing Type from environment: $TEST_TYPE" else if [[ "$NOW_OS" == "macos" ]]; then 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') + elif [[ "$NOW_OS" == "windows" ]]; then + test_type=$(windows_click_choice "Testing Type" "What do you want to run?" "Web" "Web" "App") + test_type=$(echo "$test_type" | tr '[:upper:]' '[:lower:]') else echo "Select testing type:" select opt in "web" "app"; do @@ -120,7 +171,7 @@ get_test_type() { esac done fi - log_msg_to "✅ Selected Testing Type: $TEST_TYPE" + log_msg_to "Selected Testing Type: $TEST_TYPE" RUN_MODE=$test_type log_info "Run Mode: ${RUN_MODE:-default}" fi diff --git a/common/win/common-utils.ps1 b/common/win/common-utils.ps1 new file mode 100644 index 0000000..119e961 --- /dev/null +++ b/common/win/common-utils.ps1 @@ -0,0 +1,519 @@ +# Common Utilities for PowerShell + +# ===== Global Variables ===== +$script:WORKSPACE_DIR = Join-Path $env:USERPROFILE ".browserstack" +$script:PROJECT_FOLDER = "NOW" + +$script:GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER +$script:LOG_DIR = Join-Path $GLOBAL_DIR "logs" +$script:GLOBAL_LOG = "" + +# Script state +$script:BROWSERSTACK_USERNAME = "" +$script:BROWSERSTACK_ACCESS_KEY = "" +$script:TEST_TYPE = "" # Web / App +$script:TECH_STACK = "" # Java / Python / JS +[double]$script:PARALLEL_PERCENTAGE = 1.00 + +$script:WEB_PLAN_FETCHED = $false +$script:MOBILE_PLAN_FETCHED = $false +[int]$script:TEAM_PARALLELS_MAX_ALLOWED_WEB = 0 +[int]$script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE = 0 + +# URL handling +$script:DEFAULT_TEST_URL = "https://bstackdemo.com" +$script:CX_TEST_URL = $DEFAULT_TEST_URL + +# App handling +$script:BROWSERSTACK_APP = "" +$script:APP_PLATFORM = "" # ios | android | all + +# Chosen Python command tokens +$script:PY_CMD = @() + +# ===== Workspace Management ===== +function Ensure-Workspace { + if (!(Test-Path $script:GLOBAL_DIR)) { + New-Item -ItemType Directory -Path $script:GLOBAL_DIR | Out-Null + Log-Line "Onboarding workspace created: $script:GLOBAL_DIR" $global:NOW_RUN_LOG_FILE + } else { + Log-Line "Onboarding workspace found at: $script:GLOBAL_DIR" $global:NOW_RUN_LOG_FILE + } +} + +function Setup-Workspace { + Log-Section "Environment & Credentials" + Ensure-Workspace +} + +function Clear-OldLogs { + if (!(Test-Path $script:LOG_DIR)) { + New-Item -ItemType Directory -Path $script:LOG_DIR | Out-Null + } + + $legacyLogs = @("global.log","web_run_result.log","mobile_run_result.log") + foreach ($legacy in $legacyLogs) { + $legacyPath = Join-Path $script:LOG_DIR $legacy + if (Test-Path $legacyPath) { + Remove-Item -Path $legacyPath -Force -ErrorAction SilentlyContinue + } + } + + Log-Line "Logs directory cleaned. Legacy files removed." $global:NOW_RUN_LOG_FILE +} + +# ===== Git Clone ===== +function Invoke-GitClone { + param( + [Parameter(Mandatory)] [string]$Url, + [Parameter(Mandatory)] [string]$Target, + [string]$Branch, + [string]$LogFile + ) + $argsList = @("clone") + if ($Branch) { $argsList += @("-b", $Branch) } + $argsList += @($Url, $Target) + + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = "git" + $psi.Arguments = ($argsList | ForEach-Object { + if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } + }) -join ' ' + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + $psi.WorkingDirectory = (Get-Location).Path + + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $psi + [void]$p.Start() + $stdout = $p.StandardOutput.ReadToEnd() + $stderr = $p.StandardError.ReadToEnd() + $p.WaitForExit() + + if ($LogFile) { + if ($stdout) { Add-Content -Path $LogFile -Value $stdout } + if ($stderr) { Add-Content -Path $LogFile -Value $stderr } + } + + if ($p.ExitCode -ne 0) { + throw "git clone failed (exit $($p.ExitCode)): $stderr" + } +} + +function Invoke-External { + param( + [Parameter(Mandatory)][string]$Exe, + [Parameter()][string[]]$Arguments = @(), + [string]$LogFile, + [string]$WorkingDirectory + ) + $psi = New-Object System.Diagnostics.ProcessStartInfo + $exeToRun = $Exe + $argLine = ($Arguments | ForEach-Object { if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } }) -join ' ' + + $ext = [System.IO.Path]::GetExtension($Exe) + if ($ext -and ($ext.ToLowerInvariant() -in @('.cmd','.bat'))) { + if (-not (Test-Path $Exe)) { throw "Command not found: $Exe" } + $psi.FileName = "cmd.exe" + $psi.Arguments = "/c `"$Exe`" $argLine" + } else { + $psi.FileName = $exeToRun + $psi.Arguments = $argLine + } + + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) { + $psi.WorkingDirectory = (Get-Location).Path + } else { + $psi.WorkingDirectory = $WorkingDirectory + } + + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $psi + + if ($LogFile) { + $logDir = Split-Path -Parent $LogFile + if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } + + # Synchronous read is safer for simple logging to avoid deadlock if buffer fills, + # but async is better for real-time. We'll use async events. + + $outputHandler = { + if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { + Add-Content -Path $Event.MessageData -Value $EventArgs.Data -Encoding UTF8 + } + } + + $stdoutEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $outputHandler -MessageData $LogFile + $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $outputHandler -MessageData $LogFile + + [void]$p.Start() + $p.BeginOutputReadLine() + $p.BeginErrorReadLine() + $p.WaitForExit() + + Unregister-Event -SourceIdentifier $stdoutEvent.Name + Unregister-Event -SourceIdentifier $stderrEvent.Name + Remove-Job -Id $stdoutEvent.Id -Force + Remove-Job -Id $stderrEvent.Id -Force + } else { + [void]$p.Start() + $p.WaitForExit() + } + + return $p.ExitCode +} + +function Get-MavenCommand { + param([Parameter(Mandatory)][string]$RepoDir) + $mvnCmd = Get-Command mvn -ErrorAction SilentlyContinue + if ($mvnCmd) { return $mvnCmd.Source } + $wrapper = Join-Path $RepoDir "mvnw.cmd" + if (Test-Path $wrapper) { return $wrapper } + throw "Maven not found in PATH and 'mvnw.cmd' not present under $RepoDir. Install Maven or ensure the wrapper exists." +} + +function Get-VenvPython { + param([Parameter(Mandatory)][string]$VenvDir) + $py = Join-Path $VenvDir "Scripts\python.exe" + if (Test-Path $py) { return $py } + throw "Python interpreter not found in venv: $VenvDir" +} + +function Set-PythonCmd { + $candidates = @( + @("python3"), + @("python"), + @("py","-3"), + @("py") + ) + foreach ($cand in $candidates) { + try { + $exe = $cand[0] + $argsList = @() + if ($cand.Length -gt 1) { $argsList = $cand[1..($cand.Length-1)] } + $code = Invoke-External -Exe $exe -Arguments ($argsList + @("--version")) -LogFile $null + if ($code -eq 0) { + $script:PY_CMD = $cand + return + } + } catch {} + } + throw "Python not found via python3/python/py. Please install Python 3 and ensure it's on PATH." +} + +function Invoke-Py { + param( + [Parameter(Mandatory)][string[]]$Arguments, + [string]$LogFile, + [string]$WorkingDirectory + ) + if (-not $script:PY_CMD -or $script:PY_CMD.Count -eq 0) { Set-PythonCmd } + $exe = $script:PY_CMD[0] + $baseArgs = @() + if ($script:PY_CMD.Count -gt 1) { $baseArgs = $script:PY_CMD[1..($script:PY_CMD.Count-1)] } + return (Invoke-External -Exe $exe -Arguments ($baseArgs + $Arguments) -LogFile $LogFile -WorkingDirectory $WorkingDirectory) +} + +function Show-Spinner { + param([Parameter(Mandatory)][System.Diagnostics.Process]$Process) + $spin = @('|','/','-','\') + $i = 0 + while (!$Process.HasExited) { + Write-Host "`rProcessing... $($spin[$i])" -NoNewline -ForegroundColor Cyan + $i = ($i + 1) % 4 + Start-Sleep -Milliseconds 100 + } + Write-Host "" + Log-Info "Run Test command completed." + Start-Sleep -Seconds 2 +} + +function Test-PrivateIP { + param([string]$IP) + if ([string]::IsNullOrWhiteSpace($IP)) { return $false } + $parts = $IP.Split('.') + if ($parts.Count -ne 4) { return $false } + $first = [int]$parts[0] + $second = [int]$parts[1] + if ($first -eq 10) { return $true } + if ($first -eq 192 -and $second -eq 168) { return $true } + if ($first -eq 172 -and $second -ge 16 -and $second -le 31) { return $true } + return $false +} + +function Test-DomainPrivate { + $domain = $script:CX_TEST_URL -replace '^https?://', '' -replace '/.*$', '' + Log-Line "Website domain: $domain" $global:NOW_RUN_LOG_FILE + $env:NOW_WEB_DOMAIN = $script:CX_TEST_URL + + $IP_ADDRESS = "" + try { + $dnsResult = Resolve-DnsName -Name $domain -Type A -ErrorAction Stop | Where-Object { $_.Type -eq 'A' } | Select-Object -First 1 + if ($dnsResult) { + $IP_ADDRESS = $dnsResult.IPAddress + } + } catch { + try { + $nslookupOutput = nslookup $domain 2>&1 | Out-String + if ($nslookupOutput -match '(?:Address|Addresses):\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') { + $IP_ADDRESS = $matches[1] + } + } catch { + Log-Error "Failed to resolve domain: $domain (assuming public domain)" $global:NOW_RUN_LOG_FILE + $IP_ADDRESS = "" + } + } + + if ([string]::IsNullOrWhiteSpace($IP_ADDRESS)) { + Log-Warn "DNS resolution failed for: $domain (treating as public domain, BrowserStack Local will be DISABLED)" $global:NOW_RUN_LOG_FILE + } else { + Log-Info "Resolved IP: $IP_ADDRESS" $global:NOW_RUN_LOG_FILE + } + + return (Test-PrivateIP -IP $IP_ADDRESS) +} + +function Report-BStack-Local-Status { + param([bool]$LocalFlag) + if ($LocalFlag) { + Log-Info "BrowserStack Local: ENABLED" + } else { + Log-Info "BrowserStack Local: DISABLED" + } +} + +function Get-BasicAuthHeader { + param([string]$User, [string]$Key) + $pair = "{0}:{1}" -f $User,$Key + $bytes = [System.Text.Encoding]::UTF8.GetBytes($pair) + "Basic {0}" -f [System.Convert]::ToBase64String($bytes) +} + +function Fetch-Plan-Details { + param([string]$TestType) + + if ([string]::IsNullOrWhiteSpace($TestType)) { + throw "Test type is required to fetch plan details." + } + + $normalized = $TestType.ToLowerInvariant() + Log-Section "Account & Plan Details" + Log-Info "Fetching BrowserStack plan for $normalized" + + $auth = Get-BasicAuthHeader -User $script:BROWSERSTACK_USERNAME -Key $script:BROWSERSTACK_ACCESS_KEY + $headers = @{ Authorization = $auth } + + switch ($normalized) { + "web" { + try { + $resp = Invoke-RestMethod -Method Get -Uri "https://api.browserstack.com/automate/plan.json" -Headers $headers + $script:WEB_PLAN_FETCHED = $true + $script:TEAM_PARALLELS_MAX_ALLOWED_WEB = [int]$resp.parallel_sessions_max_allowed + Log-Success "Web Testing Plan fetched: Team max parallel sessions = $script:TEAM_PARALLELS_MAX_ALLOWED_WEB" $global:NOW_RUN_LOG_FILE + } catch { + Log-Error "Web Testing Plan fetch failed ($($_.Exception.Message))" $global:NOW_RUN_LOG_FILE + } + if (-not $script:WEB_PLAN_FETCHED) { + throw "Unable to fetch Web Testing plan details." + } + } + "app" { + try { + $resp2 = Invoke-RestMethod -Method Get -Uri "https://api-cloud.browserstack.com/app-automate/plan.json" -Headers $headers + $script:MOBILE_PLAN_FETCHED = $true + $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE = [int]$resp2.parallel_sessions_max_allowed + Log-Line "Mobile App Testing Plan fetched: Team max parallel sessions = $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE" $global:NOW_RUN_LOG_FILE + } catch { + Log-Line "Mobile App Testing Plan fetch failed ($($_.Exception.Message))" $global:NOW_RUN_LOG_FILE + } + if (-not $script:MOBILE_PLAN_FETCHED) { + throw "Unable to fetch Mobile App Testing plan details." + } + } + default { + throw "Unsupported TEST_TYPE: $TestType. Allowed values: Web, App." + } + } + + if ($RunMode -match "--silent|--debug") { + $script:TEAM_PARALLELS_MAX_ALLOWED_WEB = 5 + $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE = 5 + Log-Line "Silent mode: Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" $global:NOW_RUN_LOG_FILE + } else { + Log-Line "Plan summary: Web fetched=$script:WEB_PLAN_FETCHED (team max=$script:TEAM_PARALLELS_MAX_ALLOWED_WEB), Mobile fetched=$script:MOBILE_PLAN_FETCHED (team max=$script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE)" $global:NOW_RUN_LOG_FILE + } + +} + +function Invoke-SampleAppUpload { + Log-Line "Uploading sample app to BrowserStack..." $global:NOW_RUN_LOG_FILE + $headers = @{ + Authorization = (Get-BasicAuthHeader -User $script:BROWSERSTACK_USERNAME -Key $script:BROWSERSTACK_ACCESS_KEY) + } + $body = @{ + url = "https://www.browserstack.com/app-automate/sample-apps/android/WikipediaSample.apk" + } + try { + $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -ContentType "application/x-www-form-urlencoded" -Body $body + $url = $resp.app_url + if ([string]::IsNullOrWhiteSpace($url)) { + throw "Sample app upload failed (empty URL)" + } + Log-Success "App uploaded successfully: $url" $global:NOW_RUN_LOG_FILE + return @{ + Url = $url + Platform = "android" + } + } catch { + Log-Error "Upload failed: $($_.Exception.Message)" $global:NOW_RUN_LOG_FILE + throw + } +} + +function Invoke-CustomAppUpload { + param( + [Parameter(Mandatory)][string]$FilePath + ) + + $ext = [System.IO.Path]::GetExtension($FilePath).ToLowerInvariant() + switch ($ext) { + ".apk" { $platform = "android" } + ".ipa" { $platform = "ios" } + default { throw "Unsupported app file (only .apk/.ipa)" } + } + + Log-Line "Uploading app to BrowserStack..." $global:NOW_RUN_LOG_FILE + + $boundary = [System.Guid]::NewGuid().ToString() + $LF = "`r`n" + $fileBin = [System.IO.File]::ReadAllBytes($FilePath) + $fileName = [System.IO.Path]::GetFileName($FilePath) + + $bodyLines = ( + "--$boundary", + "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", + "Content-Type: application/octet-stream$LF", + [System.Text.Encoding]::GetEncoding("iso-8859-1").GetString($fileBin), + "--$boundary--$LF" + ) -join $LF + + $headers = @{ + Authorization = (Get-BasicAuthHeader -User $script:BROWSERSTACK_USERNAME -Key $script:BROWSERSTACK_ACCESS_KEY) + "Content-Type" = "multipart/form-data; boundary=$boundary" + } + + try { + $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -Body $bodyLines + $url = $resp.app_url + if ([string]::IsNullOrWhiteSpace($url)) { + throw "Upload failed (empty URL)" + } + Log-Line "App uploaded successfully: $url" $global:NOW_RUN_LOG_FILE + return @{ + Url = $url + Platform = $platform + } + } catch { + Log-Error "Upload failed: $($_.Exception.Message)" $global:NOW_RUN_LOG_FILE + throw + } +} + +function Identify-Run-Status-Java { + param([string]$LogFile) + + $content = Get-Content -Path $LogFile -Raw -ErrorAction SilentlyContinue + if (-not $content) { + Log-Warn "No test summary line found." + return $false + } + + # Regex for "Tests run: 1, Failures: 0, Errors: 0, Skipped: 0" + if ($content -match "Tests run: (\d+), Failures: (\d+), Errors: (\d+), Skipped: (\d+)") { + $testsRun = [int]$matches[1] + $failures = [int]$matches[2] + $errors = [int]$matches[3] + $skipped = [int]$matches[4] + + $passed = $testsRun - ($failures + $errors + $skipped) + if ($passed -gt 0) { + Log-Success "Success: $passed test(s) passed." + return $true + } else { + Log-Error "Error: No tests passed (Tests run: $testsRun, Failures: $failures, Errors: $errors, Skipped: $skipped)" + return $false + } + } + + Log-Warn "No test summary line found." + return $false +} + +function Identify-Run-Status-Python { + param([string]$LogFile) + + $content = Get-Content -Path $LogFile -Raw -ErrorAction SilentlyContinue + if (-not $content) { + Log-Warn "No test summary line found." + return $false + } + + # Regex for "X passed" + $matches = [regex]::Matches($content, '(\d+) passed') + $passedSum = 0 + foreach ($m in $matches) { + $passedSum += [int]$m.Groups[1].Value + } + + Write-Host "Total Passed: $passedSum" -ForegroundColor Green + + if ($passedSum -gt 0) { + Log-Success "Success: $passedSum test(s) completed" + return $true + } else { + Log-Error "Error: No tests completed" + return $false + } +} + +function Identify-Run-Status-NodeJS { + param([string]$LogFile) + + $content = Get-Content -Path $LogFile -Raw -ErrorAction SilentlyContinue + if (-not $content) { + Log-Warn "No test summary line found." + return $false + } + + # Regex for "Spec Files:.*passed.*total" + if ($content -match "Spec Files:.*passed.*total") { + # Extract passed count + if ($content -match '(\d+) passed') { + $passed = [int]$matches[1] + if ($passed -gt 0) { + Log-Success "Success: $passed test(s) passed" + return $true + } + } + } + + Log-Error "Error: No tests passed" + return $false +} + +# ===== Dynamic config generators ===== +function Generate-Web-Platforms { + param($MaxTotalParallels, $Format) + return Pick-Terminal-Devices -PlatformName "web" -Count $MaxTotalParallels -PlatformsListContentFormat $Format +} + +function Generate-Mobile-Platforms { + param($MaxTotalParallels, $Format) + return Pick-Terminal-Devices -PlatformName $script:APP_PLATFORM -Count $MaxTotalParallels -PlatformsListContentFormat $Format +} \ No newline at end of file diff --git a/common/win/device-machine-allocation.ps1 b/common/win/device-machine-allocation.ps1 new file mode 100644 index 0000000..276861c --- /dev/null +++ b/common/win/device-machine-allocation.ps1 @@ -0,0 +1,87 @@ +$script:CONFIG_FILE = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config\devices.txt" + +function Get-Matching-Devices { + param([string]$PlatformName) + + $devices = @() + if (-not (Test-Path $script:CONFIG_FILE)) { return $devices } + + $lines = Get-Content $script:CONFIG_FILE + + if ($PlatformName -eq "android" -or $PlatformName -eq "ios") { + foreach ($line in $lines) { + if ($line -match "^MOBILE\|") { + $entry = $line -replace "^MOBILE\|", "" + $prefix = ($entry -split '\|')[0] + if ($prefix -eq $PlatformName) { + $devices += $entry + } + } + } + } else { + foreach ($line in $lines) { + if ($line -match "^WEB\|") { + $devices += ($line -replace "^WEB\|", "") + } elseif ($line -match "^MOBILE\|") { + $devices += ($line -replace "^MOBILE\|", "") + } + } + } + return $devices +} + +function Pick-Terminal-Devices { + param( + [string]$PlatformName, + [string]$Count, + [string]$PlatformsListContentFormat + ) + + $Count = $Count -replace ',$', '' + if (-not ($Count -match '^\d+$')) { return "" } + + $matchingDevices = Get-Matching-Devices -PlatformName $PlatformName + if ($matchingDevices.Count -eq 0) { return "" } + + $yaml = "" + $jsonList = @() + $countInt = [int]$Count + + for ($i = 1; $i -le $countInt; $i++) { + $index = ($i - 1) % $matchingDevices.Count + $entry = $matchingDevices[$index] + $parts = $entry -split '\|' + $prefixEntry = $parts[0] + $suffixEntry = $parts[1] + + $mod = $i % 4 + $hardcodedBVersion = 140 + $bVersionLiteral = "" + if (($i % 4) -ne 0) { $bVersionLiteral = "-$mod" } + $bVersion = "latest$bVersionLiteral" + + if ($PlatformsListContentFormat -eq "yaml") { + if ($prefixEntry -eq "android" -or $prefixEntry -eq "ios") { + $yaml += " - platformName: $prefixEntry`n deviceName: $suffixEntry`n" + } else { + $browserVer = $hardcodedBVersion - $i + $yaml += " - osVersion: $prefixEntry`n browserName: $suffixEntry`n browserVersion: $browserVer`n" + } + if ($i -lt $countInt) { $yaml += "`n" } + } elseif ($PlatformsListContentFormat -eq "json") { + if ($prefixEntry -eq "android" -or $prefixEntry -eq "ios") { + $obj = @{ platformName = $prefixEntry; "bstack:options" = @{ deviceName = $suffixEntry } } + $jsonList += $obj + } else { + $obj = @{ "bstack:options" = @{ os = $prefixEntry }; browserName = $suffixEntry; browserVersion = $bVersion } + $jsonList += $obj + } + } + } + + if ($PlatformsListContentFormat -eq "yaml") { + return $yaml + } else { + return ($jsonList | ConvertTo-Json -Depth 5 -Compress) + } +} \ No newline at end of file diff --git a/common/win/env-prequisite-checks.ps1 b/common/win/env-prequisite-checks.ps1 new file mode 100644 index 0000000..e77599f --- /dev/null +++ b/common/win/env-prequisite-checks.ps1 @@ -0,0 +1,155 @@ +# Environment Prerequisite Checks for PowerShell + +$script:PROXY_TEST_URL = "https://www.browserstack.com/automate/browsers.json" +$script:PROXY_HOST = "" +$script:PROXY_PORT = "" + +function Parse-Proxy { + param([string]$ProxyUrl) + # Strip protocol + $p = $ProxyUrl -replace '^https?://', '' + # Strip credentials + $p = $p -replace '^.*@', '' + + if ($p -match '^([^:]+):(\d+)$') { + $script:PROXY_HOST = $matches[1] + $script:PROXY_PORT = $matches[2] + } +} + +function Set-ProxyInEnv { + Log-Section "Network & Proxy Validation" + + # Detect proxy from env + $proxy = $env:http_proxy + if (-not $proxy) { $proxy = $env:HTTP_PROXY } + if (-not $proxy) { $proxy = $env:https_proxy } + if (-not $proxy) { $proxy = $env:HTTPS_PROXY } + + if ([string]::IsNullOrWhiteSpace($proxy)) { + Log-Warn "No proxy found. Using direct connection." + $script:PROXY_HOST = "" + $script:PROXY_PORT = "" + return + } + + Log-Line "Proxy detected: $proxy" $global:NOW_RUN_LOG_FILE + Parse-Proxy -ProxyUrl $proxy + + Log-Line "Testing reachability via proxy..." $global:NOW_RUN_LOG_FILE + + $auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${env:BROWSERSTACK_USERNAME}:${env:BROWSERSTACK_ACCESS_KEY}")) + + try { + $resp = Invoke-WebRequest -Uri $PROXY_TEST_URL -Proxy $proxy -Headers @{ Authorization = "Basic $auth" } -Method Head -ErrorAction Stop + $statusCode = $resp.StatusCode + Log-Line "Endpoint reachable. HTTP $statusCode" $global:NOW_RUN_LOG_FILE + Log-Line "Exporting PROXY_HOST=$script:PROXY_HOST" $global:NOW_RUN_LOG_FILE + Log-Line "Exporting PROXY_PORT=$script:PROXY_PORT" $global:NOW_RUN_LOG_FILE + + $env:PROXY_HOST = $script:PROXY_HOST + $env:PROXY_PORT = $script:PROXY_PORT + Log-Success "Connected to BrowserStack from proxy: $script:PROXY_HOST:$script:PROXY_PORT" + } catch { + Log-Error "Could not connect to BrowserStack using proxy. Using direct connection." + Log-Warn "Not reachable ($($_.Exception.Message)). Clearing variables." $global:NOW_RUN_LOG_FILE + $script:PROXY_HOST = "" + $script:PROXY_PORT = "" + $env:PROXY_HOST = "" + $env:PROXY_PORT = "" + } +} + +function Check-Java-Installation { + Log-Line "Checking if 'java' command exists..." $global:NOW_RUN_LOG_FILE + if (-not (Get-Command java -ErrorAction SilentlyContinue)) { + Log-Error "Java command not found in PATH." $global:NOW_RUN_LOG_FILE + return $false + } + + Log-Line "Checking if Java runs correctly..." $global:NOW_RUN_LOG_FILE + try { + $output = & cmd /c 'java -version 2>&1' | Out-String + Log-Success "Java installed and functional" + Log-Success "$output" + return $true + } catch { + Log-Error "Java exists but failed to run." $global:NOW_RUN_LOG_FILE + return $false + } +} + +function Check-Python-Installation { + Log-Line "Checking if 'python' command exists..." $global:NOW_RUN_LOG_FILE + # Windows usually uses 'python', not 'python3' + $pyCmd = Get-Command python -ErrorAction SilentlyContinue + if (-not $pyCmd) { + $pyCmd = Get-Command python3 -ErrorAction SilentlyContinue + } + + if (-not $pyCmd) { + Log-Error "Python command not found in PATH." $global:NOW_RUN_LOG_FILE + return $false + } + + Log-Line "Checking if Python runs correctly..." $global:NOW_RUN_LOG_FILE + try { + $output = & $pyCmd.Name --version 2>&1 | Out-String + Log-Success "Python default installation: $output" + return $true + } catch { + Log-Error "Python exists but failed to run." $global:NOW_RUN_LOG_FILE + return $false + } +} + +function Check-NodeJS-Installation { + Log-Line "Checking if 'node' command exists..." $global:NOW_RUN_LOG_FILE + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Log-Error "Node.js command not found in PATH." $global:NOW_RUN_LOG_FILE + return $false + } + + Log-Line "Checking if 'npm' command exists..." $global:NOW_RUN_LOG_FILE + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + Log-Error "npm command not found in PATH." $global:NOW_RUN_LOG_FILE + return $false + } + + Log-Line "Checking if Node.js runs correctly..." $global:NOW_RUN_LOG_FILE + try { + $nodeVer = node -v 2>&1 | Out-String + $npmVer = npm -v 2>&1 | Out-String + Log-Success "Node.js installed: $nodeVer" + Log-Success "npm installed: $npmVer" + return $true + } catch { + Log-Error "Node.js/npm exists but failed to run." $global:NOW_RUN_LOG_FILE + return $false + } +} + +function Validate-Tech-Stack { + param([string]$TechStack) + + Log-Section "System Prerequisites Check" + Log-Info "Checking prerequisites for $TechStack" + + $valid = $false + switch ($TechStack.ToLower()) { + "java" { $valid = Check-Java-Installation } + "python" { $valid = Check-Python-Installation } + "nodejs" { $valid = Check-NodeJS-Installation } + default { + Log-Error "Unknown tech stack selected: $TechStack" $global:NOW_RUN_LOG_FILE + return $false + } + } + + if ($valid) { + Log-Line "Prerequisites validated for $TechStack" $global:NOW_RUN_LOG_FILE + return $true + } else { + return $false + } +} diff --git a/common/win/env-setup-run.ps1 b/common/win/env-setup-run.ps1 new file mode 100644 index 0000000..8ffa4f4 --- /dev/null +++ b/common/win/env-setup-run.ps1 @@ -0,0 +1,282 @@ +# Environment Setup and Run for PowerShell + +$script:REPO_CONFIG = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config\repos.txt" + +function Get-Repo-Name { + param([string]$Key) + if (-not (Test-Path $script:REPO_CONFIG)) { return "" } + $lines = Get-Content $script:REPO_CONFIG + foreach ($line in $lines) { + if ($line.StartsWith("$Key|")) { + return ($line -split '\|')[1] + } + } + return "" +} + +function Setup-Environment { + param( + [string]$SetupType, + [string]$TechStack + ) + + Log-Section "Project Setup" + + $maxParallels = 0 + if ($SetupType -eq "web") { + $maxParallels = $script:TEAM_PARALLELS_MAX_ALLOWED_WEB + } else { + $maxParallels = $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE + } + + Log-Line "Starting ${SetupType} setup for $TechStack" $global:NOW_RUN_LOG_FILE + + $totalParallels = $maxParallels + if (-not $totalParallels -or $totalParallels -lt 1) { $totalParallels = 1 } + + $repoKey = "${SetupType}_${TechStack}" + $repoName = Get-Repo-Name -Key $repoKey + + if ([string]::IsNullOrWhiteSpace($repoName)) { + Log-Error "Unknown combination: $repoKey" + return + } + + $targetDir = Join-Path $script:GLOBAL_DIR $repoName + + # Clone + Clone-Repository -RepoGit $repoName -InstallFolder $targetDir -TestFolder "" + + $result = $false + switch ($repoKey) { + "web_java" { $result = Setup-Web-Java -TargetDir $targetDir -Parallels $totalParallels } + "app_java" { $result = Setup-App-Java -TargetDir $targetDir -Parallels $totalParallels } + "web_python" { $result = Setup-Web-Python -TargetDir $targetDir -Parallels $totalParallels } + "app_python" { $result = Setup-App-Python -TargetDir $targetDir -Parallels $totalParallels } + "web_nodejs" { $result = Setup-Web-NodeJS -TargetDir $targetDir -Parallels $totalParallels } + "app_nodejs" { $result = Setup-App-NodeJS -TargetDir $targetDir -Parallels $totalParallels } + } + + Log-Section "Results" + + Log-Info "${SetupType} setup completed with exit code: $result" + + # Identify run status + $status = $false + switch ($TechStack) { + "java" { $status = Identify-Run-Status-Java -LogFile $global:NOW_RUN_LOG_FILE } + "python" { $status = Identify-Run-Status-Python -LogFile $global:NOW_RUN_LOG_FILE } + "nodejs" { $status = Identify-Run-Status-NodeJS -LogFile $global:NOW_RUN_LOG_FILE } + } + + if ($status -and $result) { + Log-Success "${SetupType} setup succeeded." + } else { + Log-Error "Setup failed. Check logs for details." + exit 1 + } +} + +function Clone-Repository { + param($RepoGit, $InstallFolder, $TestFolder, $GitBranch) + if (Test-Path $InstallFolder) { Remove-Item -Path $InstallFolder -Recurse -Force -ErrorAction SilentlyContinue } + Log-Info "Cloning repository: $RepoGit" + $url = "https://github.com/BrowserStackCE/${RepoGit}.git" + Invoke-GitClone -Url $url -Target $InstallFolder -Branch $GitBranch -LogFile $global:NOW_RUN_LOG_FILE +} + +function Setup-Web-Java { + param($TargetDir, $Parallels) + Set-Location $TargetDir + + if (Test-DomainPrivate) { $LocalFlag = $true } else { $LocalFlag = $false } + Report-BStack-Local-Status $LocalFlag + + $configFile = Join-Path $TargetDir "browserstack.yml" + $platformYaml = Generate-Web-Platforms -MaxTotalParallels $script:TEAM_PARALLELS_MAX_ALLOWED_WEB -Format "yaml" + Add-Content -Path $configFile -Value "`nplatforms:`n$platformYaml" -Encoding UTF8 + + $env:BSTACK_PARALLELS = $Parallels + $env:BSTACK_PLATFORMS = $platformYaml + $env:BROWSERSTACK_LOCAL = $LocalFlag.ToString().ToLower() + $env:BROWSERSTACK_BUILD_NAME = "now-windows-web-java-testng" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-web" + + Log-Info "Installing dependencies" + $mvn = Get-MavenCommand -RepoDir $TargetDir + Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $global:NOW_RUN_LOG_FILE + + Print-Env-Variables + + Log-Section "BrowserStack SDK Test Run Execution" + $p = Start-Process -FilePath $mvn -ArgumentList "test","-P","sample-test" -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Setup-App-Java { + param($TargetDir, $Parallels) + + if ($script:APP_PLATFORM -eq "all" -or $script:APP_PLATFORM -eq "android") { + Set-Location (Join-Path $TargetDir "android/testng-examples") + } else { + Set-Location (Join-Path $TargetDir "ios/testng-examples") + } + + $configFile = Join-Path (Get-Location) "browserstack.yml" + $platformYaml = Generate-Mobile-Platforms -MaxTotalParallels $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE -Format "yaml" + Add-Content -Path $configFile -Value "`napp: $script:BROWSERSTACK_APP`nplatforms:`n$platformYaml" -Encoding UTF8 + + $env:BSTACK_PARALLELS = $Parallels + $env:BROWSERSTACK_LOCAL = "true" + $env:BSTACK_PLATFORMS = $platformYaml + $env:BROWSERSTACK_BUILD_NAME = "now-windows-app-java-testng" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-app" + + Print-Env-Variables + + Log-Info "Installing dependencies" + $mvn = Get-MavenCommand -RepoDir (Get-Location).Path + Invoke-External -Exe $mvn -Arguments @("clean") -LogFile $global:NOW_RUN_LOG_FILE + + Log-Section "BrowserStack SDK Test Run Execution" + $p = Start-Process -FilePath $mvn -ArgumentList "test","-P","sample-test" -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Setup-Web-Python { + param($TargetDir, $Parallels) + Set-Location $TargetDir + Detect-Setup-Python-Env + + $pyExe = $script:PY_CMD[0] + Log-Line "ℹ️ Installing dependencies" $global:NOW_RUN_LOG_FILE + [void](Invoke-External -Exe $pyExe -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $global:NOW_RUN_LOG_FILE -WorkingDirectory $TargetDir) + Log-Line "✅ Dependencies installed" $global:NOW_RUN_LOG_FILE + + $configFile = Join-Path $TargetDir "browserstack.yml" + $platformYaml = Generate-Web-Platforms -MaxTotalParallels $script:TEAM_PARALLELS_MAX_ALLOWED_WEB -Format "yaml" + Add-Content -Path $configFile -Value "`nplatforms:`n$platformYaml" -Encoding UTF8 + + if (Test-DomainPrivate) { $LocalFlag = $true } else { $LocalFlag = $false } + $env:BSTACK_PARALLELS = 1 + $env:BROWSERSTACK_LOCAL = $LocalFlag.ToString().ToLower() + $env:BSTACK_PLATFORMS = $platformYaml + $env:BROWSERSTACK_BUILD_NAME = "now-windows-web-python-pytest" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-web" + + Print-Env-Variables + + Log-Section "BrowserStack SDK Test Run Execution" + $sdkExe = Join-Path $TargetDir ".venv\Scripts\browserstack-sdk.exe" + $p = Start-Process -FilePath $sdkExe -ArgumentList "pytest","-s","tests/" -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Setup-App-Python { + param($TargetDir, $Parallels) + Set-Location $TargetDir + Detect-Setup-Python-Env + $pyExe = $script:PY_CMD[0] + Log-Line "ℹ️ Installing dependencies" $global:NOW_RUN_LOG_FILE + [void](Invoke-External -Exe $pyExe -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $global:NOW_RUN_LOG_FILE -WorkingDirectory $TargetDir) + Log-Line "✅ Dependencies installed" $global:NOW_RUN_LOG_FILE + + $runDir = "android" + if ($script:APP_PLATFORM -eq "ios") { $runDir = "ios" } + + $configFile = Join-Path $TargetDir "$runDir\browserstack.yml" + $platformYaml = Generate-Mobile-Platforms -MaxTotalParallels $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE -Format "yaml" + Add-Content -Path $configFile -Value "`nplatforms:`n$platformYaml" -Encoding UTF8 + + $env:BSTACK_PARALLELS = 1 + $env:BROWSERSTACK_LOCAL = "true" + $env:BSTACK_PLATFORMS = $platformYaml + $env:BROWSERSTACK_BUILD_NAME = "now-windows-app-python-pytest" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-app" + + Print-Env-Variables + + Log-Section "BrowserStack SDK Test Run Execution" + Set-Location $runDir + $sdkExe = Join-Path $TargetDir ".venv\Scripts\browserstack-sdk.exe" + $p = Start-Process -FilePath $sdkExe -ArgumentList "pytest","-s","bstack_sample.py" -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Setup-Web-NodeJS { + param($TargetDir, $Parallels) + Set-Location $TargetDir + Invoke-External -Exe "npm" -Arguments @("install") -LogFile $global:NOW_RUN_LOG_FILE + + $capsJson = Generate-Web-Platforms -MaxTotalParallels $Parallels -Format "json" + $env:BSTACK_CAPS_JSON = $capsJson + $env:BSTACK_PARALLELS = $Parallels + + if (Test-DomainPrivate) { $LocalFlag = $true } else { $LocalFlag = $false } + $env:BROWSERSTACK_LOCAL = $LocalFlag.ToString().ToLower() + $env:BROWSERSTACK_BUILD_NAME = "now-windows-web-nodejs-wdio" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-web" + + Print-Env-Variables + + Log-Section "BrowserStack SDK Test Run Execution" + $npmCmd = Get-Command npm -ErrorAction SilentlyContinue + if ($npmCmd.Source.EndsWith(".cmd")) { $exe = "cmd.exe"; $args = @("/c", "npm", "run", "test") } else { $exe = "npm"; $args = @("run", "test") } + $p = Start-Process -FilePath $exe -ArgumentList $args -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Setup-App-NodeJS { + param($TargetDir, $Parallels) + # App nodejs: clone to target, test in target/test + Set-Location (Join-Path $TargetDir "test") + + Invoke-External -Exe "npm" -Arguments @("install") -LogFile $global:NOW_RUN_LOG_FILE + + $capsJson = Generate-Mobile-Platforms -MaxTotalParallels $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE -Format "json" + $env:BSTACK_CAPS_JSON = $capsJson + $env:BSTACK_PARALLELS = $Parallels + $env:BROWSERSTACK_LOCAL = "true" + $env:BROWSERSTACK_APP = $script:BROWSERSTACK_APP + $env:BROWSERSTACK_BUILD_NAME = "now-windows-app-nodejs-wdio" + $env:BROWSERSTACK_PROJECT_NAME = "now-windows-app" + + Print-Env-Variables + + Log-Section "BrowserStack SDK Test Run Execution" + $npmCmd = Get-Command npm -ErrorAction SilentlyContinue + if ($npmCmd.Source.EndsWith(".cmd")) { $exe = "cmd.exe"; $args = @("/c", "npm", "run", "test") } else { $exe = "npm"; $args = @("run", "test") } + $p = Start-Process -FilePath $exe -ArgumentList $args -RedirectStandardOutput $global:NOW_RUN_LOG_FILE -RedirectStandardError "$global:NOW_RUN_LOG_FILE_ERR" -PassThru -NoNewWindow + Show-Spinner -Process $p + $p.WaitForExit() + return ($p.ExitCode -eq 0) +} + +function Detect-Setup-Python-Env { + Log-Info "Detecting latest Python environment" + Set-PythonCmd + $pyExe = $script:PY_CMD[0] + Invoke-External -Exe $pyExe -Arguments @("-m","venv",".venv") -LogFile $global:NOW_RUN_LOG_FILE +} + + +function Print-Env-Variables { + Log-Section "Validate Environment Variables and Platforms" + Log-Info "BrowserStack Username: $env:BROWSERSTACK_USERNAME" + Log-Info "BrowserStack Project Name: $env:BROWSERSTACK_PROJECT_NAME" + Log-Info "BrowserStack Build: $env:BROWSERSTACK_BUILD_NAME" + if ($TEST_TYPE -eq "app") { Log-Info "Native App Endpoint: $env:BROWSERSTACK_APP" } + Log-Info "BrowserStack Local Flag: $env:BROWSERSTACK_LOCAL" + Log-Info "Parallels per platform: $env:BSTACK_PARALLELS" + Log-Info "Platforms: $env:BSTACK_PLATFORMS" +} diff --git a/common/win/logging-utils.ps1 b/common/win/logging-utils.ps1 new file mode 100644 index 0000000..3b68bf7 --- /dev/null +++ b/common/win/logging-utils.ps1 @@ -0,0 +1,51 @@ +# Logging Helpers for PowerShell + +# ============================================== +# COLOR & STYLE DEFINITIONS +# ============================================== +# PowerShell uses Write-Host -ForegroundColor for colors. +# We will define helper functions instead of raw escape codes for better compatibility. + +function Log-Section { + param([string]$Message) + Write-Host "" + Write-Host "-----------------------------------------------" -ForegroundColor Cyan + Write-Host $Message -ForegroundColor White + Write-Host "-----------------------------------------------" -ForegroundColor Cyan +} + +function Log-Info { + param([string]$Message) + Write-Host "$Message" -ForegroundColor Gray +} + +function Log-Success { + param([string]$Message) + Write-Host "$Message" -ForegroundColor Green +} + +function Log-Warn { + param([string]$Message) + Write-Host "$Message" -ForegroundColor Yellow +} + +function Log-Error { + param([string]$Message) + Write-Host "$Message" -ForegroundColor Red +} + +function Log-Line { + param( + [string]$Message, + [string]$LogFile + ) + + $line = "$Message" + Write-Host $line + if ($LogFile) { + $dir = Split-Path -Parent $LogFile + if ($dir -and !(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } + Add-Content -Path $LogFile -Value $line + } +} + diff --git a/common/win/run.ps1 b/common/win/run.ps1 new file mode 100644 index 0000000..96df2f5 --- /dev/null +++ b/common/win/run.ps1 @@ -0,0 +1,115 @@ +param( + [string]$RunMode = "--interactive", + [string]$TT, + [string]$TSTACK, + [string]$TestUrl, + [string]$AppPath, + [string]$AppPlatform +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing + +# ===== Import utilities ===== +$script:PSScriptRootResolved = Split-Path -Parent $MyInvocation.MyCommand.Path +. (Join-Path $PSScriptRootResolved "logging-utils.ps1") +. (Join-Path $PSScriptRootResolved "common-utils.ps1") + +. (Join-Path $PSScriptRootResolved "user-interaction.ps1") +. (Join-Path $PSScriptRootResolved "env-prequisite-checks.ps1") +. (Join-Path $PSScriptRootResolved "env-setup-run.ps1") +. (Join-Path $PSScriptRootResolved "device-machine-allocation.ps1") + + +# ===== Main flow (baseline steps then run) ===== +try { + + $script:CurrentDir = (Get-Location).Path + + # Get test type and tech stack before logging + if ($RunMode -match "--silent|--debug") { + $textInfo = (Get-Culture).TextInfo + $ttCandidate = if ($TT) { $TT } else { $env:TEST_TYPE } + if ([string]::IsNullOrWhiteSpace($ttCandidate)) { throw "TEST_TYPE is required in silent/debug mode." } + $tsCandidate = if ($TSTACK) { $TSTACK } else { $env:TECH_STACK } + if ([string]::IsNullOrWhiteSpace($tsCandidate)) { throw "TECH_STACK is required in silent/debug mode." } + $script:TEST_TYPE = $textInfo.ToTitleCase($ttCandidate.ToLowerInvariant()) + $script:TECH_STACK = $textInfo.ToTitleCase($tsCandidate.ToLowerInvariant()) + if ($TEST_TYPE -notin @("Web","App")) { throw "TEST_TYPE must be either 'Web' or 'App'." } + if ($TECH_STACK -notin @("Java","Python","NodeJS")) { throw "TECH_STACK must be one of: Java, Python, NodeJS." } + } else { + Resolve-Test-Type -RunMode $RunMode -CliValue $TT + Resolve-Tech-Stack -RunMode $RunMode -CliValue $TSTACK + } + + # Setup log file path AFTER selections + $logFileName = "{0}_{1}_run_result.log" -f $TEST_TYPE.ToLowerInvariant(), $TECH_STACK.ToLowerInvariant() + $errLogFileName = "{0}_{1}_run_result_err.log" -f $TEST_TYPE.ToLowerInvariant(), $TECH_STACK.ToLowerInvariant() + $logFile = Join-Path $script:LOG_DIR $logFileName + $errLogFile = Join-Path $script:LOG_DIR $errLogFileName + if (!(Test-Path $script:LOG_DIR)) { + New-Item -ItemType Directory -Path $script:LOG_DIR -Force | Out-Null + } + '' | Out-File -FilePath $logFile -Encoding UTF8 + '' | Out-File -FilePath $errLogFile -Encoding UTF8 + $script:GLOBAL_LOG = $logFile + $global:NOW_RUN_LOG_FILE = $logFile + $global:NOW_RUN_LOG_FILE_ERR = $errLogFile + + Log-Line "Log file path: $logFile" $global:NOW_RUN_LOG_FILE + + # Setup Summary Header + Log-Section "Setup Summary - BrowserStack NOW" + Log-Line "Timestamp: $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))" $global:NOW_RUN_LOG_FILE + Log-Line "Run Mode: $RunMode" $global:NOW_RUN_LOG_FILE + Log-Line "Log file path: $logFile" $global:NOW_RUN_LOG_FILE + Log-Line "Error log file path: $errLogFile" $global:NOW_RUN_LOG_FILE + + # Setup workspace and get credentials BEFORE app upload + Setup-Workspace + Ask-BrowserStack-Credentials -RunMode $RunMode -UsernameFromEnv $env:BROWSERSTACK_USERNAME -AccessKeyFromEnv $env:BROWSERSTACK_ACCESS_KEY + + Log-Section "Platform & Tech Stack" + Log-Line "Platform: $TEST_TYPE" $global:NOW_RUN_LOG_FILE + Log-Line "Tech Stack: $TECH_STACK" $global:NOW_RUN_LOG_FILE + + # System Prerequisites Check + Validate-Tech-Stack -TechStack $TECH_STACK + + # Account & Plan Details + Fetch-Plan-Details -TestType $TEST_TYPE + + # NOW handle URL/App upload (requires credentials) + Perform-NextSteps-BasedOnTestType -TestType $TEST_TYPE -RunMode $RunMode -TestUrl $TestUrl -AppPath $AppPath -AppPlatform $AppPlatform + + + Log-Line "Checking proxy in environment" $global:NOW_RUN_LOG_FILE + Set-ProxyInEnv + + # Getting Ready section + Log-Section "Getting Ready" + Log-Line "Detected Operating system: Windows" $global:NOW_RUN_LOG_FILE + Log-Line "Clearing old logs from NOW Home Directory inside .browserstack" $global:NOW_RUN_LOG_FILE + Clear-OldLogs + + # Run the setup + Setup-Environment -SetupType $TEST_TYPE.ToLower() -TechStack $TECH_STACK.ToLower() +} +catch { + Log-Line " " $global:NOW_RUN_LOG_FILE + Log-Line "========================================" $global:NOW_RUN_LOG_FILE + Log-Line "EXECUTION FAILED" $global:NOW_RUN_LOG_FILE + Log-Line "========================================" $global:NOW_RUN_LOG_FILE + Log-Line "Error: $($_.Exception.Message)" $global:NOW_RUN_LOG_FILE + Log-Line "Check logs for details:" $global:NOW_RUN_LOG_FILE + Log-Line (" Run Log: {0}" -f $global:NOW_RUN_LOG_FILE) $global:NOW_RUN_LOG_FILE + Log-Line "========================================" $global:NOW_RUN_LOG_FILE + Set-Location -Path $script:CurrentDir + exit 1 +} finally { + Set-Location -Path $script:CurrentDir +} + diff --git a/win/user-interaction.ps1 b/common/win/user-interaction.ps1 similarity index 75% rename from win/user-interaction.ps1 rename to common/win/user-interaction.ps1 index 307bb8e..01224e3 100644 --- a/win/user-interaction.ps1 +++ b/common/win/user-interaction.ps1 @@ -1,4 +1,5 @@ -# User interaction helpers (GUI + CLI) for Windows BrowserStack NOW. +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing function Show-InputBox { param( @@ -162,21 +163,29 @@ function Ask-BrowserStack-Credentials { if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_USERNAME) -or [string]::IsNullOrWhiteSpace($script:BROWSERSTACK_ACCESS_KEY)) { throw "BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY must be provided in silent/debug mode." } - Log-Line "✅ BrowserStack credentials loaded from environment for user: $script:BROWSERSTACK_USERNAME" $GLOBAL_LOG + Log-Line "BrowserStack credentials loaded from environment for user: $script:BROWSERSTACK_USERNAME" $global:NOW_RUN_LOG_FILE + + # Export to process env for child processes + $env:BROWSERSTACK_USERNAME = $script:BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $script:BROWSERSTACK_ACCESS_KEY return } $script:BROWSERSTACK_USERNAME = Show-InputBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Username:`n`nLocate it on https://www.browserstack.com/accounts/profile/details" -DefaultText "" if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_USERNAME)) { - Log-Line "❌ Username empty" $GLOBAL_LOG + Log-Error "Username empty" $global:NOW_RUN_LOG_FILE throw "Username is required" } $script:BROWSERSTACK_ACCESS_KEY = Show-PasswordBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Access Key:`n`nLocate it on https://www.browserstack.com/accounts/profile/details" if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_ACCESS_KEY)) { - Log-Line "❌ Access Key empty" $GLOBAL_LOG + Log-Error "Access Key empty" $global:NOW_RUN_LOG_FILE throw "Access Key is required" } - Log-Line "✅ BrowserStack credentials captured (access key hidden)" $GLOBAL_LOG + + $env:BROWSERSTACK_USERNAME = $script:BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $script:BROWSERSTACK_ACCESS_KEY + + Log-Line "BrowserStack credentials captured (access key hidden)" $global:NOW_RUN_LOG_FILE } function Resolve-Test-Type { @@ -189,7 +198,10 @@ function Resolve-Test-Type { if ([string]::IsNullOrWhiteSpace($CliValue)) { throw "TEST_TYPE is required in silent/debug mode." } $candidate = (Get-Culture).TextInfo.ToTitleCase($CliValue.ToLowerInvariant()) if ($candidate -notin @("Web","App")) { - throw "TEST_TYPE must be either 'Web' or 'App'." + # Try to be flexible + if ($candidate -eq "Web") { $candidate = "Web" } + elseif ($candidate -eq "App") { $candidate = "App" } + else { throw "TEST_TYPE must be either 'Web' or 'App'." } } $script:TEST_TYPE = $candidate return @@ -214,7 +226,10 @@ function Resolve-Tech-Stack { $textInfo = (Get-Culture).TextInfo $candidate = $textInfo.ToTitleCase($CliValue.ToLowerInvariant()) if ($candidate -notin @("Java","Python","NodeJS")) { - throw "TECH_STACK must be one of: Java, Python, NodeJS." + if ($candidate -eq "Java") { } + elseif ($candidate -eq "Python") { } + elseif ($candidate -match "Node") { $candidate = "NodeJS" } + else { throw "TECH_STACK must be one of: Java, Python, NodeJS." } } $script:TECH_STACK = $candidate return @@ -231,16 +246,22 @@ function Resolve-Tech-Stack { function Ask-User-TestUrl { param([string]$RunMode,[string]$CliValue) if ($RunMode -match "--silent" -or $RunMode -match "--debug") { - $script:CX_TEST_URL = if ($CliValue) { $CliValue } elseif ($env:CX_TEST_URL) { $env:CX_TEST_URL } else { $DEFAULT_TEST_URL } + $script:CX_TEST_URL = if ($CliValue) { $CliValue } elseif ($env:CX_TEST_URL) { $env:CX_TEST_URL } else { $script:DEFAULT_TEST_URL } return } + + if (-not [string]::IsNullOrWhiteSpace($CliValue)) { + $script:CX_TEST_URL = $CliValue + Log-Line "Using custom test URL from CLI: $CliValue" $global:NOW_RUN_LOG_FILE + return + } - $testUrl = Show-InputBox -Title "Test URL Setup" -Prompt "Enter the URL you want to test with BrowserStack:`n(Leave blank for default: $DEFAULT_TEST_URL)" -DefaultText "" + $testUrl = Show-InputBox -Title "Test URL Setup" -Prompt "Enter the URL you want to test with BrowserStack:`n(Leave blank for default: $script:DEFAULT_TEST_URL)" -DefaultText "" if ([string]::IsNullOrWhiteSpace($testUrl)) { - $testUrl = $DEFAULT_TEST_URL - Log-Line "⚠️ No URL entered. Falling back to default: $testUrl" $GLOBAL_LOG + $testUrl = $script:DEFAULT_TEST_URL + Log-Line "No URL entered. Falling back to default: $testUrl" $global:NOW_RUN_LOG_FILE } else { - Log-Line "🌐 Using custom test URL: $testUrl" $GLOBAL_LOG + Log-Line "Using custom test URL: $testUrl" $global:NOW_RUN_LOG_FILE } $script:CX_TEST_URL = $testUrl } @@ -253,65 +274,6 @@ function Show-OpenOrSampleAppDialog { return $appChoice } -function Invoke-SampleAppUpload { - $headers = @{ - Authorization = (Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY) - } - $body = @{ - url = "https://www.browserstack.com/app-automate/sample-apps/android/WikipediaSample.apk" - } - $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -ContentType "application/x-www-form-urlencoded" -Body $body - $url = $resp.app_url - if ([string]::IsNullOrWhiteSpace($url)) { - throw "Sample app upload failed" - } - return @{ - Url = $url - Platform = "android" - } -} - -function Invoke-CustomAppUpload { - param( - [Parameter(Mandatory)][string]$FilePath - ) - - $ext = [System.IO.Path]::GetExtension($FilePath).ToLowerInvariant() - switch ($ext) { - ".apk" { $platform = "android" } - ".ipa" { $platform = "ios" } - default { throw "Unsupported app file (only .apk/.ipa)" } - } - - $boundary = [System.Guid]::NewGuid().ToString() - $LF = "`r`n" - $fileBin = [System.IO.File]::ReadAllBytes($FilePath) - $fileName = [System.IO.Path]::GetFileName($FilePath) - - $bodyLines = ( - "--$boundary", - "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", - "Content-Type: application/octet-stream$LF", - [System.Text.Encoding]::GetEncoding("iso-8859-1").GetString($fileBin), - "--$boundary--$LF" - ) -join $LF - - $headers = @{ - Authorization = (Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY) - "Content-Type" = "multipart/form-data; boundary=$boundary" - } - - $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -Body $bodyLines - $url = $resp.app_url - if ([string]::IsNullOrWhiteSpace($url)) { - throw "Upload failed" - } - return @{ - Url = $url - Platform = $platform - } -} - function Ask-And-Upload-App { param( [string]$RunMode, @@ -322,42 +284,48 @@ function Ask-And-Upload-App { if ($RunMode -match "--silent" -or $RunMode -match "--debug") { if ($CliPath) { $result = Invoke-CustomAppUpload -FilePath $CliPath - $script:APP_URL = $result.Url + $script:BROWSERSTACK_APP = $result.Url $script:APP_PLATFORM = if ($CliPlatform) { $CliPlatform } else { $result.Platform } return } $result = Invoke-SampleAppUpload - Log-Line "⚠️ Using auto-uploaded sample app: $($result.Url)" $GLOBAL_LOG - $script:APP_URL = $result.Url + Log-Line "Using auto-uploaded sample app: $($result.Url)" $global:NOW_RUN_LOG_FILE + $script:BROWSERSTACK_APP = $result.Url $script:APP_PLATFORM = $result.Platform return } + + if (-not [string]::IsNullOrWhiteSpace($CliPath)) { + $result = Invoke-CustomAppUpload -FilePath $CliPath + $script:BROWSERSTACK_APP = $result.Url + $script:APP_PLATFORM = if ($CliPlatform) { $CliPlatform } else { $result.Platform } + return + } $choice = Show-OpenOrSampleAppDialog if ([string]::IsNullOrWhiteSpace($choice) -or $choice -eq "Sample App") { $result = Invoke-SampleAppUpload - Log-Line "⚠️ Using sample app: $($result.Url)" $GLOBAL_LOG - $script:APP_URL = $result.Url + Log-Line "Using sample app: $($result.Url)" $global:NOW_RUN_LOG_FILE + $script:BROWSERSTACK_APP = $result.Url $script:APP_PLATFORM = $result.Platform return } - $path = Show-OpenFileDialog -Title "📱 Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" + $path = Show-OpenFileDialog -Title "Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" if ([string]::IsNullOrWhiteSpace($path)) { $result = Invoke-SampleAppUpload - Log-Line "⚠️ No app selected. Using sample app: $($result.Url)" $GLOBAL_LOG - $script:APP_URL = $result.Url + Log-Line "No app selected. Using sample app: $($result.Url)" $global:NOW_RUN_LOG_FILE + $script:BROWSERSTACK_APP = $result.Url $script:APP_PLATFORM = $result.Platform return } $result = Invoke-CustomAppUpload -FilePath $path - $script:APP_URL = $result.Url + $script:BROWSERSTACK_APP = $result.Url $script:APP_PLATFORM = $result.Platform - Log-Line "✅ App uploaded successfully: $($result.Url)" $GLOBAL_LOG + Log-Line "App uploaded successfully: $($result.Url)" $global:NOW_RUN_LOG_FILE } -# ===== Perform next steps based on test type (like Mac's perform_next_steps_based_on_test_type) ===== function Perform-NextSteps-BasedOnTestType { param( [string]$TestType, @@ -378,5 +346,4 @@ function Perform-NextSteps-BasedOnTestType { throw "Unsupported TEST_TYPE: $TestType. Allowed values: Web, App." } } -} - +} \ No newline at end of file diff --git a/common/win/windows-gui.ps1 b/common/win/windows-gui.ps1 new file mode 100644 index 0000000..f5319a5 --- /dev/null +++ b/common/win/windows-gui.ps1 @@ -0,0 +1,169 @@ +param( + [string]$Command, + [string]$Title, + [string]$Prompt, + [string]$DefaultText, + [string[]]$Choices, + [string]$DefaultChoice, + [string]$Filter +) + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing + +function Show-InputBox { + param( + [string]$Title = "Input", + [string]$Prompt = "Enter value:", + [string]$DefaultText = "" + ) + $form = New-Object System.Windows.Forms.Form + $form.Text = $Title + $form.Size = New-Object System.Drawing.Size(500,220) + $form.StartPosition = "CenterScreen" + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $label.MaximumSize = New-Object System.Drawing.Size(460,0) + $label.AutoSize = $true + $label.Location = New-Object System.Drawing.Point(10,20) + $form.Controls.Add($label) + + $textBox = New-Object System.Windows.Forms.TextBox + $textBox.Size = New-Object System.Drawing.Size(460,20) + $textBox.Location = New-Object System.Drawing.Point(10,($label.Bottom + 10)) + $textBox.Text = $DefaultText + $form.Controls.Add($textBox) + + $okButton = New-Object System.Windows.Forms.Button + $okButton.Text = "OK" + $okButton.Location = New-Object System.Drawing.Point(380,($textBox.Bottom + 20)) + $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() }) + $form.Controls.Add($okButton) + + $form.AcceptButton = $okButton + [void]$form.ShowDialog() + return [string]$form.Tag +} + +function Show-PasswordBox { + param( + [string]$Title = "Secret", + [string]$Prompt = "Enter secret:" + ) + $form = New-Object System.Windows.Forms.Form + $form.Text = $Title + $form.Size = New-Object System.Drawing.Size(500,220) + $form.StartPosition = "CenterScreen" + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $label.MaximumSize = New-Object System.Drawing.Size(460,0) + $label.AutoSize = $true + $label.Location = New-Object System.Drawing.Point(10,20) + $form.Controls.Add($label) + + $textBox = New-Object System.Windows.Forms.TextBox + $textBox.Size = New-Object System.Drawing.Size(460,20) + $textBox.Location = New-Object System.Drawing.Point(10,($label.Bottom + 10)) + $textBox.UseSystemPasswordChar = $true + $form.Controls.Add($textBox) + + $okButton = New-Object System.Windows.Forms.Button + $okButton.Text = "OK" + $okButton.Location = New-Object System.Drawing.Point(380,($textBox.Bottom + 20)) + $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() }) + $form.Controls.Add($okButton) + + $form.AcceptButton = $okButton + [void]$form.ShowDialog() + return [string]$form.Tag +} + +function Show-ClickChoice { + param( + [string]$Title = "Choose", + [string]$Prompt = "Select one:", + [string[]]$Choices, + [string]$DefaultChoice + ) + if (-not $Choices -or $Choices.Count -eq 0) { return "" } + + $form = New-Object System.Windows.Forms.Form + $form.Text = $Title + $form.StartPosition = "CenterScreen" + $form.MinimizeBox = $false + $form.MaximizeBox = $false + $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog + $form.BackColor = [System.Drawing.Color]::FromArgb(245,245,245) + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $label.AutoSize = $true + $label.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Regular) + $label.Location = New-Object System.Drawing.Point(12, 12) + $form.Controls.Add($label) + + $panel = New-Object System.Windows.Forms.FlowLayoutPanel + $panel.Location = New-Object System.Drawing.Point(12, 40) + $panel.Size = New-Object System.Drawing.Size(460, 140) + $panel.WrapContents = $true + $panel.AutoScroll = $true + $panel.FlowDirection = [System.Windows.Forms.FlowDirection]::LeftToRight + $form.Controls.Add($panel) + + $selected = $null + foreach ($c in $Choices) { + $btn = New-Object System.Windows.Forms.Button + $btn.Text = $c + $btn.Width = 140 + $btn.Height = 40 + $btn.Margin = '8,8,8,8' + $btn.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold) + $btn.FlatStyle = 'System' + if ($c -eq $DefaultChoice) { + $btn.BackColor = [System.Drawing.Color]::FromArgb(232,240,254) + } + $btn.Add_Click({ + $script:selected = $this.Text + $form.Tag = $script:selected + $form.Close() + }) + $panel.Controls.Add($btn) + } + + $cancel = New-Object System.Windows.Forms.Button + $cancel.Text = "Cancel" + $cancel.Width = 90 + $cancel.Height = 32 + $cancel.Location = New-Object System.Drawing.Point(382, 188) + $cancel.Add_Click({ $form.Tag = ""; $form.Close() }) + $form.Controls.Add($cancel) + $form.CancelButton = $cancel + + $form.ClientSize = New-Object System.Drawing.Size(484, 230) + [void]$form.ShowDialog() + return [string]$form.Tag +} + +function Show-OpenFileDialog { + param( + [string]$Title = "Select File", + [string]$Filter = "All files (*.*)|*.*" + ) + $ofd = New-Object System.Windows.Forms.OpenFileDialog + $ofd.Title = $Title + $ofd.Filter = $Filter + $ofd.Multiselect = $false + if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { + return $ofd.FileName + } + return "" +} + +switch ($Command) { + "InputBox" { Show-InputBox -Title $Title -Prompt $Prompt -DefaultText $DefaultText } + "PasswordBox" { Show-PasswordBox -Title $Title -Prompt $Prompt } + "ClickChoice" { Show-ClickChoice -Title $Title -Prompt $Prompt -Choices $Choices -DefaultChoice $DefaultChoice } + "OpenFileDialog" { Show-OpenFileDialog -Title $Title -Filter $Filter } +} \ No newline at end of file diff --git a/mac/device-machine-allocation.sh b/mac/device-machine-allocation.sh deleted file mode 100644 index 69b9512..0000000 --- a/mac/device-machine-allocation.sh +++ /dev/null @@ -1,171 +0,0 @@ -#!/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: $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 )) - - local hardcodedBVersion=140 # python doesn't support dynamic latest versioning yet - - 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: $(( hardcodedBVersion-i )) -" - 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-setup-run.sh b/mac/env-setup-run.sh deleted file mode 100644 index 398ce24..0000000 --- a/mac/env-setup-run.sh +++ /dev/null @@ -1,548 +0,0 @@ -#!/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=$(awk -v n="$max_parallels" 'BEGIN { printf "%d", n }') - [ -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 --only-binary grpcio -r requirements.txt >> "$NOW_RUN_LOG_FILE" 2>&1 - pip3 uninstall -y pytest-html pytest-rerunfailures >> "$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 --only-binary grpcio -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 - - # 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 - - export BROWSERSTACK_CONFIG_FILE="./$run_dir/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 & cmd_pid=$!|| return 1 - show_spinner "$cmd_pid" - wait "$cmd_pid" - ) - - 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 - } - - # Activate virtual environment (handle Windows/Unix paths) - if [ -f ".venv/Scripts/activate" ]; then - # shellcheck source=/dev/null - source .venv/Scripts/activate - else - # shellcheck source=/dev/null - source .venv/bin/activate - fi - log_success "Virtual environment created and activated." -} \ No newline at end of file diff --git a/mac/logging-utils.sh b/mac/logging-utils.sh deleted file mode 100644 index 2358b31..0000000 --- a/mac/logging-utils.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/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/run.sh b/mac/run.sh index a6a9c9b..aef69b6 100755 --- a/mac/run.sh +++ b/mac/run.sh @@ -1,78 +1 @@ -#!/bin/bash -set -o pipefail - -# 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" -} - -# ===== Main flow (baseline steps then run) ===== - -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) - -log_section "🧭 Setup Summary – BrowserStack NOW" -log_info "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')" - - -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 - 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_info "Log file path: $log_file" -export NOW_RUN_LOG_FILE="$log_file" -setup_workspace -get_browserstack_credentials "$RUN_MODE" - -log_section "⚙️ Platform & Tech Stack" -log_info "Platform: ${TEST_TYPE:-N/A}" -log_info "Tech Stack: ${TECH_STACK:-N/A}" - -validate_tech_stack_installed "$TECH_STACK" -fetch_plan_details "$TEST_TYPE" - -if [[ "$RUN_MODE" == *"--silent"* || "$RUN_MODE" == *"--debug"* ]]; then - if [[ $TEST_TYPE == "app" ]]; then - get_upload_app - log_success "Sample App uploaded successfully" - fi -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 - -log_section "🧹 Getting Ready" -detect_os -log_info "Detected Operating system: $NOW_OS" -log_info "Clearing old logs fron NOW Home Directory inside .browserstack" - -clear_old_logs - -log_info "Starting $TEST_TYPE setup for $TECH_STACK" -run_setup "$TEST_TYPE" "$TECH_STACK" +exec "$(dirname "$0")/../common/mac/run.sh" "$@" diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 deleted file mode 100644 index 105bc69..0000000 --- a/win/common-utils.ps1 +++ /dev/null @@ -1,358 +0,0 @@ -# Common helpers shared by the Windows BrowserStack NOW scripts. - -# ===== Global Variables ===== -$script:WORKSPACE_DIR = Join-Path $env:USERPROFILE ".browserstack" -$script:PROJECT_FOLDER = "NOW" - -$script:GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER -$script:LOG_DIR = Join-Path $GLOBAL_DIR "logs" -$script:GLOBAL_LOG = "" -$script:WEB_LOG = "" -$script:MOBILE_LOG = "" - -# Script state -$script:BROWSERSTACK_USERNAME = "" -$script:BROWSERSTACK_ACCESS_KEY = "" -$script:TEST_TYPE = "" # Web / App -$script:TECH_STACK = "" # Java / Python / JS -[double]$script:PARALLEL_PERCENTAGE = 1.00 - -$script:WEB_PLAN_FETCHED = $false -$script:MOBILE_PLAN_FETCHED = $false -[int]$script:TEAM_PARALLELS_MAX_ALLOWED_WEB = 0 -[int]$script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE = 0 - -# URL handling -$script:DEFAULT_TEST_URL = "https://bstackdemo.com" -$script:CX_TEST_URL = $DEFAULT_TEST_URL - -# App handling -$script:APP_URL = "" -$script:APP_PLATFORM = "" # ios | android | all - -# Chosen Python command tokens (set during validation when Python is selected) -$script:PY_CMD = @() - -# ===== Error patterns ===== -$script:WEB_SETUP_ERRORS = @("") -$script:WEB_LOCAL_ERRORS = @("") -$script:MOBILE_SETUP_ERRORS= @("") -$script:MOBILE_LOCAL_ERRORS= @("") - -# ===== Workspace Management ===== -function Ensure-Workspace { - if (!(Test-Path $GLOBAL_DIR)) { - New-Item -ItemType Directory -Path $GLOBAL_DIR | Out-Null - Log-Line "✅ Created Onboarding workspace: $GLOBAL_DIR" $GLOBAL_LOG - } else { - Log-Line "✅ Onboarding workspace found at: $GLOBAL_DIR" $GLOBAL_LOG - } -} - -function Setup-Workspace { - Log-Section "⚙️ Environment & Credentials" $GLOBAL_LOG - Ensure-Workspace -} - -function Clear-OldLogs { - if (!(Test-Path $LOG_DIR)) { - New-Item -ItemType Directory -Path $LOG_DIR | Out-Null - } - - $legacyLogs = @("global.log","web_run_result.log","mobile_run_result.log") - foreach ($legacy in $legacyLogs) { - $legacyPath = Join-Path $LOG_DIR $legacy - if (Test-Path $legacyPath) { - Remove-Item -Path $legacyPath -Force -ErrorAction SilentlyContinue - } - } - - Log-Line "✅ Logs directory cleaned. Legacy files removed." $GLOBAL_LOG -} - -# ===== Git Clone ===== -function Invoke-GitClone { - param( - [Parameter(Mandatory)] [string]$Url, - [Parameter(Mandatory)] [string]$Target, - [string]$Branch, - [string]$LogFile - ) - $args = @("clone") - if ($Branch) { $args += @("-b", $Branch) } - $args += @($Url, $Target) - - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = "git" - $psi.Arguments = ($args | ForEach-Object { - if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } - }) -join ' ' - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true - $psi.WorkingDirectory = (Get-Location).Path - - $p = New-Object System.Diagnostics.Process - $p.StartInfo = $psi - [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() - - if ($LogFile) { - if ($stdout) { Add-Content -Path $LogFile -Value $stdout } - if ($stderr) { Add-Content -Path $LogFile -Value $stderr } - } - - if ($p.ExitCode -ne 0) { - throw "git clone failed (exit $($p.ExitCode)): $stderr" - } -} - -function Set-ContentNoBom { - param( - [Parameter(Mandatory)][string]$Path, - [Parameter(Mandatory)][string]$Value - ) - $enc = New-Object System.Text.UTF8Encoding($false) # no BOM - [System.IO.File]::WriteAllText($Path, $Value, $enc) -} - -function Invoke-External { - param( - [Parameter(Mandatory)][string]$Exe, - [Parameter()][string[]]$Arguments = @(), - [string]$LogFile, - [string]$WorkingDirectory - ) - $psi = New-Object System.Diagnostics.ProcessStartInfo - $exeToRun = $Exe - $argLine = ($Arguments | ForEach-Object { if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } }) -join ' ' - - $ext = [System.IO.Path]::GetExtension($Exe) - if ($ext -and ($ext.ToLowerInvariant() -in @('.cmd','.bat'))) { - if (-not (Test-Path $Exe)) { throw "Command not found: $Exe" } - $psi.FileName = "cmd.exe" - $psi.Arguments = "/c `"$Exe`" $argLine" - } else { - $psi.FileName = $exeToRun - $psi.Arguments = $argLine - } - - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true - if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) { - $psi.WorkingDirectory = (Get-Location).Path - } else { - $psi.WorkingDirectory = $WorkingDirectory - } - - $p = New-Object System.Diagnostics.Process - $p.StartInfo = $psi - - if ($LogFile) { - $logDir = Split-Path -Parent $LogFile - if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } - - $stdoutAction = { - if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { - Add-Content -Path $Event.MessageData -Value $EventArgs.Data - } - } - $stderrAction = { - if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { - Add-Content -Path $Event.MessageData -Value $EventArgs.Data - } - } - - $stdoutEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $stdoutAction -MessageData $LogFile - $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile - - [void]$p.Start() - $p.BeginOutputReadLine() - $p.BeginErrorReadLine() - $p.WaitForExit() - - Unregister-Event -SourceIdentifier $stdoutEvent.Name - Unregister-Event -SourceIdentifier $stderrEvent.Name - Remove-Job -Id $stdoutEvent.Id -Force - Remove-Job -Id $stderrEvent.Id -Force - } else { - [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() - } - - return $p.ExitCode -} - -function Get-MavenCommand { - param([Parameter(Mandatory)][string]$RepoDir) - $mvnCmd = Get-Command mvn -ErrorAction SilentlyContinue - if ($mvnCmd) { return $mvnCmd.Source } - $wrapper = Join-Path $RepoDir "mvnw.cmd" - if (Test-Path $wrapper) { return $wrapper } - throw "Maven not found in PATH and 'mvnw.cmd' not present under $RepoDir. Install Maven or ensure the wrapper exists." -} - -function Get-VenvPython { - param([Parameter(Mandatory)][string]$VenvDir) - $py = Join-Path $VenvDir "Scripts\python.exe" - if (Test-Path $py) { return $py } - throw "Python interpreter not found in venv: $VenvDir" -} - -function Set-PythonCmd { - $candidates = @( - @("python3"), - @("python"), - @("py","-3"), - @("py") - ) - foreach ($cand in $candidates) { - try { - $exe = $cand[0] - $args = @() - if ($cand.Length -gt 1) { $args = $cand[1..($cand.Length-1)] } - $code = Invoke-External -Exe $exe -Arguments ($args + @("--version")) -LogFile $null - if ($code -eq 0) { - $script:PY_CMD = $cand - return - } - } catch {} - } - throw "Python not found via python3/python/py. Please install Python 3 and ensure it's on PATH." -} - -function Invoke-Py { - param( - [Parameter(Mandatory)][string[]]$Arguments, - [string]$LogFile, - [string]$WorkingDirectory - ) - if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } - $exe = $PY_CMD[0] - $baseArgs = @() - if ($PY_CMD.Count -gt 1) { $baseArgs = $PY_CMD[1..($PY_CMD.Count-1)] } - return (Invoke-External -Exe $exe -Arguments ($baseArgs + $Arguments) -LogFile $LogFile -WorkingDirectory $WorkingDirectory) -} - -function Show-Spinner { - param([Parameter(Mandatory)][System.Diagnostics.Process]$Process) - $spin = @('|','/','-','\') - $i = 0 - $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - while (!$Process.HasExited) { - Write-Host "`r[$ts] ⏳ Processing... $($spin[$i])" -NoNewline - $i = ($i + 1) % 4 - Start-Sleep -Milliseconds 100 - } - Write-Host "`r[$ts] ✅ Done! " -} - -function Test-PrivateIP { - param([string]$IP) - if ([string]::IsNullOrWhiteSpace($IP)) { return $false } - $parts = $IP.Split('.') - if ($parts.Count -ne 4) { return $false } - $first = [int]$parts[0] - $second = [int]$parts[1] - if ($first -eq 10) { return $true } - if ($first -eq 192 -and $second -eq 168) { return $true } - if ($first -eq 172 -and $second -ge 16 -and $second -le 31) { return $true } - return $false -} - -function Test-DomainPrivate { - $domain = $CX_TEST_URL -replace '^https?://', '' -replace '/.*$', '' - Log-Line "Website domain: $domain" $GLOBAL_LOG - $env:NOW_WEB_DOMAIN = $CX_TEST_URL - - $IP_ADDRESS = "" - try { - $dnsResult = Resolve-DnsName -Name $domain -Type A -ErrorAction Stop | Where-Object { $_.Type -eq 'A' } | Select-Object -First 1 - if ($dnsResult) { - $IP_ADDRESS = $dnsResult.IPAddress - } - } catch { - try { - $nslookupOutput = nslookup $domain 2>&1 | Out-String - if ($nslookupOutput -match '(?:Address|Addresses):\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') { - $IP_ADDRESS = $matches[1] - } - } catch { - Log-Line "⚠️ Failed to resolve domain: $domain (assuming public domain)" $GLOBAL_LOG - $IP_ADDRESS = "" - } - } - - if ([string]::IsNullOrWhiteSpace($IP_ADDRESS)) { - Log-Line "⚠️ DNS resolution failed for: $domain (treating as public domain, BrowserStack Local will be DISABLED)" $GLOBAL_LOG - } else { - Log-Line "✅ Resolved IP: $IP_ADDRESS" $GLOBAL_LOG - } - - return (Test-PrivateIP -IP $IP_ADDRESS) -} - -function Get-BasicAuthHeader { - param([string]$User, [string]$Key) - $pair = "{0}:{1}" -f $User,$Key - $bytes = [System.Text.Encoding]::UTF8.GetBytes($pair) - "Basic {0}" -f [System.Convert]::ToBase64String($bytes) -} - -# ===== Fetch plan details ===== -function Fetch-Plan-Details { - param([string]$TestType) - - if ([string]::IsNullOrWhiteSpace($TestType)) { - throw "Test type is required to fetch plan details." - } - - $normalized = $TestType.ToLowerInvariant() - Log-Line "ℹ️ Fetching BrowserStack plan for $normalized" $GLOBAL_LOG - - $auth = Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY - $headers = @{ Authorization = $auth } - - switch ($normalized) { - "web" { - try { - $resp = Invoke-RestMethod -Method Get -Uri "https://api.browserstack.com/automate/plan.json" -Headers $headers - $script:WEB_PLAN_FETCHED = $true - $script:TEAM_PARALLELS_MAX_ALLOWED_WEB = [int]$resp.parallel_sessions_max_allowed - Log-Line "✅ Web Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_WEB" $GLOBAL_LOG - } catch { - Log-Line "❌ Web Testing Plan fetch failed ($($_.Exception.Message))" $GLOBAL_LOG - } - if (-not $WEB_PLAN_FETCHED) { - throw "Unable to fetch Web Testing plan details." - } - } - "app" { - try { - $resp2 = Invoke-RestMethod -Method Get -Uri "https://api-cloud.browserstack.com/app-automate/plan.json" -Headers $headers - $script:MOBILE_PLAN_FETCHED = $true - $script:TEAM_PARALLELS_MAX_ALLOWED_MOBILE = [int]$resp2.parallel_sessions_max_allowed - Log-Line "✅ Mobile App Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" $GLOBAL_LOG - } catch { - Log-Line "❌ Mobile App Testing Plan fetch failed ($($_.Exception.Message))" $GLOBAL_LOG - } - if (-not $MOBILE_PLAN_FETCHED) { - throw "Unable to fetch Mobile App Testing plan details." - } - } - default { - throw "Unsupported TEST_TYPE: $TestType. Allowed values: Web, App." - } - } - - Log-Line "ℹ️ Plan summary: Web fetched=$WEB_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_WEB), Mobile fetched=$MOBILE_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE)" $GLOBAL_LOG -} - - diff --git a/win/device-machine-allocation.ps1 b/win/device-machine-allocation.ps1 deleted file mode 100644 index 35c7a08..0000000 --- a/win/device-machine-allocation.ps1 +++ /dev/null @@ -1,285 +0,0 @@ -# Device and platform allocation utilities for the Windows BrowserStack NOW flow. -# Mirrors the macOS shell script structure so we can share logic between both platforms. - -# ===== Example Platform Templates ===== -$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 tiers (kept for parity) -$MOBILE_TIER1 = @( - "ios|iPhone 15|17", - "ios|iPhone 15 Pro|17", - "ios|iPhone 16|18", - "android|Samsung Galaxy S25|15", - "android|Samsung Galaxy S24|14" -) -$MOBILE_TIER2 = @( - "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" -) -$MOBILE_TIER3 = @( - "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 Note 20|10", - "android|Samsung Galaxy S10|9", - "android|Samsung Galaxy Note 9|8", - "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" -) -$MOBILE_TIER4 = @( - "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" -) - -# MOBILE_ALL combines the tiers -$MOBILE_ALL = @() -$MOBILE_ALL += $MOBILE_TIER1 -$MOBILE_ALL += $MOBILE_TIER2 -$MOBILE_ALL += $MOBILE_TIER3 -$MOBILE_ALL += $MOBILE_TIER4 - -# ===== Generators ===== -function Generate-Web-Platforms-Yaml { - param([int]$MaxTotalParallels) - $max = [Math]::Floor($MaxTotalParallels * $PARALLEL_PERCENTAGE) - if ($max -lt 0) { $max = 0 } - $sb = New-Object System.Text.StringBuilder - $count = 0 - - foreach ($t in $WEB_PLATFORM_TEMPLATES) { - $parts = $t.Split('|') - $os = $parts[0]; $osVersion = $parts[1]; $browserName = $parts[2] - foreach ($version in @('latest','latest-1','latest-2')) { - [void]$sb.AppendLine(" - os: $os") - [void]$sb.AppendLine(" osVersion: $osVersion") - [void]$sb.AppendLine(" browserName: $browserName") - [void]$sb.AppendLine(" browserVersion: $version") - $count++ - if ($count -ge $max -and $max -gt 0) { - return $sb.ToString() - } - } - } - return $sb.ToString() -} - -function Generate-Mobile-Platforms-Yaml { - param([int]$MaxTotalParallels) - $max = [Math]::Floor($MaxTotalParallels * $PARALLEL_PERCENTAGE) - if ($max -lt 1) { $max = 1 } - $sb = New-Object System.Text.StringBuilder - $count = 0 - - foreach ($t in $MOBILE_ALL) { - $parts = $t.Split('|') - $platformName = $parts[0] - $deviceName = $parts[1] - $platformVer = $parts[2] - - if (-not [string]::IsNullOrWhiteSpace($APP_PLATFORM)) { - if ($APP_PLATFORM -eq 'ios' -and $platformName -ne 'ios') { continue } - if ($APP_PLATFORM -eq 'android' -and $platformName -ne 'android') { continue } - } - - [void]$sb.AppendLine(" - platformName: $platformName") - [void]$sb.AppendLine(" deviceName: $deviceName") - [void]$sb.AppendLine(" platformVersion: '${platformVer}.0'") - $count++ - if ($count -ge $max) { return $sb.ToString() } - } - return $sb.ToString() -} - -function Generate-Mobile-Caps-Json { - param([int]$MaxTotalParallels, [string]$OutputFile) - $json = Generate-Mobile-Caps-Json-String -MaxTotalParallels $MaxTotalParallels - Set-ContentNoBom -Path $OutputFile -Value $json - return $json -} - -function Generate-Mobile-Caps-Json-String { - param([int]$MaxTotalParallels) - $max = $MaxTotalParallels - if ($max -lt 1) { $max = 1 } - - $items = @() - $count = 0 - - foreach ($t in $MOBILE_ALL) { - $parts = $t.Split('|') - $platformName = $parts[0] - $deviceName = $parts[1] - $platformVer = $parts[2] - - # Filter based on APP_PLATFORM - if (-not [string]::IsNullOrWhiteSpace($APP_PLATFORM)) { - if ($APP_PLATFORM -eq 'ios' -and $platformName -ne 'ios') { continue } - if ($APP_PLATFORM -eq 'android' -and $platformName -ne 'android') { continue } - } - - $items += [pscustomobject]@{ - 'bstack:options' = @{ - deviceName = $deviceName - osVersion = "${platformVer}.0" - } - } - $count++ - if ($count -ge $max) { break } - } - - $json = ($items | ConvertTo-Json -Depth 5 -Compress) - return $json -} - -function Generate-Web-Caps-Json { - param([int]$MaxTotalParallels) - $max = [Math]::Floor($MaxTotalParallels * $PARALLEL_PERCENTAGE) - if ($max -lt 1) { $max = 1 } - - $items = @() - $count = 0 - foreach ($t in $WEB_PLATFORM_TEMPLATES) { - $parts = $t.Split('|') - $os = $parts[0]; $osVersion = $parts[1]; $browserName = $parts[2] - foreach ($version in @('latest','latest-1','latest-2')) { - $items += [pscustomobject]@{ - browserName = $browserName - browserVersion = $version - 'bstack:options' = @{ - os = $os - osVersion = $osVersion - } - } - $count++ - if ($count -ge $max) { break } - } - if ($count -ge $max) { break } - } - - # Return valid JSON array (keep the brackets!) - $json = ($items | ConvertTo-Json -Depth 5 -Compress) - return $json -} - - - diff --git a/win/env-prequisite-checks.ps1 b/win/env-prequisite-checks.ps1 deleted file mode 100644 index e384d55..0000000 --- a/win/env-prequisite-checks.ps1 +++ /dev/null @@ -1,148 +0,0 @@ -# Environment prerequisite checks (proxy + tech stack validation). - -$PROXY_TEST_URL = "https://www.browserstack.com/automate/browsers.json" - -function Parse-ProxyUrl { - param([string]$ProxyUrl) - if ([string]::IsNullOrWhiteSpace($ProxyUrl)) { - return $null - } - - $cleaned = $ProxyUrl -replace '^https?://', '' - if ($cleaned -match '@') { - $cleaned = $cleaned.Substring($cleaned.IndexOf('@') + 1) - } - - if ($cleaned -match '^([^:]+):(\d+)') { - return @{ - Host = $matches[1] - Port = $matches[2] - } - } elseif ($cleaned -match '^([^:]+)') { - return @{ - Host = $matches[1] - Port = "8080" - } - } - return $null -} - -function Set-ProxyInEnv { - param( - [string]$Username, - [string]$AccessKey - ) - - Log-Section "🌐 Network & Proxy Validation" $GLOBAL_LOG - - $proxy = $env:http_proxy - if ([string]::IsNullOrWhiteSpace($proxy)) { $proxy = $env:HTTP_PROXY } - if ([string]::IsNullOrWhiteSpace($proxy)) { $proxy = $env:https_proxy } - if ([string]::IsNullOrWhiteSpace($proxy)) { $proxy = $env:HTTPS_PROXY } - - $env:PROXY_HOST = "" - $env:PROXY_PORT = "" - - if ([string]::IsNullOrWhiteSpace($proxy)) { - Log-Line "No proxy found in environment. Using direct connection." $GLOBAL_LOG - return - } - - Log-Line "Proxy detected: $proxy" $GLOBAL_LOG - $proxyInfo = Parse-ProxyUrl -ProxyUrl $proxy - if (-not $proxyInfo) { - Log-Line "❌ Failed to parse proxy URL: $proxy" $GLOBAL_LOG - return - } - - $pair = if ($Username -and $AccessKey) { "$Username`:$AccessKey" } else { "" } - $base64Creds = "" - if ($pair) { - $base64Creds = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pair)) - } - - try { - $proxyUri = "http://$($proxyInfo.Host):$($proxyInfo.Port)" - $webProxy = New-Object System.Net.WebProxy($proxyUri) - $webClient = New-Object System.Net.WebClient - $webClient.Proxy = $webProxy - if ($base64Creds) { - $webClient.Headers.Add("Authorization", "Basic $base64Creds") - } - - $null = $webClient.DownloadString($PROXY_TEST_URL) - - Log-Line "✅ Reachable via proxy. HTTP 200" $GLOBAL_LOG - Log-Line "Exporting PROXY_HOST=$($proxyInfo.Host)" $GLOBAL_LOG - Log-Line "Exporting PROXY_PORT=$($proxyInfo.Port)" $GLOBAL_LOG - $env:PROXY_HOST = $proxyInfo.Host - $env:PROXY_PORT = $proxyInfo.Port - } catch { - $statusMsg = $_.Exception.Message - Log-Line "❌ Not reachable via proxy. Error: $statusMsg" $GLOBAL_LOG - $env:PROXY_HOST = "" - $env:PROXY_PORT = "" - } -} - -function Validate-Tech-Stack { - Log-Line "ℹ️ Checking prerequisites for $script:TECH_STACK" $GLOBAL_LOG - switch ($script:TECH_STACK) { - "Java" { - if (-not (Get-Command java -ErrorAction SilentlyContinue)) { - Log-Line "❌ Java command not found in PATH." $GLOBAL_LOG - throw "Java not found" - } - $verInfo = & cmd /c 'java -version 2>&1' - if (-not $verInfo) { - Log-Line "❌ Java exists but failed to run." $GLOBAL_LOG - throw "Java invocation failed" - } - Log-Line "✅ Java is installed. Version details:" $GLOBAL_LOG - ($verInfo -split "`r?`n") | ForEach-Object { if ($_ -ne "") { Log-Line " $_" $GLOBAL_LOG } } - } - "Python" { - try { - Set-PythonCmd - $code = Invoke-Py -Arguments @("--version") -LogFile $null -WorkingDirectory (Get-Location).Path - if ($code -eq 0) { - Log-Line ("✅ Python3 is installed: {0}" -f ( ($PY_CMD -join ' ') )) $GLOBAL_LOG - } else { - throw "Python present but failed to execute" - } - } catch { - Log-Line "❌ Python3 exists but failed to run." $GLOBAL_LOG - throw - } - } - "NodeJS" { - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Log-Line "❌ Node.js command not found in PATH." $GLOBAL_LOG - throw "Node not found" - } - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - Log-Line "❌ npm command not found in PATH." $GLOBAL_LOG - throw "npm not found" - } - $nodeVer = & node -v 2>&1 - if (-not $nodeVer) { - Log-Line "❌ Node.js exists but failed to run." $GLOBAL_LOG - throw "Node.js invocation failed" - } - $npmVer = & npm -v 2>&1 - if (-not $npmVer) { - Log-Line "❌ npm exists but failed to run." $GLOBAL_LOG - throw "npm invocation failed" - } - Log-Line "✅ Node.js is installed: $nodeVer" $GLOBAL_LOG - Log-Line "✅ npm is installed: $npmVer" $GLOBAL_LOG - } - default { - Log-Line "❌ Unknown TECH_STACK: $script:TECH_STACK" $GLOBAL_LOG - throw "Unknown tech stack" - } - } -} - - - diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 deleted file mode 100644 index 982bd1b..0000000 --- a/win/env-setup-run.ps1 +++ /dev/null @@ -1,589 +0,0 @@ -# Environment Setup and Run functions for Windows BrowserStack NOW. -# Mirrors the Mac env-setup-run.sh structure. - -# ===== Setup: Web (Java) ===== -function Setup-Web-Java { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-testng-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - Push-Location $TARGET - try { - Log-Line "ℹ️ Target website: $CX_TEST_URL" $GLOBAL_LOG - - if (Test-DomainPrivate) { - $UseLocal = $true - } - - Report-BStackLocalStatus -LocalFlag $UseLocal - - Log-Line "🧩 Generating YAML config (browserstack.yml)" $GLOBAL_LOG - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform - $localFlag = if ($UseLocal) { "true" } else { "false" } - - $yamlContent = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: testng -browserstackLocal: $localFlag -buildName: now-windows-web-java-testng -projectName: NOW-Web-Test -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ - - Set-Content "browserstack.yml" -Value $yamlContent - Log-Line "✅ Created browserstack.yml in root directory" $GLOBAL_LOG - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: now-windows-web-java-testng" $GLOBAL_LOG - Log-Line "ℹ️ Web Application Endpoint: $CX_TEST_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $localFlag" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms:" $GLOBAL_LOG - $platforms -split "`n" | ForEach-Object { if ($_.Trim()) { Log-Line " $_" $GLOBAL_LOG } } - - $mvn = Get-MavenCommand -RepoDir $TARGET - Log-Line "⚙️ Running '$mvn install -DskipTests'" $GLOBAL_LOG - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - Print-TestsRunningSection -Command "mvn test -P sample-test" - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Setup: Web (Python) ===== -function Setup-Web-Python { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-pytest-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - Push-Location $TARGET - try { - if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } - $venv = Join-Path $TARGET "venv" - if (!(Test-Path $venv)) { - [void](Invoke-Py -Arguments @("-m","venv",$venv) -LogFile $LogFile -WorkingDirectory $TARGET) - } - $venvPy = Get-VenvPython -VenvDir $venv - - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - if (Test-DomainPrivate) { - $UseLocal = $true - } - - Report-BStackLocalStatus -LocalFlag $UseLocal - - $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform - $localFlag = if ($UseLocal) { "true" } else { "false" } - -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: now-windows-web-python-pytest -projectName: NOW-Web-Test -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ | Set-Content "browserstack.yml" - - Log-Line "✅ Updated browserstack.yml with platforms and credentials" $GLOBAL_LOG - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: now-windows-web-python-pytest" $GLOBAL_LOG - Log-Line "ℹ️ Web Application Endpoint: $CX_TEST_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $localFlag" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms:" $GLOBAL_LOG - $platforms -split "`n" | ForEach-Object { if ($_.Trim()) { Log-Line " $_" $GLOBAL_LOG } } - - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Print-TestsRunningSection -Command "browserstack-sdk pytest -s tests/bstack-sample-test.py" - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Setup: Web (NodeJS) ===== -function Setup-Web-NodeJS { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-webdriverio-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - Push-Location $TARGET - try { - Log-Line "⚙️ Running 'npm install'" $GLOBAL_LOG - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - $caps = Generate-Web-Caps-Json -MaxTotalParallels $ParallelsPerPlatform - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - $env:BSTACK_CAPS_JSON = $caps - - if (Test-DomainPrivate) { - $UseLocal = $true - } - - Report-BStackLocalStatus -LocalFlag $UseLocal - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $localFlagStr = if ($UseLocal) { "true" } else { "false" } - $env:BROWSERSTACK_LOCAL = $localFlagStr - $env:BROWSERSTACK_BUILD_NAME = "now-windows-web-nodejs-wdio" - $env:BROWSERSTACK_PROJECT_NAME = "NOW-Web-Test" - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: $($env:BROWSERSTACK_BUILD_NAME)" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Project: $($env:BROWSERSTACK_PROJECT_NAME)" $GLOBAL_LOG - Log-Line "ℹ️ Web Application Endpoint: $CX_TEST_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $localFlagStr" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms:" $GLOBAL_LOG - Log-Line " $caps" $GLOBAL_LOG - - Print-TestsRunningSection -Command "npm run test" - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Setup: Mobile (Java) ===== -function Setup-Mobile-Java { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-testng-appium-app-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - Push-Location $TARGET - try { - if ($APP_PLATFORM -eq "all" -or $APP_PLATFORM -eq "android") { - Set-Location "android\testng-examples" - } else { - Set-Location "ios\testng-examples" - } - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $env:BROWSERSTACK_CONFIG_FILE = ".\browserstack.yml" - - $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform - $localFlag = if ($UseLocal) { "true" } else { "false" } - - # Write complete browserstack.yml (not just append) - $yamlContent = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: testng -browserstackLocal: $localFlag -buildName: now-windows-app-java-testng -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platforms -"@ - $yamlContent | Set-Content -Path $env:BROWSERSTACK_CONFIG_FILE -Encoding UTF8 - - Report-BStackLocalStatus -LocalFlag $UseLocal - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: now-windows-app-java-testng" $GLOBAL_LOG - Log-Line "ℹ️ Native App Endpoint: $APP_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $localFlag" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms:" $GLOBAL_LOG - $platforms -split "`n" | ForEach-Object { if ($_.Trim()) { Log-Line " $_" $GLOBAL_LOG } } - - $mvn = Get-MavenCommand -RepoDir (Get-Location).Path - Log-Line "⚙️ Running '$mvn clean'" $GLOBAL_LOG - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - $cleanExit = Invoke-External -Exe $mvn -Arguments @("clean") -LogFile $LogFile -WorkingDirectory (Get-Location).Path - if ($cleanExit -ne 0) { - Log-Line "❌ 'mvn clean' FAILED. See $LogFile for details." $GLOBAL_LOG - throw "Maven clean failed" - } - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - Print-TestsRunningSection -Command "mvn test -P sample-test" - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Setup: Mobile (Python) ===== -function Setup-Mobile-Python { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-pytest-appium-app-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - Push-Location $TARGET - try { - if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } - $venv = Join-Path $TARGET "venv" - if (!(Test-Path $venv)) { - [void](Invoke-Py -Arguments @("-m","venv",$venv) -LogFile $LogFile -WorkingDirectory $TARGET) - } - $venvPy = Get-VenvPython -VenvDir $venv - - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $env:BROWSERSTACK_APP = $APP_URL - - $originalPlatform = $APP_PLATFORM - $localFlag = if ($UseLocal) { "true" } else { "false" } - - # Generate platform YAMLs - $script:APP_PLATFORM = "android" - $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform - $androidYmlPath = Join-Path $TARGET "android\browserstack.yml" -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: now-windows-app-python-pytest -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlAndroid -"@ | Set-Content $androidYmlPath - - $script:APP_PLATFORM = "ios" - $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform - $iosYmlPath = Join-Path $TARGET "ios\browserstack.yml" -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: now-windows-app-python-pytest -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlIos -"@ | Set-Content $iosYmlPath - - $script:APP_PLATFORM = $originalPlatform - Log-Line "✅ Wrote platform YAMLs" $GLOBAL_LOG - - $runDirName = if ($APP_PLATFORM -eq "ios") { "ios" } else { "android" } - $runDir = Join-Path $TARGET $runDirName - $platformYaml = if ($runDirName -eq "ios") { $platformYamlIos } else { $platformYamlAndroid } - - Report-BStackLocalStatus -LocalFlag $UseLocal - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: now-windows-app-python-pytest" $GLOBAL_LOG - Log-Line "ℹ️ Native App Endpoint: $APP_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $localFlag" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms:" $GLOBAL_LOG - $platformYaml -split "`n" | ForEach-Object { if ($_.Trim()) { Log-Line " $_" $GLOBAL_LOG } } - - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Print-TestsRunningSection -Command "cd $runDirName && browserstack-sdk pytest -s bstack_sample.py" - - Push-Location $runDir - try { - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - } finally { - Pop-Location - } - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Setup: Mobile (NodeJS) ===== -function Setup-Mobile-NodeJS { - param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - - $REPO = "now-webdriverio-appium-app-browserstack" - $TARGET = Join-Path $GLOBAL_DIR $REPO - - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null - if (Test-Path $TARGET) { - Remove-Item -Path $TARGET -Recurse -Force - } - - Log-Line "ℹ️ Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) - - $testDir = Join-Path $TARGET "test" - Push-Location $testDir - try { - Log-Line "⚙️ Running 'npm install'" $GLOBAL_LOG - Log-Line "ℹ️ Installing dependencies" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir) - Log-Line "✅ Dependencies installed" $GLOBAL_LOG - - # Generate capabilities JSON and set as environment variable (like Mac) - $capsJson = Generate-Mobile-Caps-Json-String -MaxTotalParallels $ParallelsPerPlatform - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - $env:BSTACK_CAPS_JSON = $capsJson - $env:BROWSERSTACK_APP = $APP_URL - $env:BROWSERSTACK_BUILD_NAME = "now-windows-app-nodejs-wdio" - $env:BROWSERSTACK_PROJECT_NAME = "NOW-Mobile-Test" - $env:BROWSERSTACK_LOCAL = "true" - - # Validate Environment Variables - Log-Section "Validate Environment Variables" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Build: $($env:BROWSERSTACK_BUILD_NAME)" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Project: $($env:BROWSERSTACK_PROJECT_NAME)" $GLOBAL_LOG - Log-Line "ℹ️ Native App Endpoint: $APP_URL" $GLOBAL_LOG - Log-Line "ℹ️ BrowserStack Local Flag: $($env:BROWSERSTACK_LOCAL)" $GLOBAL_LOG - Log-Line "ℹ️ Parallels per platform: $ParallelsPerPlatform" $GLOBAL_LOG - Log-Line "ℹ️ Platforms: $capsJson" $GLOBAL_LOG - - Print-TestsRunningSection -Command "npm run test" - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) - Log-Line "ℹ️ Run Test command completed." $GLOBAL_LOG - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Helper Functions ===== -function Report-BStackLocalStatus { - param([bool]$LocalFlag) - if ($LocalFlag) { - Log-Line "✅ Target website is behind firewall. BrowserStack Local enabled for this run." $GLOBAL_LOG - } else { - Log-Line "✅ Target website is publicly resolvable. BrowserStack Local disabled for this run." $GLOBAL_LOG - } -} - -function Print-TestsRunningSection { - param([string]$Command) - Log-Section "🚀 Running Tests: $Command" $GLOBAL_LOG - Log-Line "ℹ️ Executing: Test run command. This could take a few minutes..." $GLOBAL_LOG - Log-Line "ℹ️ You can monitor test progress here: 🔗 https://automation.browserstack.com/" $GLOBAL_LOG -} - -function Identify-RunStatus-Java { - param([string]$LogFile) - if (!(Test-Path $LogFile)) { return $false } - $content = Get-Content $LogFile -Raw - $match = [regex]::Match($content, 'Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)') - if (-not $match.Success) { return $false } - $passed = [int]$match.Groups[1].Value - ([int]$match.Groups[2].Value + [int]$match.Groups[3].Value + [int]$match.Groups[4].Value) - if ($passed -gt 0) { - Log-Line "✅ Success: $passed test(s) passed." $GLOBAL_LOG - return $true - } - return $false -} - -function Identify-RunStatus-Python { - param([string]$LogFile) - if (!(Test-Path $LogFile)) { return $false } - $content = Get-Content $LogFile -Raw - $matches = [regex]::Matches($content, '(\d+)\s+passed') - $passedSum = 0 - foreach ($m in $matches) { $passedSum += [int]$m.Groups[1].Value } - if ($passedSum -gt 0) { - Log-Line "✅ Success: $passedSum test(s) passed." $GLOBAL_LOG - return $true - } - return $false -} - -function Identify-RunStatus-NodeJS { - param([string]$LogFile) - if (!(Test-Path $LogFile)) { return $false } - $content = Get-Content $LogFile -Raw - $match = [regex]::Match($content, '(\d+)\s+pass') - if ($match.Success -and [int]$match.Groups[1].Value -gt 0) { - Log-Line "✅ Success: $($match.Groups[1].Value) test(s) passed." $GLOBAL_LOG - return $true - } - return $false -} - -# ===== Setup Environment Wrapper ===== -function Setup-Environment { - param( - [Parameter(Mandatory)][string]$SetupType, - [Parameter(Mandatory)][string]$TechStack, - [string]$RunMode = "--interactive" - ) - - Log-Section "📦 Project Setup" $GLOBAL_LOG - - $maxParallels = if ($SetupType -match "web") { $TEAM_PARALLELS_MAX_ALLOWED_WEB } else { $TEAM_PARALLELS_MAX_ALLOWED_MOBILE } - Log-Line "Team max parallels: $maxParallels" $GLOBAL_LOG - - $localFlag = $false - $totalParallels = [int]([Math]::Floor($maxParallels * $PARALLEL_PERCENTAGE)) - if ($totalParallels -lt 1) { $totalParallels = 1 } - - if ($RunMode -match "--silent" -and $totalParallels -gt 5) { - $originalParallels = $totalParallels - $totalParallels = 5 - Log-Line "ℹ️ Silent mode: capping parallels per platform to $totalParallels (requested $originalParallels)" $GLOBAL_LOG - } - - Log-Line "Total parallels allocated: $totalParallels" $GLOBAL_LOG - - $success = $false - $logFile = Get-RunLogFile - - switch ($TechStack) { - "Java" { - if ($SetupType -match "web") { - Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-Java -LogFile $logFile - } else { - Setup-Mobile-Java -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-Java -LogFile $logFile - } - } - "Python" { - if ($SetupType -match "web") { - Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-Python -LogFile $logFile - } else { - Setup-Mobile-Python -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-Python -LogFile $logFile - } - } - "NodeJS" { - if ($SetupType -match "web") { - Setup-Web-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-NodeJS -LogFile $logFile - } else { - Setup-Mobile-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $totalParallels -LogFile $logFile - $success = Identify-RunStatus-NodeJS -LogFile $logFile - } - } - default { - Log-Line "⚠️ Unknown TECH_STACK: $TechStack" $GLOBAL_LOG - return - } - } - - Log-Section "✅ Results" $GLOBAL_LOG - if ($success) { - Log-Line "✅ $SetupType setup succeeded." $GLOBAL_LOG - } else { - Log-Line "❌ $SetupType setup ended. Check $logFile for details." $GLOBAL_LOG - } -} - -# ===== Run Setup Wrapper (like Mac's run_setup) ===== -function Run-Setup { - param( - [string]$TestType, - [string]$TechStack, - [string]$RunMode = "--interactive" - ) - Setup-Environment -SetupType $TestType -TechStack $TechStack -RunMode $RunMode -} - - diff --git a/win/logging-utils.ps1 b/win/logging-utils.ps1 deleted file mode 100644 index 68da254..0000000 --- a/win/logging-utils.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -# Logging helpers shared across the Windows BrowserStack NOW scripts. - -if (-not (Get-Variable -Name NOW_RUN_LOG_FILE -Scope Script -ErrorAction SilentlyContinue)) { - $script:NOW_RUN_LOG_FILE = "" -} - -function Set-RunLogFile { - param([string]$Path) - $script:NOW_RUN_LOG_FILE = $Path - if ($Path) { - $env:NOW_RUN_LOG_FILE = $Path - } else { - Remove-Item Env:NOW_RUN_LOG_FILE -ErrorAction SilentlyContinue - } -} - -function Get-RunLogFile { - return $script:NOW_RUN_LOG_FILE -} - -function Log-Line { - param( - [Parameter(Mandatory=$true)][AllowEmptyString()][string]$Message, - [string]$DestFile - ) - if (-not $DestFile) { - $DestFile = Get-RunLogFile - } - - $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - $line = "[$ts] $Message" - Write-Host $line - if ($DestFile) { - $dir = Split-Path -Parent $DestFile - if ($dir -and !(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } - Add-Content -Path $DestFile -Value $line - } -} - -function Log-Section { - param( - [Parameter(Mandatory)][AllowEmptyString()][string]$Title, - [string]$DestFile - ) - $divider = "───────────────────────────────────────────────" - Log-Line "" $DestFile - Log-Line $divider $DestFile - Log-Line ("{0}" -f $Title) $DestFile - Log-Line $divider $DestFile -} - -function Log-Info { param([string]$Message,[string]$DestFile) Log-Line ("ℹ️ $Message") $DestFile } -function Log-Success { param([string]$Message,[string]$DestFile) Log-Line ("✅ $Message") $DestFile } -function Log-Warn { param([string]$Message,[string]$DestFile) Log-Line ("⚠️ $Message") $DestFile } -function Log-Error { param([string]$Message,[string]$DestFile) Log-Line ("❌ $Message") $DestFile } - diff --git a/win/run.ps1 b/win/run.ps1 index 8003d2b..ce68703 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -1,12 +1,4 @@ -#requires -version 5.0 -<# - BrowserStack Onboarding (PowerShell 5.0, GUI) - - Full parity port of macOS bash run.sh - - Uses WinForms for GUI prompts - - Logs to %USERPROFILE%\.browserstack\NOW\logs -#> - -param( +param( [string]$RunMode = "--interactive", [string]$TT, [string]$TSTACK, @@ -15,103 +7,18 @@ param( [string]$AppPlatform ) -Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -Add-Type -AssemblyName System.Windows.Forms -Add-Type -AssemblyName System.Drawing - -# ===== Import utilities (like Mac's source commands) ===== -$script:PSScriptRootResolved = Split-Path -Parent $MyInvocation.MyCommand.Path -. (Join-Path $PSScriptRootResolved "logging-utils.ps1") -. (Join-Path $PSScriptRootResolved "common-utils.ps1") -. (Join-Path $PSScriptRootResolved "device-machine-allocation.ps1") -. (Join-Path $PSScriptRootResolved "user-interaction.ps1") -. (Join-Path $PSScriptRootResolved "env-prequisite-checks.ps1") -. (Join-Path $PSScriptRootResolved "env-setup-run.ps1") - -# ===== Main flow (baseline steps then run) ===== -try { - # Get test type and tech stack before logging - if ($RunMode -match "--silent|--debug") { - $textInfo = (Get-Culture).TextInfo - $ttCandidate = if ($TT) { $TT } else { $env:TEST_TYPE } - if ([string]::IsNullOrWhiteSpace($ttCandidate)) { throw "TEST_TYPE is required in silent/debug mode." } - $tsCandidate = if ($TSTACK) { $TSTACK } else { $env:TECH_STACK } - if ([string]::IsNullOrWhiteSpace($tsCandidate)) { throw "TECH_STACK is required in silent/debug mode." } - $script:TEST_TYPE = $textInfo.ToTitleCase($ttCandidate.ToLowerInvariant()) - $script:TECH_STACK = $textInfo.ToTitleCase($tsCandidate.ToLowerInvariant()) - if ($TEST_TYPE -notin @("Web","App")) { throw "TEST_TYPE must be either 'Web' or 'App'." } - if ($TECH_STACK -notin @("Java","Python","NodeJS")) { throw "TECH_STACK must be one of: Java, Python, NodeJS." } - } else { - Resolve-Test-Type -RunMode $RunMode -CliValue $TT - Resolve-Tech-Stack -RunMode $RunMode -CliValue $TSTACK - } - - # Setup log file path AFTER selections - $logFileName = "{0}_{1}_run_result.log" -f $TEST_TYPE.ToLowerInvariant(), $TECH_STACK.ToLowerInvariant() - $logFile = Join-Path $LOG_DIR $logFileName - if (!(Test-Path $LOG_DIR)) { - New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null - } - '' | Out-File -FilePath $logFile -Encoding UTF8 - Set-RunLogFile $logFile - $script:GLOBAL_LOG = $logFile - $script:WEB_LOG = $logFile - $script:MOBILE_LOG = $logFile - Log-Line "ℹ️ Log file path: $logFile" $GLOBAL_LOG - - # Setup Summary Header - Log-Section "🧭 Setup Summary – BrowserStack NOW" $GLOBAL_LOG - Log-Line "ℹ️ Timestamp: $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))" $GLOBAL_LOG - Log-Line "ℹ️ Run Mode: $RunMode" $GLOBAL_LOG - Log-Line "ℹ️ Selected Testing Type: $TEST_TYPE" $GLOBAL_LOG - Log-Line "ℹ️ Selected Tech Stack: $TECH_STACK" $GLOBAL_LOG - - # Setup workspace and get credentials BEFORE app upload - Setup-Workspace - Ask-BrowserStack-Credentials -RunMode $RunMode -UsernameFromEnv $env:BROWSERSTACK_USERNAME -AccessKeyFromEnv $env:BROWSERSTACK_ACCESS_KEY - - # NOW handle URL/App upload (requires credentials) - Perform-NextSteps-BasedOnTestType -TestType $TEST_TYPE -RunMode $RunMode -TestUrl $TestUrl -AppPath $AppPath -AppPlatform $AppPlatform - - # Platform & Tech Stack section - Log-Section "⚙️ Platform & Tech Stack" $GLOBAL_LOG - Log-Line "ℹ️ Platform: $TEST_TYPE" $GLOBAL_LOG - Log-Line "ℹ️ Tech Stack: $TECH_STACK" $GLOBAL_LOG - - # System Prerequisites Check - Log-Section "🧩 System Prerequisites Check" $GLOBAL_LOG - Validate-Tech-Stack - - # Account & Plan Details - Log-Section "☁️ Account & Plan Details" $GLOBAL_LOG - Fetch-Plan-Details -TestType $TEST_TYPE - - Log-Line "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 - Log-Line "Checking proxy in environment" $GLOBAL_LOG - Set-ProxyInEnv -Username $BROWSERSTACK_USERNAME -AccessKey $BROWSERSTACK_ACCESS_KEY - - # Getting Ready section - Log-Section "🧹 Getting Ready" $GLOBAL_LOG - Log-Line "ℹ️ Detected Operating system: Windows" $GLOBAL_LOG - Log-Line "ℹ️ Clearing old logs from NOW Home Directory inside .browserstack" $GLOBAL_LOG - Clear-OldLogs - - Log-Line "ℹ️ Starting $TEST_TYPE setup for $TECH_STACK" $GLOBAL_LOG - - # Run the setup - Run-Setup -TestType $TEST_TYPE -TechStack $TECH_STACK -RunMode $RunMode +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$commonScript = Join-Path $scriptDir "..\common\win\run.ps1" -} catch { - Log-Line " " $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG - Log-Line "❌ EXECUTION FAILED" $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG - Log-Line "Error: $($_.Exception.Message)" $GLOBAL_LOG - Log-Line "Check logs for details:" $GLOBAL_LOG - Log-Line (" Run Log: {0}" -f (Get-RunLogFile)) $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG - throw +# Resolve to absolute path +if (Test-Path $commonScript) { + $commonScript = (Resolve-Path $commonScript).Path +} else { + Write-Error "Common script not found at $commonScript" + exit 1 } +# Execute common script with arguments +& $commonScript -RunMode $RunMode -TT $TT -TSTACK $TSTACK -TestUrl $TestUrl -AppPath $AppPath -AppPlatform $AppPlatform