From 14aa9ebe47d07d8dcb8be0233d4aad85aeec96fe Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 13:39:50 +0530 Subject: [PATCH 01/36] win changes init --- win/common-utils.ps1 | 282 +++++ win/device-machine-allocation.ps1 | 295 ++++++ win/env-prequisite-checks.ps1 | 75 ++ win/env-setup-run.ps1 | 592 +++++++++++ win/logging-utils.ps1 | 31 + win/proxy-check.ps1 | 135 --- win/run.ps1 | 1592 +---------------------------- win/user-interaction.ps1 | 330 ++++++ 8 files changed, 1612 insertions(+), 1720 deletions(-) create mode 100644 win/common-utils.ps1 create mode 100644 win/device-machine-allocation.ps1 create mode 100644 win/env-prequisite-checks.ps1 create mode 100644 win/env-setup-run.ps1 create mode 100644 win/logging-utils.ps1 delete mode 100644 win/proxy-check.ps1 create mode 100644 win/user-interaction.ps1 diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 new file mode 100644 index 0000000..9dc7883 --- /dev/null +++ b/win/common-utils.ps1 @@ -0,0 +1,282 @@ +# ============================================== +# šŸ› ļø COMMON UTILITIES +# ============================================== + +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 already exists: $GLOBAL_DIR" $GLOBAL_LOG + } +} + +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) +} + +# Run external tools capturing stdout/stderr without throwing on 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 ' ' + + # .cmd/.bat need to be invoked via cmd.exe when UseShellExecute=false + $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 + + # Stream output to log file in real-time if LogFile is specified + if ($LogFile) { + # Ensure the log file directory exists + $logDir = Split-Path -Parent $LogFile + if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } + + # Create script blocks to handle output streaming + $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 + } + } + + # Register events to capture output line by line as it's produced + $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() + + # Clean up event handlers + Unregister-Event -SourceIdentifier $stdoutEvent.Name + Unregister-Event -SourceIdentifier $stderrEvent.Name + Remove-Job -Id $stdoutEvent.Id -Force + Remove-Job -Id $stderrEvent.Id -Force + } else { + # If no log file, just read all output at once (original behavior) + [void]$p.Start() + $stdout = $p.StandardOutput.ReadToEnd() + $stderr = $p.StandardError.ReadToEnd() + $p.WaitForExit() + } + + return $p.ExitCode +} + +# Return a Maven executable path or wrapper for a given repo directory +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." +} + +# Get the python.exe inside a Windows venv +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" +} + +# Detect a working Python interpreter and set $PY_CMD accordingly +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." +} + +# Invoke Python with arguments using the detected interpreter +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) +} + +# Check if IP is private +function Test-PrivateIP { + param([string]$IP) + # If IP resolution failed (empty), assume it's a public domain + # BrowserStack Local should only be enabled for confirmed private IPs + 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 +} + +# Check if domain is private +function Test-DomainPrivate { + $domain = $CX_TEST_URL -replace '^https?://', '' -replace '/.*$', '' + Log-Line "Website domain: $domain" $GLOBAL_LOG + $env:NOW_WEB_DOMAIN = $CX_TEST_URL + + # Resolve domain using Resolve-DnsName (more reliable than nslookup) + $IP_ADDRESS = "" + try { + # Try using Resolve-DnsName first (Windows PowerShell 5.1+) + $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 { + # Fallback to nslookup if Resolve-DnsName fails + try { + $nslookupOutput = nslookup $domain 2>&1 | Out-String + # Extract IP addresses from nslookup output (match IPv4 pattern) + 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) +} + +# ===== Fetch plan details ===== +function Fetch-Plan-Details { + Log-Line "ā„¹ļø Fetching BrowserStack Plan Details..." $GLOBAL_LOG + $auth = Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY + $headers = @{ Authorization = $auth } + + if ($TEST_TYPE -in @("Web","Both")) { + 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 ($TEST_TYPE -in @("App","Both")) { + 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 ( ($TEST_TYPE -eq "Web" -and -not $WEB_PLAN_FETCHED) -or + ($TEST_TYPE -eq "App" -and -not $MOBILE_PLAN_FETCHED) -or + ($TEST_TYPE -eq "Both" -and -not ($WEB_PLAN_FETCHED -or $MOBILE_PLAN_FETCHED)) ) { + Log-Line "āŒ Unauthorized to fetch required plan(s) or failed request(s). Exiting." $GLOBAL_LOG + throw "Plan fetch failed" + } +} diff --git a/win/device-machine-allocation.ps1 b/win/device-machine-allocation.ps1 new file mode 100644 index 0000000..90c04dc --- /dev/null +++ b/win/device-machine-allocation.ps1 @@ -0,0 +1,295 @@ +# ============================================== +# šŸ“± DEVICE & MACHINE ALLOCATION +# ============================================== + +# ===== 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) + $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 } + # If APP_PLATFORM is 'all', include both ios and android (no filtering) + } + + $items += [pscustomobject]@{ + 'bstack:options' = @{ + deviceName = $deviceName + osVersion = "${platformVer}.0" + } + } + $count++ + if ($count -ge $max) { break } + } + + # Convert to JSON + $json = ($items | ConvertTo-Json -Depth 5) + + # Write to file + Set-ContentNoBom -Path $OutputFile -Value $json + + 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 } + } + + # Convert to JSON and remove outer brackets to match macOS behavior + # The test code adds brackets: JSON.parse("[" + process.env.BSTACK_CAPS_JSON + "]") + $json = ($items | ConvertTo-Json -Depth 5) + + # Remove leading [ and trailing ] + if ($json.StartsWith('[')) { + $json = $json.Substring(1) + } + if ($json.EndsWith(']')) { + $json = $json.Substring(0, $json.Length - 1) + } + + # Trim any leading/trailing whitespace + $json = $json.Trim() + + return $json +} diff --git a/win/env-prequisite-checks.ps1 b/win/env-prequisite-checks.ps1 new file mode 100644 index 0000000..0ffb730 --- /dev/null +++ b/win/env-prequisite-checks.ps1 @@ -0,0 +1,75 @@ +# ============================================== +# 🧩 ENVIRONMENT PREREQUISITE CHECKS +# ============================================== + +function Validate-Tech-Stack { + Log-Line "ā„¹ļø Checking prerequisites for $script:TECH_STACK" $GLOBAL_LOG + switch ($script:TECH_STACK) { + "Java" { + Log-Line "šŸ” Checking if 'java' command exists..." $GLOBAL_LOG + if (-not (Get-Command java -ErrorAction SilentlyContinue)) { + Log-Line "āŒ Java command not found in PATH." $GLOBAL_LOG + throw "Java not found" + } + Log-Line "šŸ” Checking if Java runs correctly..." $GLOBAL_LOG + $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" { + Log-Line "šŸ” Checking if 'python3' command exists..." $GLOBAL_LOG + try { + Set-PythonCmd + Log-Line "šŸ” Checking if Python3 runs correctly..." $GLOBAL_LOG + $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" { + Log-Line "šŸ” Checking if 'node' command exists..." $GLOBAL_LOG + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Log-Line "āŒ Node.js command not found in PATH." $GLOBAL_LOG + throw "Node not found" + } + Log-Line "šŸ” Checking if 'npm' command exists..." $GLOBAL_LOG + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + Log-Line "āŒ npm command not found in PATH." $GLOBAL_LOG + throw "npm not found" + } + Log-Line "šŸ” Checking if Node.js runs correctly..." $GLOBAL_LOG + $nodeVer = & node -v 2>&1 + if (-not $nodeVer) { + Log-Line "āŒ Node.js exists but failed to run." $GLOBAL_LOG + throw "Node.js invocation failed" + } + Log-Line "šŸ” Checking if npm runs correctly..." $GLOBAL_LOG + $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 selected: $script:TECH_STACK" $GLOBAL_LOG; throw "Unknown tech stack" } + } + Log-Line "āœ… Prerequisites validated for $script:TECH_STACK" $GLOBAL_LOG +} + +# fix Python branch without ternary +function Get-PythonCmd { + if (Get-Command python3 -ErrorAction SilentlyContinue) { return "python3" } + return "python" +} diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 new file mode 100644 index 0000000..9d56988 --- /dev/null +++ b/win/env-setup-run.ps1 @@ -0,0 +1,592 @@ +# ============================================== +# āš™ļø SETUP & RUN +# ============================================== + +# ===== 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 repo $REPO into $TARGET" $GLOBAL_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + + Push-Location $TARGET + try { + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } + + # Log local flag status + if ($UseLocal) { + Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG + } else { + Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG + } + + # Generate YAML config in the correct location + Log-Line "🧩 Generating YAML config (browserstack.yml)" $GLOBAL_LOG + $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB + $localFlag = if ($UseLocal) { "true" } else { "false" } + + $yamlContent = @" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: testng +browserstackLocal: $localFlag +buildName: now-testng-java-web +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 + + $mvn = Get-MavenCommand -RepoDir $TARGET + Log-Line "āš™ļø Running '$mvn compile'" $GLOBAL_LOG + [void](Invoke-External -Exe $mvn -Arguments @("compile") -LogFile $LogFile -WorkingDirectory $TARGET) + + Log-Line "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG + [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET) + + } 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 + } + + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + + 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) + Log-Line "āœ… Created Python virtual environment" $GLOBAL_LOG + } + $venvPy = Get-VenvPython -VenvDir $venv + [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) + # Ensure SDK can find pytest on PATH + $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } + + # Log local flag status + if ($UseLocal) { + Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG + } else { + Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG + } + + $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" + $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB + $localFlag = if ($UseLocal) { "true" } else { "false" } + + $yamlContent = @" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: pytest +browserstackLocal: $localFlag +buildName: browserstack-sample-python-web +projectName: NOW-Web-Test +percy: true +accessibility: true +platforms: +$platforms +parallelsPerPlatform: $ParallelsPerPlatform +"@ + Set-Content "browserstack.yml" -Value $yamlContent + + Log-Line "āœ… Updated root-level browserstack.yml with platforms and credentials" $GLOBAL_LOG + + $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" + Log-Line "šŸš€ Running 'browserstack-sdk pytest -s tests/bstack-sample-test.py'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG + [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET) + + } 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 repo $REPO into $TARGET" $GLOBAL_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + + Push-Location $TARGET + try { + Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG + [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $TARGET) + + # Generate capabilities JSON + Log-Line "🧩 Generating browser/OS capabilities" $GLOBAL_LOG + $caps = Generate-Web-Caps-Json -MaxTotalParallels $ParallelsPerPlatform + + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + $env:BSTACK_CAPS_JSON = $caps + + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } + + # Log local flag status + if ($UseLocal) { + Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG + } else { + Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG + } + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + $localFlagStr = if ($UseLocal) { "true" } else { "false" } + $env:BROWSERSTACK_LOCAL = $localFlagStr + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + + Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG + [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET) + + Log-Line "āœ… Web NodeJS setup and test execution completed successfully." $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 = "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 + } + + Invoke-GitClone -Url "https://github.com/browserstack/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + + 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 + [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) + # Ensure SDK can find pytest on PATH + $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + + # Prepare platform-specific YAMLs in android/ and ios/ + $originalPlatform = $APP_PLATFORM + + $script:APP_PLATFORM = "android" + $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $localFlag = if ($UseLocal) { "true" } else { "false" } + $androidYmlPath = Join-Path $TARGET "android\browserstack.yml" + $yamlContentAndroid = @" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: pytest +browserstackLocal: $localFlag +buildName: browserstack-build-mobile +projectName: NOW-Mobile-Test +parallelsPerPlatform: $ParallelsPerPlatform +app: $APP_URL +platforms: +$platformYamlAndroid +"@ + Set-Content $androidYmlPath -Value $yamlContentAndroid + + $script:APP_PLATFORM = "ios" + $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $iosYmlPath = Join-Path $TARGET "ios\browserstack.yml" + $yamlContentIos = @" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: pytest +browserstackLocal: $localFlag +buildName: browserstack-build-mobile +projectName: NOW-Mobile-Test +parallelsPerPlatform: $ParallelsPerPlatform +app: $APP_URL +platforms: +$platformYamlIos +"@ + Set-Content $iosYmlPath -Value $yamlContentIos + + $script:APP_PLATFORM = $originalPlatform + + Log-Line "āœ… Wrote platform YAMLs to android/browserstack.yml and ios/browserstack.yml" $GLOBAL_LOG + + # Replace sample tests in both android and ios with universal, locator-free test + $testContent = @" +import pytest + + +@pytest.mark.usefixtures('setWebdriver') +class TestUniversalAppCheck: + + def test_app_health_check(self): + + # 1. Get initial app and device state (no locators) + initial_package = self.driver.current_package + initial_activity = self.driver.current_activity + initial_orientation = self.driver.orientation + + # 2. Log the captured data to BrowserStack using 'annotate' + log_data = f"Initial State: Package='{initial_package}', Activity='{initial_activity}', Orientation='{initial_orientation}'" + self.driver.execute_script( + 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' + ) + + # 3. Perform a locator-free action: change device orientation + self.driver.orientation = 'LANDSCAPE' + + # 4. Perform locator-free assertions + assert self.driver.orientation == 'LANDSCAPE' + + # 5. Log the successful state change + self.driver.execute_script( + 'browserstack_executor: {"action": "annotate", "arguments": {"data": "Successfully changed orientation to LANDSCAPE", "level": "info"}}' + ) + + # 6. Set the final session status to 'passed' + self.driver.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "App state verified and orientation changed!"}}' + ) +"@ + $androidTestPath = Join-Path $TARGET "android\bstack_sample.py" + $iosTestPath = Join-Path $TARGET "ios\bstack_sample.py" + Set-ContentNoBom -Path $androidTestPath -Value $testContent + Set-ContentNoBom -Path $iosTestPath -Value $testContent + + # Decide which directory to run based on APP_PLATFORM (default to android) + $runDirName = "android" + if ($APP_PLATFORM -eq "ios") { + $runDirName = "ios" + } + $runDir = Join-Path $TARGET $runDirName + + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } + + # Log local flag status + if ($UseLocal) { + Log-Line "āš ļø BrowserStack Local is ENABLED for this run." $GLOBAL_LOG + } else { + Log-Line "āš ļø BrowserStack Local is DISABLED for this run." $GLOBAL_LOG + } + + Log-Line "šŸš€ Running 'cd $runDirName && browserstack-sdk pytest -s bstack_sample.py'" $GLOBAL_LOG + $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" + Push-Location $runDir + try { + [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path) + } finally { + Pop-Location + } + + } 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 + } + + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + + Push-Location $TARGET + try { + # Navigate to platform-specific directory + 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 + + # YAML config path + $env:BROWSERSTACK_CONFIG_FILE = ".\browserstack.yml" + $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $localFlag = if ($UseLocal) { "true" } else { "false" } + + # Append to existing YAML (repo has base config) + $yamlAppend = @" +app: $APP_URL +platforms: +$platforms +"@ + Add-Content -Path $env:BROWSERSTACK_CONFIG_FILE -Value $yamlAppend + + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } + + # Log local flag status + if ($UseLocal) { + Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG + } else { + Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG + } + + $mvn = Get-MavenCommand -RepoDir (Get-Location).Path + Log-Line "āš™ļø Running '$mvn clean'" $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 "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG + [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path) + + } 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) + + Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) + + $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 + } + + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + + $testDir = Join-Path $TARGET "test" + Push-Location $testDir + try { + Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG + [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir) + + # Generate mobile capabilities JSON file + Log-Line "🧩 Generating mobile capabilities JSON" $GLOBAL_LOG + $usageFile = Join-Path $GLOBAL_DIR "usage_file.json" + [void](Generate-Mobile-Caps-Json -MaxTotalParallels $ParallelsPerPlatform -OutputFile $usageFile) + Log-Line "āœ… Created usage_file.json at: $usageFile" $GLOBAL_LOG + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + + Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG + [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) + + } finally { + Pop-Location + Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) + } +} + +# ===== Wrappers with retry ===== +function Setup-Web { + Log-Line "Starting Web setup for $TECH_STACK" $WEB_LOG + Log-Line "🌐 ========================================" $GLOBAL_LOG + Log-Line "🌐 Starting WEB Testing ($TECH_STACK)" $GLOBAL_LOG + Log-Line "🌐 ========================================" $GLOBAL_LOG + + $localFlag = $false + $attempt = 1 + $success = $true + + $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) + if ($totalParallels -lt 1) { $totalParallels = 1 } + $parallelsPerPlatform = $totalParallels + + while ($attempt -le 1) { + Log-Line "[Web Setup]" $WEB_LOG + switch ($TECH_STACK) { + "Java" { + Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG + # Add a small delay to ensure all output is flushed to disk + Start-Sleep -Milliseconds 500 + if (Test-Path $WEB_LOG) { + $content = Get-Content $WEB_LOG -Raw + if ($content -match "BUILD FAILURE") { + $success = $false + } + } + } + "Python" { + Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG + # Add a small delay to ensure all output is flushed to disk + Start-Sleep -Milliseconds 500 + if (Test-Path $WEB_LOG) { + $content = Get-Content $WEB_LOG -Raw + if ($content -match "BUILD FAILURE") { + $success = $false + } + } + } + "NodeJS" { + Setup-Web-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG + # Add a small delay to ensure all output is flushed to disk + Start-Sleep -Milliseconds 500 + if (Test-Path $WEB_LOG) { + $content = Get-Content $WEB_LOG -Raw + if ($content -match "([1-9][0-9]*) passed, 0 failed") { + $success = $false + } + } + } + default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $WEB_LOG; return } + } + + if ($success) { + Log-Line "āœ… Web setup succeeded." $WEB_LOG + Log-Line "āœ… WEB Testing completed successfully" $GLOBAL_LOG + Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG + break + } else { + Log-Line "āŒ Web setup ended without success; check $WEB_LOG for details" $WEB_LOG + Log-Line "āŒ WEB Testing completed with errors" $GLOBAL_LOG + Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG + break + } + } +} + + +function Setup-Mobile { + Log-Line "Starting Mobile setup for $TECH_STACK" $MOBILE_LOG + Log-Line "šŸ“± ========================================" $GLOBAL_LOG + Log-Line "šŸ“± Starting MOBILE APP Testing ($TECH_STACK)" $GLOBAL_LOG + Log-Line "šŸ“± ========================================" $GLOBAL_LOG + + $localFlag = $true + $attempt = 1 + $success = $false + + $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE)) + if ($totalParallels -lt 1) { $totalParallels = 1 } + $parallelsPerPlatform = $totalParallels + + while ($attempt -le 1) { + Log-Line "[Mobile Setup Attempt $attempt] browserstackLocal: $localFlag" $MOBILE_LOG + switch ($TECH_STACK) { + "Java" { Setup-Mobile-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } + "Python" { Setup-Mobile-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } + "NodeJS" { Setup-Mobile-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } + default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $MOBILE_LOG; return } + } + + # Add a small delay to ensure all output is flushed to disk (especially important for Java) + Start-Sleep -Milliseconds 500 + + if (!(Test-Path $MOBILE_LOG)) { + $content = "" + } else { + $content = Get-Content $MOBILE_LOG -Raw + } + + $LOCAL_FAILURE = $false + $SETUP_FAILURE = $false + + foreach ($p in $MOBILE_LOCAL_ERRORS) { if ($p -and ($content -match $p)) { $LOCAL_FAILURE = $true; break } } + foreach ($p in $MOBILE_SETUP_ERRORS) { if ($p -and ($content -match $p)) { $SETUP_FAILURE = $true; break } } + + # Check for BrowserStack link (success indicator) + if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { + $success = $true + } + + if ($success) { + Log-Line "āœ… Mobile setup succeeded" $MOBILE_LOG + Log-Line "āœ… MOBILE APP Testing completed successfully" $GLOBAL_LOG + Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG + break + } elseif ($LOCAL_FAILURE -and $attempt -eq 1) { + $localFlag = $false + $attempt++ + Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $MOBILE_LOG + Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $GLOBAL_LOG + } elseif ($SETUP_FAILURE) { + Log-Line "āŒ Mobile test failed due to setup error. Check logs at: $MOBILE_LOG" $MOBILE_LOG + Log-Line "āŒ MOBILE APP Testing failed due to setup error" $GLOBAL_LOG + Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG + break + } else { + Log-Line "āŒ Mobile setup ended without success; check $MOBILE_LOG for details" $MOBILE_LOG + Log-Line "āŒ MOBILE APP Testing completed with errors" $GLOBAL_LOG + Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG + break + } + } +} diff --git a/win/logging-utils.ps1 b/win/logging-utils.ps1 new file mode 100644 index 0000000..08599a0 --- /dev/null +++ b/win/logging-utils.ps1 @@ -0,0 +1,31 @@ +# ============================================== +# šŸŖ„ LOGGING HELPERS +# ============================================== + +function Log-Line { + param( + [Parameter(Mandatory=$true)][string]$Message, + [string]$DestFile + ) + $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 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! " +} diff --git a/win/proxy-check.ps1 b/win/proxy-check.ps1 deleted file mode 100644 index bc47faa..0000000 --- a/win/proxy-check.ps1 +++ /dev/null @@ -1,135 +0,0 @@ -#requires -version 5.0 -<# - BrowserStack Proxy Detection and Validation - - Detects proxy from environment variables - - Tests BrowserStack API connectivity through proxy - - Exports PROXY_HOST and PROXY_PORT if successful -#> - -param( - [string]$BrowserStackUsername, - [string]$BrowserStackAccessKey -) - -$ErrorActionPreference = 'Continue' - -# Test URL for connectivity check -$TEST_URL = "https://www.browserstack.com/automate/browsers.json" - -# Function to parse proxy URL -function Parse-ProxyUrl { - param([string]$ProxyUrl) - - if ([string]::IsNullOrWhiteSpace($ProxyUrl)) { - return $null - } - - # Remove protocol (http:// or https://) - $cleaned = $ProxyUrl -replace '^https?://', '' - - # Remove credentials if present (user:pass@) - if ($cleaned -match '@') { - $cleaned = $cleaned.Substring($cleaned.IndexOf('@') + 1) - } - - # Extract host and port - if ($cleaned -match '^([^:]+):(\d+)') { - return @{ - Host = $matches[1] - Port = $matches[2] - } - } elseif ($cleaned -match '^([^:]+)') { - # No port specified, use default - return @{ - Host = $matches[1] - Port = "8080" # default proxy port - } - } - - return $null -} - -# Detect proxy from environment variables (case-insensitive) -$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 } - -# Reset output variables -$env:PROXY_HOST = "" -$env:PROXY_PORT = "" - -# If no proxy configured, exit early -if ([string]::IsNullOrWhiteSpace($PROXY)) { - Write-Host "No proxy found in environment. Clearing proxy host and port variables." - $env:PROXY_HOST = "" - $env:PROXY_PORT = "" - exit 0 -} - -Write-Host "Proxy detected: $PROXY" - -# Parse proxy URL -$proxyInfo = Parse-ProxyUrl -ProxyUrl $PROXY -if (-not $proxyInfo) { - Write-Host "āŒ Failed to parse proxy URL: $PROXY" - $env:PROXY_HOST = "" - $env:PROXY_PORT = "" - exit 1 -} - -Write-Host "Testing reachability via proxy..." -Write-Host " Proxy Host: $($proxyInfo.Host)" -Write-Host " Proxy Port: $($proxyInfo.Port)" - -# Encode BrowserStack credentials in Base64 -$base64Creds = "" -if (-not [string]::IsNullOrWhiteSpace($BrowserStackUsername) -and - -not [string]::IsNullOrWhiteSpace($BrowserStackAccessKey)) { - $pair = "${BrowserStackUsername}:${BrowserStackAccessKey}" - $bytes = [System.Text.Encoding]::UTF8.GetBytes($pair) - $base64Creds = [System.Convert]::ToBase64String($bytes) -} - -# Test connectivity through proxy -try { - $proxyUri = "http://$($proxyInfo.Host):$($proxyInfo.Port)" - - # Create web request with proxy - $webProxy = New-Object System.Net.WebProxy($proxyUri) - $webClient = New-Object System.Net.WebClient - $webClient.Proxy = $webProxy - - # Add authorization header if credentials provided - if (-not [string]::IsNullOrWhiteSpace($base64Creds)) { - $webClient.Headers.Add("Authorization", "Basic $base64Creds") - } - - # Attempt to download (with timeout) - $null = $webClient.DownloadString($TEST_URL) - - # If we reach here, the request succeeded - Write-Host "āœ… Reachable. HTTP 200" - Write-Host "Exporting PROXY_HOST=$($proxyInfo.Host)" - Write-Host "Exporting PROXY_PORT=$($proxyInfo.Port)" - - $env:PROXY_HOST = $proxyInfo.Host - $env:PROXY_PORT = $proxyInfo.Port - - exit 0 - -} catch { - $statusCode = "Unknown" - if ($_.Exception.InnerException -and $_.Exception.InnerException.Response) { - $statusCode = [int]$_.Exception.InnerException.Response.StatusCode - } - - Write-Host "āŒ Not reachable (HTTP $statusCode). Clearing variables." - Write-Host " Error: $($_.Exception.Message)" - - $env:PROXY_HOST = "" - $env:PROXY_PORT = "" - - exit 0 # Exit successfully even if proxy check fails -} - diff --git a/win/run.ps1 b/win/run.ps1 index 3ba470d..9eb9727 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -57,1591 +57,13 @@ $WEB_LOCAL_ERRORS = @("") $MOBILE_SETUP_ERRORS= @("") $MOBILE_LOCAL_ERRORS= @("") -# ===== 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 - -# ===== Helpers ===== -function Log-Line { - param( - [Parameter(Mandatory=$true)][string]$Message, - [string]$DestFile - ) - $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - $line = "[$ts] $Message" - Write-Host $line - if ($DestFile) { - $dir = Split-Path -Parent $DestFile - if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } - Add-Content -Path $DestFile -Value $line - } -} - -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 already exists: $GLOBAL_DIR" $GLOBAL_LOG - } -} - -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) -} - -# Run external tools capturing stdout/stderr without throwing on 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 ' ' - - # .cmd/.bat need to be invoked via cmd.exe when UseShellExecute=false - $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 - - # Stream output to log file in real-time if LogFile is specified - if ($LogFile) { - # Ensure the log file directory exists - $logDir = Split-Path -Parent $LogFile - if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } - - # Create script blocks to handle output streaming - $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 - } - } - - # Register events to capture output line by line as it's produced - $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() - - # Clean up event handlers - Unregister-Event -SourceIdentifier $stdoutEvent.Name - Unregister-Event -SourceIdentifier $stderrEvent.Name - Remove-Job -Id $stdoutEvent.Id -Force - Remove-Job -Id $stderrEvent.Id -Force - } else { - # If no log file, just read all output at once (original behavior) - [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() - } - - return $p.ExitCode -} - -# Return a Maven executable path or wrapper for a given repo directory -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." -} - -# Get the python.exe inside a Windows venv -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" -} - -# Detect a working Python interpreter and set $PY_CMD accordingly -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." -} - -# Invoke Python with arguments using the detected interpreter -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) -} - -# Spinner function for long-running operations -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! " -} - -# Check if IP is private -function Test-PrivateIP { - param([string]$IP) - # If IP resolution failed (empty), assume it's a public domain - # BrowserStack Local should only be enabled for confirmed private IPs - 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 -} - -# Check if domain is private -function Test-DomainPrivate { - $domain = $CX_TEST_URL -replace '^https?://', '' -replace '/.*$', '' - Log-Line "Website domain: $domain" $GLOBAL_LOG - $env:NOW_WEB_DOMAIN = $CX_TEST_URL - - # Resolve domain using Resolve-DnsName (more reliable than nslookup) - $IP_ADDRESS = "" - try { - # Try using Resolve-DnsName first (Windows PowerShell 5.1+) - $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 { - # Fallback to nslookup if Resolve-DnsName fails - try { - $nslookupOutput = nslookup $domain 2>&1 | Out-String - # Extract IP addresses from nslookup output (match IPv4 pattern) - 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) -} - -# ===== GUI helpers ===== -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-ChoiceBox { - param( - [string]$Title = "Choose", - [string]$Prompt = "Select one:", - [string[]]$Choices, - [string]$DefaultChoice - ) - $form = New-Object System.Windows.Forms.Form - $form.Text = $Title - $form.Size = New-Object System.Drawing.Size(420, 240) - $form.StartPosition = "CenterScreen" - - $label = New-Object System.Windows.Forms.Label - $label.Text = $Prompt - $label.AutoSize = $true - $label.Location = New-Object System.Drawing.Point(10, 10) - $form.Controls.Add($label) - - $group = New-Object System.Windows.Forms.Panel - $group.Location = New-Object System.Drawing.Point(10, 35) - $group.Width = 380 - $startY = 10 - $spacing = 28 - - $radios = @() - [int]$i = 0 - foreach ($c in $Choices) { - $rb = New-Object System.Windows.Forms.RadioButton - $rb.Text = $c - $rb.AutoSize = $true - $rb.Location = New-Object System.Drawing.Point(10, ($startY + $i * $spacing)) - if ($c -eq $DefaultChoice) { $rb.Checked = $true } - $group.Controls.Add($rb) - $radios += $rb - $i++ - } - $group.Height = [Math]::Max(120, $startY + ($Choices.Count * $spacing) + 10) - $form.Controls.Add($group) - - $ok = New-Object System.Windows.Forms.Button - $ok.Text = "OK" - $ok.Location = New-Object System.Drawing.Point(300, ($group.Bottom + 10)) - $ok.Add_Click({ - foreach ($rb in $radios) { if ($rb.Checked) { $form.Tag = $rb.Text; break } } - $form.Close() - }) - $form.Controls.Add($ok) - - $form.Height = $ok.Bottom + 70 - $form.AcceptButton = $ok - [void]$form.ShowDialog() - return [string]$form.Tag -} - -# === NEW: Big clickable button chooser === -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 "" -} - -# ===== Baseline interactions ===== -function Ask-BrowserStack-Credentials { - $script:BROWSERSTACK_USERNAME = Show-InputBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Username:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" -DefaultText "" - if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_USERNAME)) { - Log-Line "āŒ Username empty" $GLOBAL_LOG - throw "Username is required" - } - $script:BROWSERSTACK_ACCESS_KEY = Show-PasswordBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Access Key:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" - if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_ACCESS_KEY)) { - Log-Line "āŒ Access Key empty" $GLOBAL_LOG - throw "Access Key is required" - } - Log-Line "āœ… BrowserStack credentials captured (access key hidden)" $GLOBAL_LOG -} - -# === UPDATED: click-select for Web/App/Both === -function Ask-Test-Type { - $choice = Show-ClickChoice -Title "Testing Type" ` - -Prompt "What do you want to run?" ` - -Choices @("Web","App","Both") ` - -DefaultChoice "Web" - if ([string]::IsNullOrWhiteSpace($choice)) { throw "No testing type selected" } - $script:TEST_TYPE = $choice - Log-Line "āœ… Selected Testing Type: $script:TEST_TYPE" $GLOBAL_LOG - - switch ($script:TEST_TYPE) { - "Web" { Ask-User-TestUrl } - "App" { Ask-And-Upload-App } - "Both" { Ask-User-TestUrl; Ask-And-Upload-App } - } -} - -# === UPDATED: click-select for Tech Stack === -function Ask-Tech-Stack { - $choice = Show-ClickChoice -Title "Tech Stack" ` - -Prompt "Select your installed language / framework:" ` - -Choices @("Java","Python","NodeJS") ` - -DefaultChoice "Java" - if ([string]::IsNullOrWhiteSpace($choice)) { throw "No tech stack selected" } - $script:TECH_STACK = $choice - Log-Line "āœ… Selected Tech Stack: $script:TECH_STACK" $GLOBAL_LOG -} - -function Validate-Tech-Stack { - Log-Line "ā„¹ļø Checking prerequisites for $script:TECH_STACK" $GLOBAL_LOG - switch ($script:TECH_STACK) { - "Java" { - Log-Line "šŸ” Checking if 'java' command exists..." $GLOBAL_LOG - if (-not (Get-Command java -ErrorAction SilentlyContinue)) { - Log-Line "āŒ Java command not found in PATH." $GLOBAL_LOG - throw "Java not found" - } - Log-Line "šŸ” Checking if Java runs correctly..." $GLOBAL_LOG - $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" { - Log-Line "šŸ” Checking if 'python3' command exists..." $GLOBAL_LOG - try { - Set-PythonCmd - Log-Line "šŸ” Checking if Python3 runs correctly..." $GLOBAL_LOG - $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" { - Log-Line "šŸ” Checking if 'node' command exists..." $GLOBAL_LOG - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Log-Line "āŒ Node.js command not found in PATH." $GLOBAL_LOG - throw "Node not found" - } - Log-Line "šŸ” Checking if 'npm' command exists..." $GLOBAL_LOG - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - Log-Line "āŒ npm command not found in PATH." $GLOBAL_LOG - throw "npm not found" - } - Log-Line "šŸ” Checking if Node.js runs correctly..." $GLOBAL_LOG - $nodeVer = & node -v 2>&1 - if (-not $nodeVer) { - Log-Line "āŒ Node.js exists but failed to run." $GLOBAL_LOG - throw "Node.js invocation failed" - } - Log-Line "šŸ” Checking if npm runs correctly..." $GLOBAL_LOG - $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 selected: $script:TECH_STACK" $GLOBAL_LOG; throw "Unknown tech stack" } - } - Log-Line "āœ… Prerequisites validated for $script:TECH_STACK" $GLOBAL_LOG -} -# fix Python branch without ternary -function Get-PythonCmd { - if (Get-Command python3 -ErrorAction SilentlyContinue) { return "python3" } - return "python" -} - -function Ask-User-TestUrl { - $u = 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 "" - if ([string]::IsNullOrWhiteSpace($u)) { - $script:CX_TEST_URL = $DEFAULT_TEST_URL - Log-Line "āš ļø No URL entered. Falling back to default: $script:CX_TEST_URL" $GLOBAL_LOG - } else { - $script:CX_TEST_URL = $u - Log-Line "🌐 Using custom test URL: $script:CX_TEST_URL" $GLOBAL_LOG - } -} - -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 Ask-And-Upload-App { - # First, show a choice screen for Sample App vs Browse - $appChoice = Show-ClickChoice -Title "App Selection" ` - -Prompt "Choose an app to test:" ` - -Choices @("Sample App","Browse") ` - -DefaultChoice "Sample App" - - if ([string]::IsNullOrWhiteSpace($appChoice) -or $appChoice -eq "Sample App") { - Log-Line "āš ļø Using default sample app: bs://sample.app" $GLOBAL_LOG - $script:APP_URL = "bs://sample.app" - $script:APP_PLATFORM = "all" - return - } - - # User chose "Browse", so open file picker - $path = Show-OpenFileDialog -Title "šŸ“± Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" - if ([string]::IsNullOrWhiteSpace($path)) { - Log-Line "āš ļø No app selected. Using default sample app: bs://sample.app" $GLOBAL_LOG - $script:APP_URL = "bs://sample.app" - $script:APP_PLATFORM = "all" - return - } - - $ext = [System.IO.Path]::GetExtension($path).ToLowerInvariant() - switch ($ext) { - ".apk" { $script:APP_PLATFORM = "android" } - ".ipa" { $script:APP_PLATFORM = "ios" } - default { Log-Line "āŒ Unsupported file type. Only .apk or .ipa allowed." $GLOBAL_LOG; throw "Unsupported app file" } - } - - Log-Line "ā¬†ļø Uploading $path to BrowserStack..." $GLOBAL_LOG - - # Create multipart form data manually for PowerShell 5.1 compatibility - $boundary = [System.Guid]::NewGuid().ToString() - $LF = "`r`n" - $fileBin = [System.IO.File]::ReadAllBytes($path) - $fileName = [System.IO.Path]::GetFileName($path) - - $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)) { - Log-Line "āŒ Upload failed. Response: $(ConvertTo-Json $resp -Depth 5)" $GLOBAL_LOG - throw "Upload failed" - } - $script:APP_URL = $url - Log-Line "āœ… App uploaded successfully: $script:APP_URL" $GLOBAL_LOG -} - -# ===== 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) - $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 } - # If APP_PLATFORM is 'all', include both ios and android (no filtering) - } - - $items += [pscustomobject]@{ - 'bstack:options' = @{ - deviceName = $deviceName - osVersion = "${platformVer}.0" - } - } - $count++ - if ($count -ge $max) { break } - } - - # Convert to JSON - $json = ($items | ConvertTo-Json -Depth 5) - - # Write to file - Set-ContentNoBom -Path $OutputFile -Value $json - - 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 } - } - - # Convert to JSON and remove outer brackets to match macOS behavior - # The test code adds brackets: JSON.parse("[" + process.env.BSTACK_CAPS_JSON + "]") - $json = ($items | ConvertTo-Json -Depth 5) - - # Remove leading [ and trailing ] - if ($json.StartsWith('[')) { - $json = $json.Substring(1) - } - if ($json.EndsWith(']')) { - $json = $json.Substring(0, $json.Length - 1) - } - - # Trim any leading/trailing whitespace - $json = $json.Trim() - - return $json -} - -# ===== Fetch plan details ===== -function Fetch-Plan-Details { - Log-Line "ā„¹ļø Fetching BrowserStack Plan Details..." $GLOBAL_LOG - $auth = Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY - $headers = @{ Authorization = $auth } - - if ($TEST_TYPE -in @("Web","Both")) { - 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 ($TEST_TYPE -in @("App","Both")) { - 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 ( ($TEST_TYPE -eq "Web" -and -not $WEB_PLAN_FETCHED) -or - ($TEST_TYPE -eq "App" -and -not $MOBILE_PLAN_FETCHED) -or - ($TEST_TYPE -eq "Both" -and -not ($WEB_PLAN_FETCHED -or $MOBILE_PLAN_FETCHED)) ) { - Log-Line "āŒ Unauthorized to fetch required plan(s) or failed request(s). Exiting." $GLOBAL_LOG - throw "Plan fetch failed" - } -} - -# ===== 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 repo $REPO into $TARGET" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - - Push-Location $TARGET - try { - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - # Generate YAML config in the correct location - Log-Line "🧩 Generating YAML config (browserstack.yml)" $GLOBAL_LOG - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB - $localFlag = if ($UseLocal) { "true" } else { "false" } - - $yamlContent = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: testng -browserstackLocal: $localFlag -buildName: now-testng-java-web -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 - - $mvn = Get-MavenCommand -RepoDir $TARGET - Log-Line "āš™ļø Running '$mvn compile'" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("compile") -LogFile $LogFile -WorkingDirectory $TARGET) - - Log-Line "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET) - - } 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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - 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) - Log-Line "āœ… Created Python virtual environment" $GLOBAL_LOG - } - $venvPy = Get-VenvPython -VenvDir $venv - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - # Ensure SDK can find pytest on PATH - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB - $localFlag = if ($UseLocal) { "true" } else { "false" } - -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-sample-python-web -projectName: NOW-Web-Test -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ | Set-Content "browserstack.yml" - - Log-Line "āœ… Updated root-level browserstack.yml with platforms and credentials" $GLOBAL_LOG - - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Log-Line "šŸš€ Running 'browserstack-sdk pytest -s tests/bstack-sample-test.py'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET) - - } 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 repo $REPO into $TARGET" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - - Push-Location $TARGET - try { - Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $TARGET) - - # Generate capabilities JSON - Log-Line "🧩 Generating browser/OS capabilities" $GLOBAL_LOG - $caps = Generate-Web-Caps-Json -MaxTotalParallels $ParallelsPerPlatform - - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - $env:BSTACK_CAPS_JSON = $caps - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $localFlagStr = if ($UseLocal) { "true" } else { "false" } - $env:BROWSERSTACK_LOCAL = $localFlagStr - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - - Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET) - - Log-Line "āœ… Web NodeJS setup and test execution completed successfully." $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 = "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 - } - - Invoke-GitClone -Url "https://github.com/browserstack/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - 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 - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - # Ensure SDK can find pytest on PATH - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - # Prepare platform-specific YAMLs in android/ and ios/ - $originalPlatform = $APP_PLATFORM - - $script:APP_PLATFORM = "android" - $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $localFlag = if ($UseLocal) { "true" } else { "false" } - $androidYmlPath = Join-Path $TARGET "android\browserstack.yml" -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-build-mobile -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlAndroid -"@ | Set-Content $androidYmlPath - - $script:APP_PLATFORM = "ios" - $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $iosYmlPath = Join-Path $TARGET "ios\browserstack.yml" -@" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-build-mobile -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlIos -"@ | Set-Content $iosYmlPath - - $script:APP_PLATFORM = $originalPlatform - - Log-Line "āœ… Wrote platform YAMLs to android/browserstack.yml and ios/browserstack.yml" $GLOBAL_LOG - - # Replace sample tests in both android and ios with universal, locator-free test - $testContent = @" -import pytest - - -@pytest.mark.usefixtures('setWebdriver') -class TestUniversalAppCheck: - - def test_app_health_check(self): - - # 1. Get initial app and device state (no locators) - initial_package = self.driver.current_package - initial_activity = self.driver.current_activity - initial_orientation = self.driver.orientation - - # 2. Log the captured data to BrowserStack using 'annotate' - log_data = f"Initial State: Package='{initial_package}', Activity='{initial_activity}', Orientation='{initial_orientation}'" - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' - ) - - # 3. Perform a locator-free action: change device orientation - self.driver.orientation = 'LANDSCAPE' - - # 4. Perform locator-free assertions - assert self.driver.orientation == 'LANDSCAPE' - - # 5. Log the successful state change - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "Successfully changed orientation to LANDSCAPE", "level": "info"}}' - ) - - # 6. Set the final session status to 'passed' - self.driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "App state verified and orientation changed!"}}' - ) -"@ - $androidTestPath = Join-Path $TARGET "android\bstack_sample.py" - $iosTestPath = Join-Path $TARGET "ios\bstack_sample.py" - Set-ContentNoBom -Path $androidTestPath -Value $testContent - Set-ContentNoBom -Path $iosTestPath -Value $testContent - - # Decide which directory to run based on APP_PLATFORM (default to android) - $runDirName = "android" - if ($APP_PLATFORM -eq "ios") { - $runDirName = "ios" - } - $runDir = Join-Path $TARGET $runDirName - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āš ļø BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āš ļø BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - Log-Line "šŸš€ Running 'cd $runDirName && browserstack-sdk pytest -s bstack_sample.py'" $GLOBAL_LOG - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Push-Location $runDir - try { - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - } finally { - Pop-Location - } - - } 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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - Push-Location $TARGET - try { - # Navigate to platform-specific directory - 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 - - # YAML config path - $env:BROWSERSTACK_CONFIG_FILE = ".\browserstack.yml" - $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $localFlag = if ($UseLocal) { "true" } else { "false" } - - # Append to existing YAML (repo has base config) - $yamlAppend = @" -app: $APP_URL -platforms: -$platforms -"@ - Add-Content -Path $env:BROWSERSTACK_CONFIG_FILE -Value $yamlAppend - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $mvn = Get-MavenCommand -RepoDir (Get-Location).Path - Log-Line "āš™ļø Running '$mvn clean'" $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 "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - - } 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) - - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - - $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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - - $testDir = Join-Path $TARGET "test" - Push-Location $testDir - try { - Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir) - - # Generate mobile capabilities JSON file - Log-Line "🧩 Generating mobile capabilities JSON" $GLOBAL_LOG - $usageFile = Join-Path $GLOBAL_DIR "usage_file.json" - [void](Generate-Mobile-Caps-Json -MaxTotalParallels $ParallelsPerPlatform -OutputFile $usageFile) - Log-Line "āœ… Created usage_file.json at: $usageFile" $GLOBAL_LOG - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - - Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Wrappers with retry ===== -function Setup-Web { - Log-Line "Starting Web setup for $TECH_STACK" $WEB_LOG - Log-Line "🌐 ========================================" $GLOBAL_LOG - Log-Line "🌐 Starting WEB Testing ($TECH_STACK)" $GLOBAL_LOG - Log-Line "🌐 ========================================" $GLOBAL_LOG - - $localFlag = $false - $attempt = 1 - $success = $true - - $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) - if ($totalParallels -lt 1) { $totalParallels = 1 } - $parallelsPerPlatform = $totalParallels - - while ($attempt -le 1) { - Log-Line "[Web Setup]" $WEB_LOG - switch ($TECH_STACK) { - "Java" { - Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "BUILD FAILURE") { - $success = $false - } - } - } - "Python" { - Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "BUILD FAILURE") { - $success = $false - } - } - } - "NodeJS" { - Setup-Web-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "([1-9][0-9]*) passed, 0 failed") { - $success = $false - } - } - } - default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $WEB_LOG; return } - } - - if ($success) { - Log-Line "āœ… Web setup succeeded." $WEB_LOG - Log-Line "āœ… WEB Testing completed successfully" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG - break - } else { - Log-Line "āŒ Web setup ended without success; check $WEB_LOG for details" $WEB_LOG - Log-Line "āŒ WEB Testing completed with errors" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG - break - } - } -} - - -function Setup-Mobile { - Log-Line "Starting Mobile setup for $TECH_STACK" $MOBILE_LOG - Log-Line "šŸ“± ========================================" $GLOBAL_LOG - Log-Line "šŸ“± Starting MOBILE APP Testing ($TECH_STACK)" $GLOBAL_LOG - Log-Line "šŸ“± ========================================" $GLOBAL_LOG - - $localFlag = $true - $attempt = 1 - $success = $false - - $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE)) - if ($totalParallels -lt 1) { $totalParallels = 1 } - $parallelsPerPlatform = $totalParallels - - while ($attempt -le 1) { - Log-Line "[Mobile Setup Attempt $attempt] browserstackLocal: $localFlag" $MOBILE_LOG - switch ($TECH_STACK) { - "Java" { Setup-Mobile-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - "Python" { Setup-Mobile-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - "NodeJS" { Setup-Mobile-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $MOBILE_LOG; return } - } - - # Add a small delay to ensure all output is flushed to disk (especially important for Java) - Start-Sleep -Milliseconds 500 - - if (!(Test-Path $MOBILE_LOG)) { - $content = "" - } else { - $content = Get-Content $MOBILE_LOG -Raw - } - - $LOCAL_FAILURE = $false - $SETUP_FAILURE = $false - - foreach ($p in $MOBILE_LOCAL_ERRORS) { if ($p -and ($content -match $p)) { $LOCAL_FAILURE = $true; break } } - foreach ($p in $MOBILE_SETUP_ERRORS) { if ($p -and ($content -match $p)) { $SETUP_FAILURE = $true; break } } - - # Check for BrowserStack link (success indicator) - if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { - $success = $true - } - - if ($success) { - Log-Line "āœ… Mobile setup succeeded" $MOBILE_LOG - Log-Line "āœ… MOBILE APP Testing completed successfully" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } elseif ($LOCAL_FAILURE -and $attempt -eq 1) { - $localFlag = $false - $attempt++ - Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $MOBILE_LOG - Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $GLOBAL_LOG - } elseif ($SETUP_FAILURE) { - Log-Line "āŒ Mobile test failed due to setup error. Check logs at: $MOBILE_LOG" $MOBILE_LOG - Log-Line "āŒ MOBILE APP Testing failed due to setup error" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } else { - Log-Line "āŒ Mobile setup ended without success; check $MOBILE_LOG for details" $MOBILE_LOG - Log-Line "āŒ MOBILE APP Testing completed with errors" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } - } -} +# ===== Source Modular Scripts ===== +. "$PSScriptRoot\logging-utils.ps1" +. "$PSScriptRoot\common-utils.ps1" +. "$PSScriptRoot\user-interaction.ps1" +. "$PSScriptRoot\device-machine-allocation.ps1" +. "$PSScriptRoot\env-prequisite-checks.ps1" +. "$PSScriptRoot\env-setup-run.ps1" # ===== Orchestration ===== diff --git a/win/user-interaction.ps1 b/win/user-interaction.ps1 new file mode 100644 index 0000000..426aa1b --- /dev/null +++ b/win/user-interaction.ps1 @@ -0,0 +1,330 @@ +# ============================================== +# šŸ‘¤ USER INTERACTION +# ============================================== + +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-ChoiceBox { + param( + [string]$Title = "Choose", + [string]$Prompt = "Select one:", + [string[]]$Choices, + [string]$DefaultChoice + ) + $form = New-Object System.Windows.Forms.Form + $form.Text = $Title + $form.Size = New-Object System.Drawing.Size(420, 240) + $form.StartPosition = "CenterScreen" + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $label.AutoSize = $true + $label.Location = New-Object System.Drawing.Point(10, 10) + $form.Controls.Add($label) + + $group = New-Object System.Windows.Forms.Panel + $group.Location = New-Object System.Drawing.Point(10, 35) + $group.Width = 380 + $startY = 10 + $spacing = 28 + + $radios = @() + [int]$i = 0 + foreach ($c in $Choices) { + $rb = New-Object System.Windows.Forms.RadioButton + $rb.Text = $c + $rb.AutoSize = $true + $rb.Location = New-Object System.Drawing.Point(10, ($startY + $i * $spacing)) + if ($c -eq $DefaultChoice) { $rb.Checked = $true } + $group.Controls.Add($rb) + $radios += $rb + $i++ + } + $group.Height = [Math]::Max(120, $startY + ($Choices.Count * $spacing) + 10) + $form.Controls.Add($group) + + $ok = New-Object System.Windows.Forms.Button + $ok.Text = "OK" + $ok.Location = New-Object System.Drawing.Point(300, ($group.Bottom + 10)) + $ok.Add_Click({ + foreach ($rb in $radios) { if ($rb.Checked) { $form.Tag = $rb.Text; break } } + $form.Close() + }) + $form.Controls.Add($ok) + + $form.Height = $ok.Bottom + 70 + $form.AcceptButton = $ok + [void]$form.ShowDialog() + return [string]$form.Tag +} + +# === NEW: Big clickable button chooser === +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 (*.apk;*.ipa)|*.apk;*.ipa|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 "" +} + +# ===== Baseline interactions ===== +function Ask-BrowserStack-Credentials { + $script:BROWSERSTACK_USERNAME = Show-InputBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Username:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" -DefaultText "" + if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_USERNAME)) { + Log-Line "āŒ Username empty" $GLOBAL_LOG + throw "Username is required" + } + $script:BROWSERSTACK_ACCESS_KEY = Show-PasswordBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Access Key:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" + if ([string]::IsNullOrWhiteSpace($script:BROWSERSTACK_ACCESS_KEY)) { + Log-Line "āŒ Access Key empty" $GLOBAL_LOG + throw "Access Key is required" + } + Log-Line "āœ… BrowserStack credentials captured (access key hidden)" $GLOBAL_LOG +} + +# === UPDATED: click-select for Web/App/Both === +function Ask-Test-Type { + $choice = Show-ClickChoice -Title "Testing Type" ` + -Prompt "What do you want to run?" ` + -Choices @("Web","App","Both") ` + -DefaultChoice "Web" + if ([string]::IsNullOrWhiteSpace($choice)) { throw "No testing type selected" } + $script:TEST_TYPE = $choice + Log-Line "āœ… Selected Testing Type: $script:TEST_TYPE" $GLOBAL_LOG + + switch ($script:TEST_TYPE) { + "Web" { Ask-User-TestUrl } + "App" { Ask-And-Upload-App } + "Both" { Ask-User-TestUrl; Ask-And-Upload-App } + } +} + +# === UPDATED: click-select for Tech Stack === +function Ask-Tech-Stack { + $choice = Show-ClickChoice -Title "Tech Stack" ` + -Prompt "Select your installed language / framework:" ` + -Choices @("Java","Python","NodeJS") ` + -DefaultChoice "Java" + if ([string]::IsNullOrWhiteSpace($choice)) { throw "No tech stack selected" } + $script:TECH_STACK = $choice + Log-Line "āœ… Selected Tech Stack: $script:TECH_STACK" $GLOBAL_LOG +} + +function Ask-User-TestUrl { + $u = 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 "" + if ([string]::IsNullOrWhiteSpace($u)) { + $script:CX_TEST_URL = $DEFAULT_TEST_URL + Log-Line "āš ļø No URL entered. Falling back to default: $script:CX_TEST_URL" $GLOBAL_LOG + } else { + $script:CX_TEST_URL = $u + Log-Line "🌐 Using custom test URL: $script:CX_TEST_URL" $GLOBAL_LOG + } +} + +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 Ask-And-Upload-App { + # First, show a choice screen for Sample App vs Browse + $appChoice = Show-ClickChoice -Title "App Selection" ` + -Prompt "Choose an app to test:" ` + -Choices @("Sample App","Browse") ` + -DefaultChoice "Sample App" + + if ([string]::IsNullOrWhiteSpace($appChoice) -or $appChoice -eq "Sample App") { + Log-Line "āš ļø Using default sample app: bs://sample.app" $GLOBAL_LOG + $script:APP_URL = "bs://sample.app" + $script:APP_PLATFORM = "all" + return + } + + # User chose "Browse", so open file picker + $path = Show-OpenFileDialog -Title "šŸ“± Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" + if ([string]::IsNullOrWhiteSpace($path)) { + Log-Line "āš ļø No app selected. Using default sample app: bs://sample.app" $GLOBAL_LOG + $script:APP_URL = "bs://sample.app" + $script:APP_PLATFORM = "all" + return + } + + $ext = [System.IO.Path]::GetExtension($path).ToLowerInvariant() + switch ($ext) { + ".apk" { $script:APP_PLATFORM = "android" } + ".ipa" { $script:APP_PLATFORM = "ios" } + default { Log-Line "āŒ Unsupported file type. Only .apk or .ipa allowed." $GLOBAL_LOG; throw "Unsupported app file" } + } + + Log-Line "ā¬†ļø Uploading $path to BrowserStack..." $GLOBAL_LOG + + # Create multipart form data manually for PowerShell 5.1 compatibility + $boundary = [System.Guid]::NewGuid().ToString() + $LF = "`r`n" + $fileBin = [System.IO.File]::ReadAllBytes($path) + $fileName = [System.IO.Path]::GetFileName($path) + + $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)) { + Log-Line "āŒ Upload failed. Response: $(ConvertTo-Json $resp -Depth 5)" $GLOBAL_LOG + throw "Upload failed" + } + $script:APP_URL = $url + Log-Line "āœ… App uploaded successfully: $script:APP_URL" $GLOBAL_LOG +} From 433d5d63877e11013d8c3f718956922b5d09569f Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 15:01:24 +0530 Subject: [PATCH 02/36] win changes compat checks --- win/env-setup-run.ps1 | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 9d56988..cc5d0ab 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -269,48 +269,6 @@ $platformYamlIos Log-Line "āœ… Wrote platform YAMLs to android/browserstack.yml and ios/browserstack.yml" $GLOBAL_LOG - # Replace sample tests in both android and ios with universal, locator-free test - $testContent = @" -import pytest - - -@pytest.mark.usefixtures('setWebdriver') -class TestUniversalAppCheck: - - def test_app_health_check(self): - - # 1. Get initial app and device state (no locators) - initial_package = self.driver.current_package - initial_activity = self.driver.current_activity - initial_orientation = self.driver.orientation - - # 2. Log the captured data to BrowserStack using 'annotate' - log_data = f"Initial State: Package='{initial_package}', Activity='{initial_activity}', Orientation='{initial_orientation}'" - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' - ) - - # 3. Perform a locator-free action: change device orientation - self.driver.orientation = 'LANDSCAPE' - - # 4. Perform locator-free assertions - assert self.driver.orientation == 'LANDSCAPE' - - # 5. Log the successful state change - self.driver.execute_script( - 'browserstack_executor: {"action": "annotate", "arguments": {"data": "Successfully changed orientation to LANDSCAPE", "level": "info"}}' - ) - - # 6. Set the final session status to 'passed' - self.driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "App state verified and orientation changed!"}}' - ) -"@ - $androidTestPath = Join-Path $TARGET "android\bstack_sample.py" - $iosTestPath = Join-Path $TARGET "ios\bstack_sample.py" - Set-ContentNoBom -Path $androidTestPath -Value $testContent - Set-ContentNoBom -Path $iosTestPath -Value $testContent - # Decide which directory to run based on APP_PLATFORM (default to android) $runDirName = "android" if ($APP_PLATFORM -eq "ios") { From 9a19082801cec8e3f20786d428cc064fc2d06708 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 15:13:34 +0530 Subject: [PATCH 03/36] win changes compat checks --- mac/common-utils.sh | 68 ++++++++++++++++++++++++++++++++++------- mac/env-setup-run.sh | 10 ++++-- mac/user-interaction.sh | 43 ++++++++++++++++++++++++-- 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/mac/common-utils.sh b/mac/common-utils.sh index 6a45a57..359c0eb 100644 --- a/mac/common-utils.sh +++ b/mac/common-utils.sh @@ -88,13 +88,25 @@ handle_app_upload() { log_msg_to "Exported APP_PLATFORM=$APP_PLATFORM" else local choice - choice=$(osascript -e ' - display dialog "How would you like to select your app?" ¬ - with title "BrowserStack App Upload" ¬ - with icon note ¬ - buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬ - default button "Upload my App (.apk/.ipa)" - ' 2>/dev/null) + if [[ "$NOW_OS" == "macos" ]]; then + choice=$(osascript -e ' + display dialog "How would you like to select your app?" ¬ + with title "BrowserStack App Upload" ¬ + with icon note ¬ + buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬ + default button "Upload my App (.apk/.ipa)" + ' 2>/dev/null) + else + echo "How would you like to select your app?" + select opt in "Use Sample App" "Upload my App (.apk/.ipa)" "Cancel"; do + case $opt in + "Use Sample App") choice="Use Sample App"; break ;; + "Upload my App (.apk/.ipa)") choice="Upload my App"; break ;; + "Cancel") choice="Cancel"; break ;; + *) echo "Invalid option";; + esac + done + fi if [[ "$choice" == *"Use Sample App"* ]]; then upload_sample_app @@ -135,9 +147,18 @@ upload_custom_app() { local file_path # Convert to POSIX path - file_path=$(osascript -e \ - 'POSIX path of (choose file with prompt "Select your .apk or .ipa file:" of type {"apk", "ipa"})' \ - 2>/dev/null) + # 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) + else + echo "Please enter the full path to your .apk or .ipa file:" + read -r file_path + # Remove quotes if user added them + file_path="${file_path%\"}" + file_path="${file_path#\"}" + fi # Trim whitespace file_path="${file_path%"${file_path##*[![:space:]]}"}" @@ -270,7 +291,7 @@ is_domain_private() { export CX_TEST_URL="$CX_TEST_URL" # Resolve domain using Cloudflare DNS - IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1) + IP_ADDRESS=$(resolve_ip "$domain") # Determine if domain is private if is_private_ip "$IP_ADDRESS"; then @@ -284,6 +305,31 @@ is_domain_private() { return $is_cx_domain_private } +resolve_ip() { + local domain=$1 + local ip="" + + # Try dig first (standard on macOS/Linux, optional on Windows) + if command -v dig >/dev/null 2>&1; then + ip=$(dig +short "$domain" @1.1.1.1 | head -n1) + fi + + # Try Python if dig failed or missing + if [ -z "$ip" ] && command -v python3 >/dev/null 2>&1; then + ip=$(python3 -c "import socket; print(socket.gethostbyname('$domain'))" 2>/dev/null) + fi + + # Try nslookup as last resort (parsing is fragile) + if [ -z "$ip" ] && command -v nslookup >/dev/null 2>&1; then + # Windows/Generic nslookup parsing + # Look for "Address:" or "Addresses:" after "Name:" + # This is a best-effort attempt + ip=$(nslookup "$domain" 2>/dev/null | grep -A 10 "Name:" | grep "Address" | tail -n1 | awk '{print $NF}') + fi + + echo "$ip" +} + identify_run_status_java() { local log_file=$1 diff --git a/mac/env-setup-run.sh b/mac/env-setup-run.sh index 5cb1327..3bbbd44 100644 --- a/mac/env-setup-run.sh +++ b/mac/env-setup-run.sh @@ -536,7 +536,13 @@ detect_setup_python_env() { exit 1 } - # shellcheck source=/dev/null - source .venv/bin/activate + # 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." } diff --git a/mac/user-interaction.sh b/mac/user-interaction.sh index 752abfe..24b9c24 100644 --- a/mac/user-interaction.sh +++ b/mac/user-interaction.sh @@ -10,16 +10,29 @@ get_browserstack_credentials() { access_key="$BROWSERSTACK_ACCESS_KEY" log_info "BrowserStack credentials loaded from environment variables for user: $username" else + 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') + else + echo "Please enter your BrowserStack Username." + echo "Note: Locate it in your BrowserStack account profile page: https://www.browserstack.com/accounts/profile/details" + read -r username + fi if [ -z "$username" ]; then 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') + else + echo "Please enter your BrowserStack Access Key." + echo "Note: Locate it in your BrowserStack account page: https://www.browserstack.com/accounts/profile/details" + read -rs access_key + echo "" # Newline after secret input + fi if [ -z "$access_key" ]; then log_msg_to "āŒ Access Key empty" return 1 @@ -41,8 +54,18 @@ get_tech_stack() { tech_stack="$TSTACK" 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') + else + echo "Select installed tech stack:" + select opt in "java" "python" "nodejs"; do + case $opt in + "java"|"python"|"nodejs") tech_stack=$opt; break ;; + *) echo "Invalid option";; + esac + done + fi fi log_msg_to "āœ… Selected Tech Stack: $tech_stack" log_info "Tech Stack: $tech_stack" @@ -56,8 +79,14 @@ get_tech_stack() { get_test_url() { local test_url=$DEFAULT_TEST_URL - test_url=$(osascript -e 'Tell application "System Events" to display dialog "Enter the URL you want to test with BrowserStack:\n(Leave blank for default: '"$DEFAULT_TEST_URL"')" default answer "" with title "Test URL Setup" buttons {"OK"} default button "OK"' \ - -e 'text returned of result') + if [[ "$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') + else + echo "Enter the URL you want to test with BrowserStack:" + echo "(Leave blank for default: $DEFAULT_TEST_URL)" + read -r test_url + fi if [ -n "$test_url" ]; then log_msg_to "🌐 Using custom test URL: $test_url" @@ -79,8 +108,18 @@ get_test_type() { test_type=$TT 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') + else + echo "Select testing type:" + select opt in "web" "app"; do + case $opt in + "web"|"app") test_type=$opt; break ;; + *) echo "Invalid option";; + esac + done + fi log_msg_to "āœ… Selected Testing Type: $TEST_TYPE" RUN_MODE=$test_type log_info "Run Mode: ${RUN_MODE:-default}" From 3110393020f1c0f8315241b6e42831b81bdc0e18 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 15:16:20 +0530 Subject: [PATCH 04/36] win changes compat checks --- mac/env-setup-run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mac/env-setup-run.sh b/mac/env-setup-run.sh index 3bbbd44..3ad7c5d 100644 --- a/mac/env-setup-run.sh +++ b/mac/env-setup-run.sh @@ -23,7 +23,7 @@ setup_environment() { # Calculate parallels local total_parallels - total_parallels=$(echo "$max_parallels" | bc | cut -d'.' -f1) + total_parallels=$(awk -v n="$max_parallels" 'BEGIN { printf "%d", n }') [ -z "$total_parallels" ] && total_parallels=1 local parallels_per_platform=$total_parallels From 603992f0538b5af6b80b7cc8302e4ffa65df9658 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 17:45:23 +0530 Subject: [PATCH 05/36] win changes gha --- .github/workflows/test-scripts.yml | 294 +++++++++++++++-------------- 1 file changed, 152 insertions(+), 142 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 3b49148..ab69517 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -171,148 +171,158 @@ jobs: retention-days: 30 if-no-files-found: ignore - # test-windows: - # name: Test win/run.ps1 on Windows - # runs-on: windows-latest - # timeout-minutes: 15 - # environment: BrowserStack - # steps: - # - name: Checkout code - # uses: actions/checkout@v4 - # - name: Set up Python 3.12 - # uses: actions/setup-python@v5 - # with: - # python-version: '3.12' - # - name: Check PowerShell version - # run: | - # $PSVersionTable.PSVersion - # Write-Host "āœ… PowerShell version check complete" - # - # - name: Validate PowerShell script syntax - # run: | - # Write-Host "Validating win/run.ps1 syntax..." - # $ScriptPath = "win/run.ps1" - # $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $ScriptPath), [ref]$null) - # Write-Host "āœ… win/run.ps1 syntax is valid" - # - # - name: Validate supporting PowerShell scripts syntax - # run: | - # Write-Host "Validating supporting PowerShell scripts..." - # $Scripts = @("win/proxy-check.ps1") - # foreach ($Script in $Scripts) { - # $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) - # Write-Host "āœ… $Script syntax is valid" - # } - # - # - name: Run PSScriptAnalyzer - # run: | - # Write-Host "Installing PSScriptAnalyzer..." - # Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -ErrorAction SilentlyContinue - # Write-Host "Running PSScriptAnalyzer..." - # Invoke-ScriptAnalyzer -Path "win/run.ps1" -Recurse -ReportSummary || $true - # Write-Host "āœ… PSScriptAnalyzer analysis complete" - # - # - name: Check script file encoding - # run: | - # Write-Host "Checking PowerShell script encoding..." - # $ScriptPath = "win/run.ps1" - # $Encoding = (Get-Item $ScriptPath).EncodingInfo - # Write-Host "File encoding: $Encoding" - # Write-Host "āœ… Encoding check complete" - # - # - name: Verify required dependencies - # run: | - # Write-Host "Checking required dependencies..." - # if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… curl found" } - # if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… git found" } - # Write-Host "āœ… PowerShell dependencies verified" - # - # - name: Integration Test - Silent Mode Execution - # if: success() - # env: - # BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - # BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - # TURL: https://bstackdemo.com - # run: | - # Write-Host "Running integration tests in silent mode..." - # - # # Set default values if secrets are not provided - # $BrowserStackUsername = if ($env:BROWSERSTACK_USERNAME) { $env:BROWSERSTACK_USERNAME } else { "test_user" } - # $BrowserStackAccessKey = if ($env:BROWSERSTACK_ACCESS_KEY) { $env:BROWSERSTACK_ACCESS_KEY } else { "test_key" } - # $TestUrl = $env:TURL - # - # # Export environment variables - # $env:BROWSERSTACK_USERNAME = $BrowserStackUsername - # $env:BROWSERSTACK_ACCESS_KEY = $BrowserStackAccessKey - # $env:TURL = $TestUrl - # - # # Test configurations - # $testConfigs = @( - # @("web", "java"), - # @("app", "java"), - # @("web", "python"), - # @("app", "python"), - # @("web", "nodejs"), - # @("app", "nodejs") - # ) - # - # foreach ($config in $testConfigs) { - # $testType = $config[0] - # $techStack = $config[1] - # - # Write-Host "================================" - # Write-Host "Testing: .\win\run.ps1 --silent $testType $techStack" - # Write-Host "================================" - # - # # Create log file path - # $logPath = "C:\Temp\run_test_${testType}_${techStack}.log" - # New-Item -ItemType Directory -Path "C:\Temp" -Force -ErrorAction SilentlyContinue | Out-Null - # - # # Run with timeout (using job for timeout capability) - # $job = Start-Job -ScriptBlock { - # param($path, $testType, $techStack, $logPath) - # & $path --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append - # } -ArgumentList ".\win\run.ps1", $testType, $techStack, $logPath - # - # # Wait for job with 600 second timeout - # $timeout = New-TimeSpan -Seconds 600 - # $completed = Wait-Job -Job $job -Timeout 600 - # - # if ($completed) { - # $result = Receive-Job -Job $job - # if ($job.State -eq "Completed") { - # Write-Host "āœ… .\win\run.ps1 --silent $testType $techStack completed successfully" - # } else { - # Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack exited with state: $($job.State)" - # if (Test-Path $logPath) { - # Write-Host "Log output (last 20 lines):" - # Get-Content -Path $logPath -Tail 20 - # } - # } - # } else { - # Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack timed out after 600 seconds" - # Stop-Job -Job $job - # if (Test-Path $logPath) { - # Write-Host "Log output (last 20 lines):" - # Get-Content -Path $logPath -Tail 20 - # } - # } - # - # Remove-Job -Job $job -Force - # } - # - # Write-Host "āœ… All integration tests completed" - # - # - name: Upload BrowserStack Logs as Artifacts - # if: always() - # uses: actions/upload-artifact@v4 - # with: - # name: browserstack-logs-windows - # path: | - # C:\Users\runneradmin\.browserstack\NOW\logs\ - # C:\Temp\run_test_*.log - # retention-days: 30 - # if-no-files-found: ignore + test-windows: + name: Test win/run.ps1 on Windows + runs-on: windows-latest + timeout-minutes: 15 + environment: BrowserStack + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Check PowerShell version + run: | + $PSVersionTable.PSVersion + Write-Host "āœ… PowerShell version check complete" + + - name: Validate PowerShell script syntax + run: | + Write-Host "Validating win/run.ps1 syntax..." + $ScriptPath = "win/run.ps1" + $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $ScriptPath), [ref]$null) + Write-Host "āœ… win/run.ps1 syntax is valid" + + - name: Validate supporting PowerShell scripts syntax + run: | + Write-Host "Validating supporting PowerShell scripts..." + $Scripts = @("win/proxy-check.ps1") + foreach ($Script in $Scripts) { + $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) + Write-Host "āœ… $Script syntax is valid" + } + + - name: Run PSScriptAnalyzer + run: | + Write-Host "Installing PSScriptAnalyzer..." + Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -ErrorAction SilentlyContinue + Write-Host "Running PSScriptAnalyzer..." + Invoke-ScriptAnalyzer -Path "win/run.ps1" -Recurse -ReportSummary || $true + Write-Host "āœ… PSScriptAnalyzer analysis complete" + + - name: Check script file encoding + run: | + Write-Host "Checking PowerShell script encoding..." + $ScriptPath = "win/run.ps1" + $Encoding = (Get-Item $ScriptPath).EncodingInfo + Write-Host "File encoding: $Encoding" + Write-Host "āœ… Encoding check complete" + + - name: Verify required dependencies + run: | + Write-Host "Checking required dependencies..." + if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… curl found" } + if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… git found" } + Write-Host "āœ… PowerShell dependencies verified" + + - name: Integration Test - Silent Mode Execution + if: success() + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + TURL: https://bstackdemo.com + run: | + Write-Host "Running integration tests in silent mode..." + + # Set default values if secrets are not provided + $BrowserStackUsername = if ($env:BROWSERSTACK_USERNAME) { $env:BROWSERSTACK_USERNAME } else { "test_user" } + $BrowserStackAccessKey = if ($env:BROWSERSTACK_ACCESS_KEY) { $env:BROWSERSTACK_ACCESS_KEY } else { "test_key" } + $TestUrl = $env:TURL + + # Export environment variables + $env:BROWSERSTACK_USERNAME = $BrowserStackUsername + $env:BROWSERSTACK_ACCESS_KEY = $BrowserStackAccessKey + $env:TURL = $TestUrl + + # Test configurations + $testConfigs = @( + @("web", "java"), + @("app", "java"), + @("web", "python"), + @("app", "python"), + @("web", "nodejs"), + @("app", "nodejs") + ) + + foreach ($config in $testConfigs) { + $testType = $config[0] + $techStack = $config[1] + + Write-Host "================================" + Write-Host "Testing: .\win\run.ps1 --silent $testType $techStack" + Write-Host "================================" + + # Create log file path + $logPath = "C:\Temp\run_test_${testType}_${techStack}.log" + New-Item -ItemType Directory -Path "C:\Temp" -Force -ErrorAction SilentlyContinue | Out-Null + + # Run with timeout (using job for timeout capability) + $job = Start-Job -ScriptBlock { + param($path, $testType, $techStack, $logPath) + & $path --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append + } -ArgumentList ".\win\run.ps1", $testType, $techStack, $logPath + + # Wait for job with 600 second timeout + $timeout = New-TimeSpan -Seconds 600 + $completed = Wait-Job -Job $job -Timeout 600 + + if ($completed) { + $result = Receive-Job -Job $job + if ($job.State -eq "Completed") { + Write-Host "āœ… .\win\run.ps1 --silent $testType $techStack completed successfully" + } else { + Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack exited with state: $($job.State)" + if (Test-Path $logPath) { + Write-Host "Log output (last 20 lines):" + Get-Content -Path $logPath -Tail 20 + } + } + } else { + Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack timed out after 600 seconds" + Stop-Job -Job $job + if (Test-Path $logPath) { + Write-Host "Log output (last 20 lines):" + Get-Content -Path $logPath -Tail 20 + } + } + + Remove-Job -Job $job -Force + } + + Write-Host "āœ… All integration tests completed" + + - name: Sync BrowserStack logs to workspace + if: always() + run: | + mkdir -p ${{ github.workspace }}/bs-logs + if [ -d ~/.browserstack/NOW/logs ]; then + cp -R ~/.browserstack/NOW/logs/* ${{ github.workspace }}/bs-logs/ || true + else + echo "No logs found in ~/.browserstack/NOW/logs" + fi + + - name: Upload BrowserStack Logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: browserstack-logs-windows + path: | + C:\Users\runneradmin\.browserstack\NOW\logs\ + C:\Temp\run_test_*.log + retention-days: 30 + if-no-files-found: ignore test-linux: name: Test mac/run.sh on Linux From b14cddc88f9dc1e11b5bdc2a2cba8a331d127667 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 18:48:13 +0530 Subject: [PATCH 06/36] win changes proxy --- win/run.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win/run.ps1 b/win/run.ps1 index 9eb9727..bafe3ba 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -149,7 +149,7 @@ try { # Check for proxy configuration Log-Line "ā„¹ļø Checking proxy in environment" $GLOBAL_LOG - $proxyCheckScript = Join-Path $PSScriptRoot "proxy-check.ps1" + $proxyCheckScript = Join-Path $PSScriptRoot "env-prequisite-checks.ps1" if (Test-Path $proxyCheckScript) { try { & $proxyCheckScript -BrowserStackUsername $BROWSERSTACK_USERNAME -BrowserStackAccessKey $BROWSERSTACK_ACCESS_KEY From a61382966ae6ca3fefc27671a4f373660c511a88 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 18:52:11 +0530 Subject: [PATCH 07/36] win changes proxy test-scripts.yml --- .github/workflows/test-scripts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index ab69517..56f041b 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -194,11 +194,11 @@ jobs: $ScriptPath = "win/run.ps1" $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $ScriptPath), [ref]$null) Write-Host "āœ… win/run.ps1 syntax is valid" - + - name: Validate supporting PowerShell scripts syntax run: | Write-Host "Validating supporting PowerShell scripts..." - $Scripts = @("win/proxy-check.ps1") + $Scripts = @("win/proxy-common-utils.ps1", "win/logging-utils.ps1", "win/env-prequisite-checks.ps1", "win/user-interaction.ps1", "win/env-setup-run.ps1") foreach ($Script in $Scripts) { $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) Write-Host "āœ… $Script syntax is valid" From bfa73b74b89f24626a01f01dbd0e06422694304f Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 19:19:21 +0530 Subject: [PATCH 08/36] win changes gha win --- .github/workflows/test-scripts.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 56f041b..2259846 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -198,7 +198,7 @@ jobs: - name: Validate supporting PowerShell scripts syntax run: | Write-Host "Validating supporting PowerShell scripts..." - $Scripts = @("win/proxy-common-utils.ps1", "win/logging-utils.ps1", "win/env-prequisite-checks.ps1", "win/user-interaction.ps1", "win/env-setup-run.ps1") + $Scripts = @("win/common-utils.ps1", "win/logging-utils.ps1", "win/env-prequisite-checks.ps1", "win/user-interaction.ps1", "win/env-setup-run.ps1") foreach ($Script in $Scripts) { $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) Write-Host "āœ… $Script syntax is valid" @@ -303,15 +303,22 @@ jobs: Write-Host "āœ… All integration tests completed" - - name: Sync BrowserStack logs to workspace + - name: Sync BrowserStack logs to workspace (Windows) if: always() + shell: powershell run: | - mkdir -p ${{ github.workspace }}/bs-logs - if [ -d ~/.browserstack/NOW/logs ]; then - cp -R ~/.browserstack/NOW/logs/* ${{ github.workspace }}/bs-logs/ || true - else - echo "No logs found in ~/.browserstack/NOW/logs" - fi + $dest = "${env:GITHUB_WORKSPACE}\bs-logs" + New-Item -ItemType Directory -Force -Path $dest | Out-Null + + $logPath = "${HOME}\.browserstack\NOW\logs" + + if (Test-Path $logPath) { + Write-Host "Copying logs from $logPath" + Copy-Item -Path "$logPath\*" -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue + } + else { + Write-Host "No logs found at $logPath" + } - name: Upload BrowserStack Logs as Artifacts if: always() From d75db8c648d4517559d9309987138bd2e59406dd Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Fri, 28 Nov 2025 19:48:11 +0530 Subject: [PATCH 09/36] win changes gha win --- .github/workflows/test-scripts.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 2259846..1f155f1 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -305,12 +305,11 @@ jobs: - name: Sync BrowserStack logs to workspace (Windows) if: always() - shell: powershell run: | $dest = "${env:GITHUB_WORKSPACE}\bs-logs" New-Item -ItemType Directory -Force -Path $dest | Out-Null - $logPath = "${HOME}\.browserstack\NOW\logs" + $logPath = "$env:USERPROFILE\.browserstack\NOW\logs" if (Test-Path $logPath) { Write-Host "Copying logs from $logPath" @@ -326,7 +325,7 @@ jobs: with: name: browserstack-logs-windows path: | - C:\Users\runneradmin\.browserstack\NOW\logs\ + ${{ github.workspace }}/bs-logs C:\Temp\run_test_*.log retention-days: 30 if-no-files-found: ignore From 68736b33720f1b5cf650453c0e1b9e5d8ab76d29 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 29 Nov 2025 02:13:51 +0530 Subject: [PATCH 10/36] updated windows setup --- win/common-utils.ps1 | 138 +++- win/device-machine-allocation.ps1 | 580 ++++++++------- win/env-prequisite-checks.ps1 | 223 ++++-- win/env-setup-run.ps1 | 1131 +++++++++++++++-------------- win/logging-utils.ps1 | 55 +- win/run.ps1 | 223 ++---- win/user-interaction.ps1 | 279 ++++--- 7 files changed, 1388 insertions(+), 1241 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 9dc7883..4e9d734 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -1,16 +1,70 @@ -# ============================================== -# šŸ› ļø COMMON UTILITIES -# ============================================== +# 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 = Join-Path $LOG_DIR "global.log" +$script:WEB_LOG = Join-Path $LOG_DIR "web_run_result.log" +$script:MOBILE_LOG = Join-Path $LOG_DIR "mobile_run_result.log" + +# Script state +$script:BROWSERSTACK_USERNAME = "" +$script:BROWSERSTACK_ACCESS_KEY = "" +$script:TEST_TYPE = "" # Web / App / Both +$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 already exists: $GLOBAL_DIR" $GLOBAL_LOG + 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 } + '' | Out-File -FilePath $GLOBAL_LOG -Encoding UTF8 + '' | Out-File -FilePath $WEB_LOG -Encoding UTF8 + '' | Out-File -FilePath $MOBILE_LOG -Encoding UTF8 + Log-Line "āœ… Logs cleared and fresh run initiated." $GLOBAL_LOG } +# ===== Git Clone ===== function Invoke-GitClone { param( [Parameter(Mandatory)] [string]$Url, @@ -59,7 +113,6 @@ function Set-ContentNoBom { [System.IO.File]::WriteAllText($Path, $Value, $enc) } -# Run external tools capturing stdout/stderr without throwing on STDERR function Invoke-External { param( [Parameter(Mandatory)][string]$Exe, @@ -71,7 +124,6 @@ function Invoke-External { $exeToRun = $Exe $argLine = ($Arguments | ForEach-Object { if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } }) -join ' ' - # .cmd/.bat need to be invoked via cmd.exe when UseShellExecute=false $ext = [System.IO.Path]::GetExtension($Exe) if ($ext -and ($ext.ToLowerInvariant() -in @('.cmd','.bat'))) { if (-not (Test-Path $Exe)) { throw "Command not found: $Exe" } @@ -94,14 +146,11 @@ function Invoke-External { $p = New-Object System.Diagnostics.Process $p.StartInfo = $psi - - # Stream output to log file in real-time if LogFile is specified + if ($LogFile) { - # Ensure the log file directory exists $logDir = Split-Path -Parent $LogFile if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } - - # Create script blocks to handle output streaming + $stdoutAction = { if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { Add-Content -Path $Event.MessageData -Value $EventArgs.Data @@ -112,33 +161,29 @@ function Invoke-External { Add-Content -Path $Event.MessageData -Value $EventArgs.Data } } - - # Register events to capture output line by line as it's produced + $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() - - # Clean up event handlers + Unregister-Event -SourceIdentifier $stdoutEvent.Name Unregister-Event -SourceIdentifier $stderrEvent.Name Remove-Job -Id $stdoutEvent.Id -Force Remove-Job -Id $stderrEvent.Id -Force } else { - # If no log file, just read all output at once (original behavior) [void]$p.Start() $stdout = $p.StandardOutput.ReadToEnd() $stderr = $p.StandardError.ReadToEnd() $p.WaitForExit() } - + return $p.ExitCode } -# Return a Maven executable path or wrapper for a given repo directory function Get-MavenCommand { param([Parameter(Mandatory)][string]$RepoDir) $mvnCmd = Get-Command mvn -ErrorAction SilentlyContinue @@ -148,7 +193,6 @@ function Get-MavenCommand { throw "Maven not found in PATH and 'mvnw.cmd' not present under $RepoDir. Install Maven or ensure the wrapper exists." } -# Get the python.exe inside a Windows venv function Get-VenvPython { param([Parameter(Mandatory)][string]$VenvDir) $py = Join-Path $VenvDir "Scripts\python.exe" @@ -156,7 +200,6 @@ function Get-VenvPython { throw "Python interpreter not found in venv: $VenvDir" } -# Detect a working Python interpreter and set $PY_CMD accordingly function Set-PythonCmd { $candidates = @( @("python3"), @@ -179,7 +222,6 @@ function Set-PythonCmd { throw "Python not found via python3/python/py. Please install Python 3 and ensure it's on PATH." } -# Invoke Python with arguments using the detected interpreter function Invoke-Py { param( [Parameter(Mandatory)][string[]]$Arguments, @@ -193,11 +235,21 @@ function Invoke-Py { return (Invoke-External -Exe $exe -Arguments ($baseArgs + $Arguments) -LogFile $LogFile -WorkingDirectory $WorkingDirectory) } -# Check if IP is private +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 IP resolution failed (empty), assume it's a public domain - # BrowserStack Local should only be enabled for confirmed private IPs if ([string]::IsNullOrWhiteSpace($IP)) { return $false } $parts = $IP.Split('.') if ($parts.Count -ne 4) { return $false } @@ -209,25 +261,20 @@ function Test-PrivateIP { return $false } -# Check if domain is private function Test-DomainPrivate { $domain = $CX_TEST_URL -replace '^https?://', '' -replace '/.*$', '' Log-Line "Website domain: $domain" $GLOBAL_LOG $env:NOW_WEB_DOMAIN = $CX_TEST_URL - - # Resolve domain using Resolve-DnsName (more reliable than nslookup) + $IP_ADDRESS = "" try { - # Try using Resolve-DnsName first (Windows PowerShell 5.1+) $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 { - # Fallback to nslookup if Resolve-DnsName fails try { $nslookupOutput = nslookup $domain 2>&1 | Out-String - # Extract IP addresses from nslookup output (match IPv4 pattern) if ($nslookupOutput -match '(?:Address|Addresses):\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') { $IP_ADDRESS = $matches[1] } @@ -236,23 +283,32 @@ function Test-DomainPrivate { $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 { - Log-Line "ā„¹ļø Fetching BrowserStack Plan Details..." $GLOBAL_LOG + param([string]$TestType) + + Log-Line "ā„¹ļø Fetching BrowserStack plan for $TestType" $GLOBAL_LOG $auth = Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY $headers = @{ Authorization = $auth } - if ($TEST_TYPE -in @("Web","Both")) { + if ($TestType -in @("Web","Both","web","both")) { try { $resp = Invoke-RestMethod -Method Get -Uri "https://api.browserstack.com/automate/plan.json" -Headers $headers $script:WEB_PLAN_FETCHED = $true @@ -262,7 +318,7 @@ function Fetch-Plan-Details { Log-Line "āŒ Web Testing Plan fetch failed ($($_.Exception.Message))" $GLOBAL_LOG } } - if ($TEST_TYPE -in @("App","Both")) { + if ($TestType -in @("App","Both","app","both")) { try { $resp2 = Invoke-RestMethod -Method Get -Uri "https://api-cloud.browserstack.com/app-automate/plan.json" -Headers $headers $script:MOBILE_PLAN_FETCHED = $true @@ -273,10 +329,14 @@ function Fetch-Plan-Details { } } - if ( ($TEST_TYPE -eq "Web" -and -not $WEB_PLAN_FETCHED) -or - ($TEST_TYPE -eq "App" -and -not $MOBILE_PLAN_FETCHED) -or - ($TEST_TYPE -eq "Both" -and -not ($WEB_PLAN_FETCHED -or $MOBILE_PLAN_FETCHED)) ) { + if ( ($TestType -match "^Web$|^web$" -and -not $WEB_PLAN_FETCHED) -or + ($TestType -match "^App$|^app$" -and -not $MOBILE_PLAN_FETCHED) -or + ($TestType -match "^Both$|^both$" -and -not ($WEB_PLAN_FETCHED -or $MOBILE_PLAN_FETCHED)) ) { Log-Line "āŒ Unauthorized to fetch required plan(s) or failed request(s). Exiting." $GLOBAL_LOG throw "Plan fetch failed" } + + Log-Line "ā„¹ļø Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" $GLOBAL_LOG } + + diff --git a/win/device-machine-allocation.ps1 b/win/device-machine-allocation.ps1 index 90c04dc..35c7a08 100644 --- a/win/device-machine-allocation.ps1 +++ b/win/device-machine-allocation.ps1 @@ -1,295 +1,285 @@ -# ============================================== -# šŸ“± DEVICE & MACHINE ALLOCATION -# ============================================== - -# ===== 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) - $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 } - # If APP_PLATFORM is 'all', include both ios and android (no filtering) - } - - $items += [pscustomobject]@{ - 'bstack:options' = @{ - deviceName = $deviceName - osVersion = "${platformVer}.0" - } - } - $count++ - if ($count -ge $max) { break } - } - - # Convert to JSON - $json = ($items | ConvertTo-Json -Depth 5) - - # Write to file - Set-ContentNoBom -Path $OutputFile -Value $json - - 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 } - } - - # Convert to JSON and remove outer brackets to match macOS behavior - # The test code adds brackets: JSON.parse("[" + process.env.BSTACK_CAPS_JSON + "]") - $json = ($items | ConvertTo-Json -Depth 5) - - # Remove leading [ and trailing ] - if ($json.StartsWith('[')) { - $json = $json.Substring(1) - } - if ($json.EndsWith(']')) { - $json = $json.Substring(0, $json.Length - 1) - } - - # Trim any leading/trailing whitespace - $json = $json.Trim() - - return $json -} +# 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 index 0ffb730..e384d55 100644 --- a/win/env-prequisite-checks.ps1 +++ b/win/env-prequisite-checks.ps1 @@ -1,75 +1,148 @@ -# ============================================== -# 🧩 ENVIRONMENT PREREQUISITE CHECKS -# ============================================== - -function Validate-Tech-Stack { - Log-Line "ā„¹ļø Checking prerequisites for $script:TECH_STACK" $GLOBAL_LOG - switch ($script:TECH_STACK) { - "Java" { - Log-Line "šŸ” Checking if 'java' command exists..." $GLOBAL_LOG - if (-not (Get-Command java -ErrorAction SilentlyContinue)) { - Log-Line "āŒ Java command not found in PATH." $GLOBAL_LOG - throw "Java not found" - } - Log-Line "šŸ” Checking if Java runs correctly..." $GLOBAL_LOG - $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" { - Log-Line "šŸ” Checking if 'python3' command exists..." $GLOBAL_LOG - try { - Set-PythonCmd - Log-Line "šŸ” Checking if Python3 runs correctly..." $GLOBAL_LOG - $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" { - Log-Line "šŸ” Checking if 'node' command exists..." $GLOBAL_LOG - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Log-Line "āŒ Node.js command not found in PATH." $GLOBAL_LOG - throw "Node not found" - } - Log-Line "šŸ” Checking if 'npm' command exists..." $GLOBAL_LOG - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - Log-Line "āŒ npm command not found in PATH." $GLOBAL_LOG - throw "npm not found" - } - Log-Line "šŸ” Checking if Node.js runs correctly..." $GLOBAL_LOG - $nodeVer = & node -v 2>&1 - if (-not $nodeVer) { - Log-Line "āŒ Node.js exists but failed to run." $GLOBAL_LOG - throw "Node.js invocation failed" - } - Log-Line "šŸ” Checking if npm runs correctly..." $GLOBAL_LOG - $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 selected: $script:TECH_STACK" $GLOBAL_LOG; throw "Unknown tech stack" } - } - Log-Line "āœ… Prerequisites validated for $script:TECH_STACK" $GLOBAL_LOG -} - -# fix Python branch without ternary -function Get-PythonCmd { - if (Get-Command python3 -ErrorAction SilentlyContinue) { return "python3" } - return "python" -} +# 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 index cc5d0ab..0eed78a 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -1,550 +1,581 @@ -# ============================================== -# āš™ļø SETUP & RUN -# ============================================== - -# ===== 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 repo $REPO into $TARGET" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - - Push-Location $TARGET - try { - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - # Generate YAML config in the correct location - Log-Line "🧩 Generating YAML config (browserstack.yml)" $GLOBAL_LOG - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB - $localFlag = if ($UseLocal) { "true" } else { "false" } - - $yamlContent = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: testng -browserstackLocal: $localFlag -buildName: now-testng-java-web -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 - - $mvn = Get-MavenCommand -RepoDir $TARGET - Log-Line "āš™ļø Running '$mvn compile'" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("compile") -LogFile $LogFile -WorkingDirectory $TARGET) - - Log-Line "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET) - - } 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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - 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) - Log-Line "āœ… Created Python virtual environment" $GLOBAL_LOG - } - $venvPy = Get-VenvPython -VenvDir $venv - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - # Ensure SDK can find pytest on PATH - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB - $localFlag = if ($UseLocal) { "true" } else { "false" } - - $yamlContent = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-sample-python-web -projectName: NOW-Web-Test -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ - Set-Content "browserstack.yml" -Value $yamlContent - - Log-Line "āœ… Updated root-level browserstack.yml with platforms and credentials" $GLOBAL_LOG - - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Log-Line "šŸš€ Running 'browserstack-sdk pytest -s tests/bstack-sample-test.py'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET) - - } 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 repo $REPO into $TARGET" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - - Push-Location $TARGET - try { - Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $TARGET) - - # Generate capabilities JSON - Log-Line "🧩 Generating browser/OS capabilities" $GLOBAL_LOG - $caps = Generate-Web-Caps-Json -MaxTotalParallels $ParallelsPerPlatform - - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - $env:BSTACK_CAPS_JSON = $caps - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $localFlagStr = if ($UseLocal) { "true" } else { "false" } - $env:BROWSERSTACK_LOCAL = $localFlagStr - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - - Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET) - - Log-Line "āœ… Web NodeJS setup and test execution completed successfully." $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 = "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 - } - - Invoke-GitClone -Url "https://github.com/browserstack/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - 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 - [void](Invoke-External -Exe $venvPy -Arguments @("-m","pip","install","-r","requirements.txt") -LogFile $LogFile -WorkingDirectory $TARGET) - # Ensure SDK can find pytest on PATH - $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - # Prepare platform-specific YAMLs in android/ and ios/ - $originalPlatform = $APP_PLATFORM - - $script:APP_PLATFORM = "android" - $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $localFlag = if ($UseLocal) { "true" } else { "false" } - $androidYmlPath = Join-Path $TARGET "android\browserstack.yml" - $yamlContentAndroid = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-build-mobile -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlAndroid -"@ - Set-Content $androidYmlPath -Value $yamlContentAndroid - - $script:APP_PLATFORM = "ios" - $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $iosYmlPath = Join-Path $TARGET "ios\browserstack.yml" - $yamlContentIos = @" -userName: $BROWSERSTACK_USERNAME -accessKey: $BROWSERSTACK_ACCESS_KEY -framework: pytest -browserstackLocal: $localFlag -buildName: browserstack-build-mobile -projectName: NOW-Mobile-Test -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlIos -"@ - Set-Content $iosYmlPath -Value $yamlContentIos - - $script:APP_PLATFORM = $originalPlatform - - Log-Line "āœ… Wrote platform YAMLs to android/browserstack.yml and ios/browserstack.yml" $GLOBAL_LOG - - # Decide which directory to run based on APP_PLATFORM (default to android) - $runDirName = "android" - if ($APP_PLATFORM -eq "ios") { - $runDirName = "ios" - } - $runDir = Join-Path $TARGET $runDirName - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āš ļø BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āš ļø BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - Log-Line "šŸš€ Running 'cd $runDirName && browserstack-sdk pytest -s bstack_sample.py'" $GLOBAL_LOG - $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - Push-Location $runDir - try { - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - } finally { - Pop-Location - } - - } 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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "āœ… Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - - Push-Location $TARGET - try { - # Navigate to platform-specific directory - 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 - - # YAML config path - $env:BROWSERSTACK_CONFIG_FILE = ".\browserstack.yml" - $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $localFlag = if ($UseLocal) { "true" } else { "false" } - - # Append to existing YAML (repo has base config) - $yamlAppend = @" -app: $APP_URL -platforms: -$platforms -"@ - Add-Content -Path $env:BROWSERSTACK_CONFIG_FILE -Value $yamlAppend - - # Check if domain is private - if (Test-DomainPrivate) { - $UseLocal = $true - } - - # Log local flag status - if ($UseLocal) { - Log-Line "āœ… BrowserStack Local is ENABLED for this run." $GLOBAL_LOG - } else { - Log-Line "āœ… BrowserStack Local is DISABLED for this run." $GLOBAL_LOG - } - - $mvn = Get-MavenCommand -RepoDir (Get-Location).Path - Log-Line "āš™ļø Running '$mvn clean'" $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 "šŸš€ Running '$mvn test -P sample-test'. This could take a few minutes. Follow the Automation build here: https://automation.browserstack.com/" $GLOBAL_LOG - [void](Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path) - - } 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) - - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - - $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 - } - - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - - $testDir = Join-Path $TARGET "test" - Push-Location $testDir - try { - Log-Line "āš™ļø Running 'npm install'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir) - - # Generate mobile capabilities JSON file - Log-Line "🧩 Generating mobile capabilities JSON" $GLOBAL_LOG - $usageFile = Join-Path $GLOBAL_DIR "usage_file.json" - [void](Generate-Mobile-Caps-Json -MaxTotalParallels $ParallelsPerPlatform -OutputFile $usageFile) - Log-Line "āœ… Created usage_file.json at: $usageFile" $GLOBAL_LOG - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - $env:BSTACK_PARALLELS = $ParallelsPerPlatform - - Log-Line "šŸš€ Running 'npm run test'" $GLOBAL_LOG - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) - - } finally { - Pop-Location - Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) - } -} - -# ===== Wrappers with retry ===== -function Setup-Web { - Log-Line "Starting Web setup for $TECH_STACK" $WEB_LOG - Log-Line "🌐 ========================================" $GLOBAL_LOG - Log-Line "🌐 Starting WEB Testing ($TECH_STACK)" $GLOBAL_LOG - Log-Line "🌐 ========================================" $GLOBAL_LOG - - $localFlag = $false - $attempt = 1 - $success = $true - - $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) - if ($totalParallels -lt 1) { $totalParallels = 1 } - $parallelsPerPlatform = $totalParallels - - while ($attempt -le 1) { - Log-Line "[Web Setup]" $WEB_LOG - switch ($TECH_STACK) { - "Java" { - Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "BUILD FAILURE") { - $success = $false - } - } - } - "Python" { - Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "BUILD FAILURE") { - $success = $false - } - } - } - "NodeJS" { - Setup-Web-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG - # Add a small delay to ensure all output is flushed to disk - Start-Sleep -Milliseconds 500 - if (Test-Path $WEB_LOG) { - $content = Get-Content $WEB_LOG -Raw - if ($content -match "([1-9][0-9]*) passed, 0 failed") { - $success = $false - } - } - } - default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $WEB_LOG; return } - } - - if ($success) { - Log-Line "āœ… Web setup succeeded." $WEB_LOG - Log-Line "āœ… WEB Testing completed successfully" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG - break - } else { - Log-Line "āŒ Web setup ended without success; check $WEB_LOG for details" $WEB_LOG - Log-Line "āŒ WEB Testing completed with errors" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed web test logs: $WEB_LOG" $GLOBAL_LOG - break - } - } -} - - -function Setup-Mobile { - Log-Line "Starting Mobile setup for $TECH_STACK" $MOBILE_LOG - Log-Line "šŸ“± ========================================" $GLOBAL_LOG - Log-Line "šŸ“± Starting MOBILE APP Testing ($TECH_STACK)" $GLOBAL_LOG - Log-Line "šŸ“± ========================================" $GLOBAL_LOG - - $localFlag = $true - $attempt = 1 - $success = $false - - $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE)) - if ($totalParallels -lt 1) { $totalParallels = 1 } - $parallelsPerPlatform = $totalParallels - - while ($attempt -le 1) { - Log-Line "[Mobile Setup Attempt $attempt] browserstackLocal: $localFlag" $MOBILE_LOG - switch ($TECH_STACK) { - "Java" { Setup-Mobile-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - "Python" { Setup-Mobile-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - "NodeJS" { Setup-Mobile-NodeJS -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $MOBILE_LOG } - default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $MOBILE_LOG; return } - } - - # Add a small delay to ensure all output is flushed to disk (especially important for Java) - Start-Sleep -Milliseconds 500 - - if (!(Test-Path $MOBILE_LOG)) { - $content = "" - } else { - $content = Get-Content $MOBILE_LOG -Raw - } - - $LOCAL_FAILURE = $false - $SETUP_FAILURE = $false - - foreach ($p in $MOBILE_LOCAL_ERRORS) { if ($p -and ($content -match $p)) { $LOCAL_FAILURE = $true; break } } - foreach ($p in $MOBILE_SETUP_ERRORS) { if ($p -and ($content -match $p)) { $SETUP_FAILURE = $true; break } } - - # Check for BrowserStack link (success indicator) - if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { - $success = $true - } - - if ($success) { - Log-Line "āœ… Mobile setup succeeded" $MOBILE_LOG - Log-Line "āœ… MOBILE APP Testing completed successfully" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } elseif ($LOCAL_FAILURE -and $attempt -eq 1) { - $localFlag = $false - $attempt++ - Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $MOBILE_LOG - Log-Line "āš ļø Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." $GLOBAL_LOG - } elseif ($SETUP_FAILURE) { - Log-Line "āŒ Mobile test failed due to setup error. Check logs at: $MOBILE_LOG" $MOBILE_LOG - Log-Line "āŒ MOBILE APP Testing failed due to setup error" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } else { - Log-Line "āŒ Mobile setup ended without success; check $MOBILE_LOG for details" $MOBILE_LOG - Log-Line "āŒ MOBILE APP Testing completed with errors" $GLOBAL_LOG - Log-Line "šŸ“Š View detailed mobile test logs: $MOBILE_LOG" $GLOBAL_LOG - break - } - } -} +# 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 $WEB_LOG + + 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 $TEAM_PARALLELS_MAX_ALLOWED_WEB + $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 $WEB_LOG + + 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 $TEAM_PARALLELS_MAX_ALLOWED_WEB + $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 $WEB_LOG + + 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 $MOBILE_LOG + + 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 $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $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 $MOBILE_LOG + + 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 $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $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 $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $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 $MOBILE_LOG + + $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 + ) + + 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 } + + Log-Line "Total parallels allocated: $totalParallels" $GLOBAL_LOG + + $success = $false + $logFile = if ($SetupType -match "web") { $WEB_LOG } else { $MOBILE_LOG } + + 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 + ) + Setup-Environment -SetupType $TestType -TechStack $TechStack +} + + diff --git a/win/logging-utils.ps1 b/win/logging-utils.ps1 index 08599a0..68da254 100644 --- a/win/logging-utils.ps1 +++ b/win/logging-utils.ps1 @@ -1,12 +1,32 @@ -# ============================================== -# šŸŖ„ LOGGING HELPERS -# ============================================== +# 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)][string]$Message, + [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 @@ -17,15 +37,20 @@ function Log-Line { } } -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 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 bafe3ba..60a03fb 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -1,171 +1,91 @@ -#requires -version 5.0 +#requires -version 5.0 <# BrowserStack Onboarding (PowerShell 5.0, GUI) - - Full parity port of macOS bash + - Full parity port of macOS bash run.sh - Uses WinForms for GUI prompts - Logs to %USERPROFILE%\.browserstack\NOW\logs #> +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 -# ===== Global Variables ===== -$WORKSPACE_DIR = Join-Path $env:USERPROFILE ".browserstack" -$PROJECT_FOLDER = "NOW" - -$GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER -$LOG_DIR = Join-Path $GLOBAL_DIR "logs" -$GLOBAL_LOG = Join-Path $LOG_DIR "global.log" -$WEB_LOG = Join-Path $LOG_DIR "web_run_result.log" -$MOBILE_LOG = Join-Path $LOG_DIR "mobile_run_result.log" - -# Clear/prepare logs -if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR | Out-Null } -'' | Out-File -FilePath $GLOBAL_LOG -Encoding UTF8 -'' | Out-File -FilePath $WEB_LOG -Encoding UTF8 -'' | Out-File -FilePath $MOBILE_LOG -Encoding UTF8 - -# Script state -$BROWSERSTACK_USERNAME = "" -$BROWSERSTACK_ACCESS_KEY = "" -$TEST_TYPE = "" # Web / App / Both -$TECH_STACK = "" # Java / Python / JS -[double]$PARALLEL_PERCENTAGE = 1.00 - -$WEB_PLAN_FETCHED = $false -$MOBILE_PLAN_FETCHED = $false -[int]$TEAM_PARALLELS_MAX_ALLOWED_WEB = 0 -[int]$TEAM_PARALLELS_MAX_ALLOWED_MOBILE = 0 - -# URL handling -$DEFAULT_TEST_URL = "https://bstackdemo.com" -$CX_TEST_URL = $DEFAULT_TEST_URL - -# App handling -$APP_URL = "" -$APP_PLATFORM = "" # ios | android | all - -# Chosen Python command tokens (set during validation when Python is selected) -$PY_CMD = @() - -# ===== Error patterns (placeholders to match your original arrays) ===== -$WEB_SETUP_ERRORS = @("") -$WEB_LOCAL_ERRORS = @("") -$MOBILE_SETUP_ERRORS= @("") -$MOBILE_LOCAL_ERRORS= @("") - -# ===== Source Modular Scripts ===== -. "$PSScriptRoot\logging-utils.ps1" -. "$PSScriptRoot\common-utils.ps1" -. "$PSScriptRoot\user-interaction.ps1" -. "$PSScriptRoot\device-machine-allocation.ps1" -. "$PSScriptRoot\env-prequisite-checks.ps1" -. "$PSScriptRoot\env-setup-run.ps1" - - -# ===== Orchestration ===== -function Run-Setup { - Log-Line "Orchestration: TEST_TYPE=$TEST_TYPE, WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED, MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED" $GLOBAL_LOG - - $webRan = $false - $mobileRan = $false - - switch ($TEST_TYPE) { - "Web" { - if ($WEB_PLAN_FETCHED) { - Setup-Web - $webRan = $true - } else { - Log-Line "āš ļø Skipping Web setup — Web plan not fetched" $GLOBAL_LOG - } - } - "App" { - if ($MOBILE_PLAN_FETCHED) { - Setup-Mobile - $mobileRan = $true - } else { - Log-Line "āš ļø Skipping Mobile setup — Mobile plan not fetched" $GLOBAL_LOG - } - } - "Both" { - $ranAny = $false - if ($WEB_PLAN_FETCHED) { - Setup-Web - $webRan = $true - $ranAny = $true - } else { - Log-Line "āš ļø Skipping Web setup — Web plan not fetched" $GLOBAL_LOG - } - if ($MOBILE_PLAN_FETCHED) { - Setup-Mobile - $mobileRan = $true - $ranAny = $true - } else { - Log-Line "āš ļø Skipping Mobile setup — Mobile plan not fetched" $GLOBAL_LOG - } - if (-not $ranAny) { - Log-Line "āŒ Both Web and Mobile setup were skipped. Exiting." $GLOBAL_LOG - throw "No setups executed" - } - } - default { - Log-Line "āŒ Invalid TEST_TYPE: $TEST_TYPE" $GLOBAL_LOG - throw "Invalid TEST_TYPE" - } - } - - # Final Summary - Log-Line " " $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG - Log-Line "šŸ“‹ EXECUTION SUMMARY" $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG - if ($webRan) { - Log-Line "āœ… Web Testing: COMPLETED" $GLOBAL_LOG - Log-Line " šŸ“„ Logs: $WEB_LOG" $GLOBAL_LOG - } - if ($mobileRan) { - Log-Line "āœ… Mobile App Testing: COMPLETED" $GLOBAL_LOG - Log-Line " šŸ“„ Logs: $MOBILE_LOG" $GLOBAL_LOG - } - Log-Line "========================================" $GLOBAL_LOG - Log-Line "šŸŽ‰ All requested tests have been executed!" $GLOBAL_LOG - Log-Line "šŸ”— View results: https://automation.browserstack.com/" $GLOBAL_LOG - Log-Line "========================================" $GLOBAL_LOG -} +# ===== 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 ===== +# ===== Main flow (baseline steps then run) ===== try { - Ensure-Workspace - Ask-BrowserStack-Credentials - Ask-Test-Type - Ask-Tech-Stack + # 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 + + # Get test type and tech stack FIRST + if ($RunMode -match "--silent|--debug") { + $script:TEST_TYPE = if ($TT) { (Get-Culture).TextInfo.ToTitleCase($TT.ToLowerInvariant()) } else { $env:TEST_TYPE } + $script:TECH_STACK = if ($TSTACK) { (Get-Culture).TextInfo.ToTitleCase($TSTACK.ToLowerInvariant()) } else { $env:TECH_STACK } + Log-Line "ā„¹ļø Run Mode: $RunMode" $GLOBAL_LOG + } else { + Resolve-Test-Type -RunMode $RunMode -CliValue $TT + Resolve-Tech-Stack -RunMode $RunMode -CliValue $TSTACK + } + + # Setup log file path + $logFile = Join-Path $LOG_DIR ("{0}_{1}_run_result.log" -f $TEST_TYPE.ToLowerInvariant(), $TECH_STACK.ToLowerInvariant()) + Log-Line "ā„¹ļø Log file path: $logFile" $GLOBAL_LOG + Set-RunLogFile $logFile + + # 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 - Fetch-Plan-Details + + # 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 - # Check for proxy configuration - Log-Line "ā„¹ļø Checking proxy in environment" $GLOBAL_LOG - $proxyCheckScript = Join-Path $PSScriptRoot "env-prequisite-checks.ps1" - if (Test-Path $proxyCheckScript) { - try { - & $proxyCheckScript -BrowserStackUsername $BROWSERSTACK_USERNAME -BrowserStackAccessKey $BROWSERSTACK_ACCESS_KEY - if ($env:PROXY_HOST -and $env:PROXY_PORT) { - Log-Line "āœ… Proxy configured: $env:PROXY_HOST:$env:PROXY_PORT" $GLOBAL_LOG - } else { - Log-Line "ā„¹ļø No proxy configured or proxy check failed" $GLOBAL_LOG - } - } catch { - Log-Line "āš ļø Proxy check script failed: $($_.Exception.Message)" $GLOBAL_LOG - } - } else { - Log-Line "āš ļø Proxy check script not found at: $proxyCheckScript" $GLOBAL_LOG - } - - Run-Setup + # Run the setup + Run-Setup -TestType $TEST_TYPE -TechStack $TECH_STACK + } catch { Log-Line " " $GLOBAL_LOG Log-Line "========================================" $GLOBAL_LOG @@ -178,4 +98,5 @@ try { Log-Line " Mobile: $MOBILE_LOG" $GLOBAL_LOG Log-Line "========================================" $GLOBAL_LOG throw -} \ No newline at end of file +} + diff --git a/win/user-interaction.ps1 b/win/user-interaction.ps1 index 426aa1b..c7cd33b 100644 --- a/win/user-interaction.ps1 +++ b/win/user-interaction.ps1 @@ -1,6 +1,4 @@ -# ============================================== -# šŸ‘¤ USER INTERACTION -# ============================================== +# User interaction helpers (GUI + CLI) for Windows BrowserStack NOW. function Show-InputBox { param( @@ -71,61 +69,6 @@ function Show-PasswordBox { return [string]$form.Tag } -function Show-ChoiceBox { - param( - [string]$Title = "Choose", - [string]$Prompt = "Select one:", - [string[]]$Choices, - [string]$DefaultChoice - ) - $form = New-Object System.Windows.Forms.Form - $form.Text = $Title - $form.Size = New-Object System.Drawing.Size(420, 240) - $form.StartPosition = "CenterScreen" - - $label = New-Object System.Windows.Forms.Label - $label.Text = $Prompt - $label.AutoSize = $true - $label.Location = New-Object System.Drawing.Point(10, 10) - $form.Controls.Add($label) - - $group = New-Object System.Windows.Forms.Panel - $group.Location = New-Object System.Drawing.Point(10, 35) - $group.Width = 380 - $startY = 10 - $spacing = 28 - - $radios = @() - [int]$i = 0 - foreach ($c in $Choices) { - $rb = New-Object System.Windows.Forms.RadioButton - $rb.Text = $c - $rb.AutoSize = $true - $rb.Location = New-Object System.Drawing.Point(10, ($startY + $i * $spacing)) - if ($c -eq $DefaultChoice) { $rb.Checked = $true } - $group.Controls.Add($rb) - $radios += $rb - $i++ - } - $group.Height = [Math]::Max(120, $startY + ($Choices.Count * $spacing) + 10) - $form.Controls.Add($group) - - $ok = New-Object System.Windows.Forms.Button - $ok.Text = "OK" - $ok.Location = New-Object System.Drawing.Point(300, ($group.Bottom + 10)) - $ok.Add_Click({ - foreach ($rb in $radios) { if ($rb.Checked) { $form.Tag = $rb.Text; break } } - $form.Close() - }) - $form.Controls.Add($ok) - - $form.Height = $ok.Bottom + 70 - $form.AcceptButton = $ok - [void]$form.ShowDialog() - return [string]$form.Tag -} - -# === NEW: Big clickable button chooser === function Show-ClickChoice { param( [string]$Title = "Choose", @@ -195,7 +138,7 @@ function Show-ClickChoice { function Show-OpenFileDialog { param( [string]$Title = "Select File", - [string]$Filter = "All files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" + [string]$Filter = "All files (*.*)|*.*" ) $ofd = New-Object System.Windows.Forms.OpenFileDialog $ofd.Title = $Title @@ -207,14 +150,28 @@ function Show-OpenFileDialog { return "" } -# ===== Baseline interactions ===== function Ask-BrowserStack-Credentials { - $script:BROWSERSTACK_USERNAME = Show-InputBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Username:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" -DefaultText "" + param( + [string]$RunMode = "--interactive", + [string]$UsernameFromEnv, + [string]$AccessKeyFromEnv + ) + if ($RunMode -match "--silent" -or $RunMode -match "--debug") { + $script:BROWSERSTACK_USERNAME = if ($UsernameFromEnv) { $UsernameFromEnv } else { $env:BROWSERSTACK_USERNAME } + $script:BROWSERSTACK_ACCESS_KEY = if ($AccessKeyFromEnv) { $AccessKeyFromEnv } else { $env:BROWSERSTACK_ACCESS_KEY } + 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 + 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 throw "Username is required" } - $script:BROWSERSTACK_ACCESS_KEY = Show-PasswordBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Access Key:`n`nNote: Locate it in your BrowserStack account page`nhttps://www.browserstack.com/accounts/profile/details" + $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 throw "Access Key is required" @@ -222,8 +179,18 @@ function Ask-BrowserStack-Credentials { Log-Line "āœ… BrowserStack credentials captured (access key hidden)" $GLOBAL_LOG } -# === UPDATED: click-select for Web/App/Both === -function Ask-Test-Type { +function Resolve-Test-Type { + param( + [string]$RunMode, + [string]$CliValue + ) + if ($RunMode -match "--silent" -or $RunMode -match "--debug") { + if (-not $CliValue) { $CliValue = $env:TEST_TYPE } + if ([string]::IsNullOrWhiteSpace($CliValue)) { throw "TEST_TYPE is required in silent/debug mode." } + $script:TEST_TYPE = (Get-Culture).TextInfo.ToTitleCase($CliValue.ToLowerInvariant()) + return + } + $choice = Show-ClickChoice -Title "Testing Type" ` -Prompt "What do you want to run?" ` -Choices @("Web","App","Both") ` @@ -231,16 +198,21 @@ function Ask-Test-Type { if ([string]::IsNullOrWhiteSpace($choice)) { throw "No testing type selected" } $script:TEST_TYPE = $choice Log-Line "āœ… Selected Testing Type: $script:TEST_TYPE" $GLOBAL_LOG +} - switch ($script:TEST_TYPE) { - "Web" { Ask-User-TestUrl } - "App" { Ask-And-Upload-App } - "Both" { Ask-User-TestUrl; Ask-And-Upload-App } +function Resolve-Tech-Stack { + param( + [string]$RunMode, + [string]$CliValue + ) + if ($RunMode -match "--silent" -or $RunMode -match "--debug") { + if (-not $CliValue) { $CliValue = $env:TECH_STACK } + if ([string]::IsNullOrWhiteSpace($CliValue)) { throw "TECH_STACK is required in silent/debug mode." } + $textInfo = (Get-Culture).TextInfo + $script:TECH_STACK = $textInfo.ToTitleCase($CliValue.ToLowerInvariant()) + return } -} -# === UPDATED: click-select for Tech Stack === -function Ask-Tech-Stack { $choice = Show-ClickChoice -Title "Tech Stack" ` -Prompt "Select your installed language / framework:" ` -Choices @("Java","Python","NodeJS") ` @@ -251,61 +223,65 @@ function Ask-Tech-Stack { } function Ask-User-TestUrl { - $u = 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 "" - if ([string]::IsNullOrWhiteSpace($u)) { - $script:CX_TEST_URL = $DEFAULT_TEST_URL - Log-Line "āš ļø No URL entered. Falling back to default: $script:CX_TEST_URL" $GLOBAL_LOG - } else { - $script:CX_TEST_URL = $u - Log-Line "🌐 Using custom test URL: $script:CX_TEST_URL" $GLOBAL_LOG + 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 } + return } -} -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) + $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 "" + if ([string]::IsNullOrWhiteSpace($testUrl)) { + $testUrl = $DEFAULT_TEST_URL + Log-Line "āš ļø No URL entered. Falling back to default: $testUrl" $GLOBAL_LOG + } else { + Log-Line "🌐 Using custom test URL: $testUrl" $GLOBAL_LOG + } + $script:CX_TEST_URL = $testUrl } -function Ask-And-Upload-App { - # First, show a choice screen for Sample App vs Browse +function Show-OpenOrSampleAppDialog { $appChoice = Show-ClickChoice -Title "App Selection" ` -Prompt "Choose an app to test:" ` -Choices @("Sample App","Browse") ` -DefaultChoice "Sample App" - - if ([string]::IsNullOrWhiteSpace($appChoice) -or $appChoice -eq "Sample App") { - Log-Line "āš ļø Using default sample app: bs://sample.app" $GLOBAL_LOG - $script:APP_URL = "bs://sample.app" - $script:APP_PLATFORM = "all" - return + return $appChoice +} + +function Invoke-SampleAppUpload { + $headers = @{ + Authorization = (Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY) } - - # User chose "Browse", so open file picker - $path = Show-OpenFileDialog -Title "šŸ“± Select your .apk or .ipa file" -Filter "App Files (*.apk;*.ipa)|*.apk;*.ipa|All files (*.*)|*.*" - if ([string]::IsNullOrWhiteSpace($path)) { - Log-Line "āš ļø No app selected. Using default sample app: bs://sample.app" $GLOBAL_LOG - $script:APP_URL = "bs://sample.app" - $script:APP_PLATFORM = "all" - return + $body = @{ + url = "https://www.browserstack.com/app-automate/sample-apps/android/WikipediaSample.apk" } - - $ext = [System.IO.Path]::GetExtension($path).ToLowerInvariant() + $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" { $script:APP_PLATFORM = "android" } - ".ipa" { $script:APP_PLATFORM = "ios" } - default { Log-Line "āŒ Unsupported file type. Only .apk or .ipa allowed." $GLOBAL_LOG; throw "Unsupported app file" } + ".apk" { $platform = "android" } + ".ipa" { $platform = "ios" } + default { throw "Unsupported app file (only .apk/.ipa)" } } - Log-Line "ā¬†ļø Uploading $path to BrowserStack..." $GLOBAL_LOG - - # Create multipart form data manually for PowerShell 5.1 compatibility $boundary = [System.Guid]::NewGuid().ToString() $LF = "`r`n" - $fileBin = [System.IO.File]::ReadAllBytes($path) - $fileName = [System.IO.Path]::GetFileName($path) - + $fileBin = [System.IO.File]::ReadAllBytes($FilePath) + $fileName = [System.IO.Path]::GetFileName($FilePath) + $bodyLines = ( "--$boundary", "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", @@ -313,18 +289,89 @@ function Ask-And-Upload-App { [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)) { - Log-Line "āŒ Upload failed. Response: $(ConvertTo-Json $resp -Depth 5)" $GLOBAL_LOG throw "Upload failed" } - $script:APP_URL = $url - Log-Line "āœ… App uploaded successfully: $script:APP_URL" $GLOBAL_LOG + return @{ + Url = $url + Platform = $platform + } +} + +function Ask-And-Upload-App { + param( + [string]$RunMode, + [string]$CliPath, + [string]$CliPlatform + ) + + if ($RunMode -match "--silent" -or $RunMode -match "--debug") { + if ($CliPath) { + $result = Invoke-CustomAppUpload -FilePath $CliPath + $script:APP_URL = $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 + $script:APP_PLATFORM = $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 + $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 (*.*)|*.*" + if ([string]::IsNullOrWhiteSpace($path)) { + $result = Invoke-SampleAppUpload + Log-Line "āš ļø No app selected. Using sample app: $($result.Url)" $GLOBAL_LOG + $script:APP_URL = $result.Url + $script:APP_PLATFORM = $result.Platform + return + } + + $result = Invoke-CustomAppUpload -FilePath $path + $script:APP_URL = $result.Url + $script:APP_PLATFORM = $result.Platform + Log-Line "āœ… App uploaded successfully: $($result.Url)" $GLOBAL_LOG +} + +# ===== Perform next steps based on test type (like Mac's perform_next_steps_based_on_test_type) ===== +function Perform-NextSteps-BasedOnTestType { + param( + [string]$TestType, + [string]$RunMode, + [string]$TestUrl, + [string]$AppPath, + [string]$AppPlatform + ) + + switch -Regex ($TestType) { + "^Web$|^web$" { + Ask-User-TestUrl -RunMode $RunMode -CliValue $TestUrl + } + "^App$|^app$" { + Ask-And-Upload-App -RunMode $RunMode -CliPath $AppPath -CliPlatform $AppPlatform + } + "^Both$|^both$" { + Ask-User-TestUrl -RunMode $RunMode -CliValue $TestUrl + Ask-And-Upload-App -RunMode $RunMode -CliPath $AppPath -CliPlatform $AppPlatform + } + } } + From 8b07e930f21217851f91e35072dc3163f27715bd Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Tue, 2 Dec 2025 01:11:36 +0530 Subject: [PATCH 11/36] Test Windows-only CI --- .github/workflows/test-scripts.yml | 230 ++++++++++++++++------------- 1 file changed, 126 insertions(+), 104 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 1f155f1..8549852 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -14,6 +14,7 @@ on: jobs: test-mac: name: Test mac/run.sh on macOS + if: false runs-on: macos-latest timeout-minutes: 15 environment: BrowserStack @@ -91,15 +92,15 @@ jobs: TURL: https://bstackdemo.com run: | echo "Running integration tests in silent mode..." - + # Set default values if secrets are not provided BROWSERSTACK_USERNAME="${BROWSERSTACK_USERNAME:-test_user}" BROWSERSTACK_ACCESS_KEY="${BROWSERSTACK_ACCESS_KEY:-test_key}" - + export BROWSERSTACK_USERNAME export BROWSERSTACK_ACCESS_KEY export TURL - + # Test configurations test_configs=( "web java" @@ -109,7 +110,7 @@ jobs: "web nodejs" "app nodejs" ) - + for config in "${test_configs[@]}"; do read -r test_type tech_stack <<< "$config" echo "================================" @@ -148,9 +149,9 @@ jobs: fi unset exit_code done - + echo "āœ… All integration tests completed" - + - name: Sync BrowserStack logs to workspace if: always() run: | @@ -166,67 +167,93 @@ jobs: with: name: browserstack-logs-macos path: | - ${{ github.workspace }}/bs-logs - /tmp/run_test_*.log + ${{ github.workspace }}/bs-logs + /tmp/run_test_*.log retention-days: 30 if-no-files-found: ignore test-windows: name: Test win/run.ps1 on Windows runs-on: windows-latest - timeout-minutes: 15 + timeout-minutes: 20 environment: BrowserStack + defaults: + run: + shell: powershell + steps: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: python-version: '3.12' + - name: Check PowerShell version run: | $PSVersionTable.PSVersion Write-Host "āœ… PowerShell version check complete" - + - name: Validate PowerShell script syntax run: | Write-Host "Validating win/run.ps1 syntax..." $ScriptPath = "win/run.ps1" - $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $ScriptPath), [ref]$null) + $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $ScriptPath), [ref]$null) Write-Host "āœ… win/run.ps1 syntax is valid" - name: Validate supporting PowerShell scripts syntax run: | Write-Host "Validating supporting PowerShell scripts..." - $Scripts = @("win/common-utils.ps1", "win/logging-utils.ps1", "win/env-prequisite-checks.ps1", "win/user-interaction.ps1", "win/env-setup-run.ps1") + $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" + ) foreach ($Script in $Scripts) { - $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content $Script), [ref]$null) + $null = [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $Script), [ref]$null) Write-Host "āœ… $Script syntax is valid" } - + - name: Run PSScriptAnalyzer run: | - Write-Host "Installing PSScriptAnalyzer..." - Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -ErrorAction SilentlyContinue + Write-Host "Installing PSScriptAnalyzer if needed..." + if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) { + Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -Scope CurrentUser + } + Write-Host "Running PSScriptAnalyzer..." - Invoke-ScriptAnalyzer -Path "win/run.ps1" -Recurse -ReportSummary || $true - Write-Host "āœ… PSScriptAnalyzer analysis complete" - + Invoke-ScriptAnalyzer -Path "win" -Recurse -ReportSummary -ErrorAction Continue + Write-Host "āœ… PSScriptAnalyzer analysis complete (continuing even if issues are found)" + - name: Check script file encoding run: | Write-Host "Checking PowerShell script encoding..." $ScriptPath = "win/run.ps1" - $Encoding = (Get-Item $ScriptPath).EncodingInfo - Write-Host "File encoding: $Encoding" + $bytes = [System.IO.File]::ReadAllBytes($ScriptPath) + + $encoding = "Unknown / ASCII / UTF-8 without BOM" + if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { + $encoding = "UTF-8 with BOM" + } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { + $encoding = "UTF-16 LE" + } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { + $encoding = "UTF-16 BE" + } + + Write-Host "Detected encoding (heuristic): $encoding" Write-Host "āœ… Encoding check complete" - + - name: Verify required dependencies run: | Write-Host "Checking required dependencies..." - if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… curl found" } - if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… git found" } + 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() env: @@ -235,103 +262,98 @@ jobs: TURL: https://bstackdemo.com run: | Write-Host "Running integration tests in silent mode..." - - # Set default values if secrets are not provided + + # Use defaults if secrets are missing (for local / dry runs) $BrowserStackUsername = if ($env:BROWSERSTACK_USERNAME) { $env:BROWSERSTACK_USERNAME } else { "test_user" } $BrowserStackAccessKey = if ($env:BROWSERSTACK_ACCESS_KEY) { $env:BROWSERSTACK_ACCESS_KEY } else { "test_key" } $TestUrl = $env:TURL - - # Export environment variables + $env:BROWSERSTACK_USERNAME = $BrowserStackUsername $env:BROWSERSTACK_ACCESS_KEY = $BrowserStackAccessKey $env:TURL = $TestUrl - - # Test configurations + + # 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") + @("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 + foreach ($config in $testConfigs) { - $testType = $config[0] - $techStack = $config[1] - - Write-Host "================================" - Write-Host "Testing: .\win\run.ps1 --silent $testType $techStack" - Write-Host "================================" - - # Create log file path - $logPath = "C:\Temp\run_test_${testType}_${techStack}.log" - New-Item -ItemType Directory -Path "C:\Temp" -Force -ErrorAction SilentlyContinue | Out-Null - - # Run with timeout (using job for timeout capability) - $job = Start-Job -ScriptBlock { - param($path, $testType, $techStack, $logPath) - & $path --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append - } -ArgumentList ".\win\run.ps1", $testType, $techStack, $logPath - - # Wait for job with 600 second timeout - $timeout = New-TimeSpan -Seconds 600 - $completed = Wait-Job -Job $job -Timeout 600 - - if ($completed) { - $result = Receive-Job -Job $job - if ($job.State -eq "Completed") { - Write-Host "āœ… .\win\run.ps1 --silent $testType $techStack completed successfully" - } else { - Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack exited with state: $($job.State)" - if (Test-Path $logPath) { - Write-Host "Log output (last 20 lines):" - Get-Content -Path $logPath -Tail 20 - } - } - } else { - Write-Host "āš ļø .\win\run.ps1 --silent $testType $techStack timed out after 600 seconds" - Stop-Job -Job $job - if (Test-Path $logPath) { - Write-Host "Log output (last 20 lines):" - Get-Content -Path $logPath -Tail 20 - } + $testType = $config[0] + $techStack = $config[1] + + Write-Host "================================" + Write-Host "Testing: $scriptPath --silent $testType $techStack" + Write-Host "================================" + + $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" + + & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append + $exitCode = $LASTEXITCODE + + 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 } - - Remove-Job -Job $job -Force + } + } + + if ($overallFailed) { + Write-Error "One or more configurations failed." + exit 1 } - - Write-Host "āœ… All integration tests completed" - + + Write-Host "āœ… All integration tests completed successfully" + - name: Sync BrowserStack logs to workspace (Windows) if: always() run: | - $dest = "${env:GITHUB_WORKSPACE}\bs-logs" + $dest = Join-Path $env:GITHUB_WORKSPACE "bs-logs" New-Item -ItemType Directory -Force -Path $dest | Out-Null - $logPath = "$env:USERPROFILE\.browserstack\NOW\logs" + $bsLogPath = Join-Path $env:USERPROFILE ".browserstack\NOW\logs" + $tempLogDir = Join-Path $env:TEMP "now-tests" - if (Test-Path $logPath) { - Write-Host "Copying logs from $logPath" - Copy-Item -Path "$logPath\*" -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue + if (Test-Path $bsLogPath) { + Write-Host "Copying logs from $bsLogPath" + Copy-Item -Path (Join-Path $bsLogPath "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue + } else { + Write-Host "No logs found at $bsLogPath" } - else { - Write-Host "No logs found at $logPath" + + if (Test-Path $tempLogDir) { + Write-Host "Copying integration logs from $tempLogDir" + Copy-Item -Path (Join-Path $tempLogDir "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue } - + - name: Upload BrowserStack Logs as Artifacts if: always() uses: actions/upload-artifact@v4 with: name: browserstack-logs-windows - path: | - ${{ github.workspace }}/bs-logs - C:\Temp\run_test_*.log + path: ${{ github.workspace }}/bs-logs retention-days: 30 if-no-files-found: ignore test-linux: name: Test mac/run.sh on Linux + if: false runs-on: ubuntu-latest timeout-minutes: 15 environment: BrowserStack @@ -415,15 +437,15 @@ jobs: TURL: https://bstackdemo.com run: | echo "Running integration tests in silent mode..." - + # Set default values if secrets are not provided BROWSERSTACK_USERNAME="${BROWSERSTACK_USERNAME:-test_user}" BROWSERSTACK_ACCESS_KEY="${BROWSERSTACK_ACCESS_KEY:-test_key}" - + export BROWSERSTACK_USERNAME export BROWSERSTACK_ACCESS_KEY export TURL - + # Test configurations test_configs=( "web java" @@ -433,7 +455,7 @@ jobs: "web nodejs" "app nodejs" ) - + for config in "${test_configs[@]}"; do read -r test_type tech_stack <<< "$config" echo "================================" @@ -456,7 +478,7 @@ jobs: fi unset exit_code done - + echo "āœ… All integration tests completed" - name: Sync BrowserStack logs to workspace @@ -483,16 +505,15 @@ jobs: test-summary: name: Test Summary runs-on: ubuntu-latest - needs: [test-mac, test-linux] + needs: [test-windows] if: always() steps: - name: Check test results run: | echo "=== Test Results Summary ===" - echo "macOS Tests: ${{ needs.test-mac.result }}" - echo "Linux Tests: ${{ needs.test-linux.result }}" - - if [ "${{ needs.test-mac.result }}" = "failure" ] || [ "${{ needs.test-linux.result }}" = "failure" ]; then + echo "Windows Tests: ${{ needs.test-windows.result }}" + + if [ "${{ needs.test-windows.result }}" = "failure" ]; then echo "āŒ Some tests failed" exit 1 fi @@ -503,4 +524,5 @@ jobs: run: | echo "āœ… All script validations passed successfully!" echo "- mac/run.sh and supporting scripts validated on macOS and Linux" - echo "- win/run.ps1 and supporting scripts validated on Windows (temporarily disabled)" + echo "- win/run.ps1 and supporting scripts validated on Windows" + echo "- win/run.ps1 and supporting scripts validated on Windows" From 3611b1e1cfdeb80be58ae2beea42a3d4c96f83c9 Mon Sep 17 00:00:00 2001 From: Himanshu Chougule <73215784+himanshu-02@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:34:20 +0530 Subject: [PATCH 12/36] increase time gha --- .github/workflows/test-scripts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 8549852..85e0c07 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -175,7 +175,7 @@ jobs: test-windows: name: Test win/run.ps1 on Windows runs-on: windows-latest - timeout-minutes: 20 + timeout-minutes: 25 environment: BrowserStack defaults: run: From 3dbb017e4bd60c32ab3999e94fc4323ebd25371c Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Wed, 3 Dec 2025 20:15:02 +0530 Subject: [PATCH 13/36] app js --- .github/workflows/test-scripts.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 85e0c07..f8f9ac6 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -276,11 +276,11 @@ jobs: $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" $testConfigs = @( - @("web", "java"), - @("app", "java"), - @("web", "python"), - @("app", "python"), - @("web", "nodejs"), + # @("web", "java"), + # @("app", "java"), + # @("web", "python"), + # @("app", "python"), + # @("web", "nodejs"), @("app", "nodejs") ) From a82570206b2fb809cde81440426bf86f38511b53 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Wed, 3 Dec 2025 20:17:48 +0530 Subject: [PATCH 14/36] app js --- .github/workflows/test-scripts.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index f8f9ac6..e7e6098 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -276,11 +276,6 @@ jobs: $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" $testConfigs = @( - # @("web", "java"), - # @("app", "java"), - # @("web", "python"), - # @("app", "python"), - # @("web", "nodejs"), @("app", "nodejs") ) From 86e9a9425a9574582aed1c2ac657e61127b5dff0 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Wed, 3 Dec 2025 21:28:18 +0530 Subject: [PATCH 15/36] revert --- .github/workflows/test-scripts.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index e7e6098..85e0c07 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -276,6 +276,11 @@ jobs: $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" $testConfigs = @( + @("web", "java"), + @("app", "java"), + @("web", "python"), + @("app", "python"), + @("web", "nodejs"), @("app", "nodejs") ) From c762b4ba866ce66f7bbecdfcd43bae0cc29fd5f3 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 4 Dec 2025 00:05:56 +0530 Subject: [PATCH 16/36] multi file logs + remove both option --- win/common-utils.ps1 | 84 ++++++++++++++++++++++++---------------- win/env-setup-run.ps1 | 14 +++---- win/run.ps1 | 43 +++++++++++++------- win/user-interaction.ps1 | 21 ++++++---- 4 files changed, 99 insertions(+), 63 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 4e9d734..105bc69 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -6,14 +6,14 @@ $script:PROJECT_FOLDER = "NOW" $script:GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER $script:LOG_DIR = Join-Path $GLOBAL_DIR "logs" -$script:GLOBAL_LOG = Join-Path $LOG_DIR "global.log" -$script:WEB_LOG = Join-Path $LOG_DIR "web_run_result.log" -$script:MOBILE_LOG = Join-Path $LOG_DIR "mobile_run_result.log" +$script:GLOBAL_LOG = "" +$script:WEB_LOG = "" +$script:MOBILE_LOG = "" # Script state $script:BROWSERSTACK_USERNAME = "" $script:BROWSERSTACK_ACCESS_KEY = "" -$script:TEST_TYPE = "" # Web / App / Both +$script:TEST_TYPE = "" # Web / App $script:TECH_STACK = "" # Java / Python / JS [double]$script:PARALLEL_PERCENTAGE = 1.00 @@ -58,10 +58,16 @@ function Clear-OldLogs { if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR | Out-Null } - '' | Out-File -FilePath $GLOBAL_LOG -Encoding UTF8 - '' | Out-File -FilePath $WEB_LOG -Encoding UTF8 - '' | Out-File -FilePath $MOBILE_LOG -Encoding UTF8 - Log-Line "āœ… Logs cleared and fresh run initiated." $GLOBAL_LOG + + $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 ===== @@ -304,39 +310,49 @@ function Get-BasicAuthHeader { function Fetch-Plan-Details { param([string]$TestType) - Log-Line "ā„¹ļø Fetching BrowserStack plan for $TestType" $GLOBAL_LOG + 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 } - if ($TestType -in @("Web","Both","web","both")) { - 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 + 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." + } } - } - if ($TestType -in @("App","Both","app","both")) { - 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 + "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." } } - if ( ($TestType -match "^Web$|^web$" -and -not $WEB_PLAN_FETCHED) -or - ($TestType -match "^App$|^app$" -and -not $MOBILE_PLAN_FETCHED) -or - ($TestType -match "^Both$|^both$" -and -not ($WEB_PLAN_FETCHED -or $MOBILE_PLAN_FETCHED)) ) { - Log-Line "āŒ Unauthorized to fetch required plan(s) or failed request(s). Exiting." $GLOBAL_LOG - throw "Plan fetch failed" - } - - Log-Line "ā„¹ļø Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" $GLOBAL_LOG + 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/env-setup-run.ps1 b/win/env-setup-run.ps1 index 0eed78a..7119f47 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -14,7 +14,7 @@ function Setup-Web-Java { } Log-Line "ā„¹ļø Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) Push-Location $TARGET try { @@ -86,7 +86,7 @@ function Setup-Web-Python { } Log-Line "ā„¹ļø Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) Push-Location $TARGET try { @@ -165,7 +165,7 @@ function Setup-Web-NodeJS { 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 $WEB_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) Push-Location $TARGET try { @@ -225,7 +225,7 @@ function Setup-Mobile-Java { } Log-Line "ā„¹ļø Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) Push-Location $TARGET try { @@ -302,7 +302,7 @@ function Setup-Mobile-Python { } Log-Line "ā„¹ļø Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) Push-Location $TARGET try { @@ -407,7 +407,7 @@ function Setup-Mobile-NodeJS { } Log-Line "ā„¹ļø Cloning repository: $REPO" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) $testDir = Join-Path $TARGET "test" Push-Location $testDir @@ -525,7 +525,7 @@ function Setup-Environment { Log-Line "Total parallels allocated: $totalParallels" $GLOBAL_LOG $success = $false - $logFile = if ($SetupType -match "web") { $WEB_LOG } else { $MOBILE_LOG } + $logFile = Get-RunLogFile switch ($TechStack) { "Java" { diff --git a/win/run.ps1 b/win/run.ps1 index 60a03fb..f547f33 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -32,24 +32,41 @@ $script:PSScriptRootResolved = Split-Path -Parent $MyInvocation.MyCommand.Path # ===== Main flow (baseline steps then run) ===== try { - # 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 - - # Get test type and tech stack FIRST + # Get test type and tech stack before logging if ($RunMode -match "--silent|--debug") { - $script:TEST_TYPE = if ($TT) { (Get-Culture).TextInfo.ToTitleCase($TT.ToLowerInvariant()) } else { $env:TEST_TYPE } - $script:TECH_STACK = if ($TSTACK) { (Get-Culture).TextInfo.ToTitleCase($TSTACK.ToLowerInvariant()) } else { $env:TECH_STACK } - Log-Line "ā„¹ļø Run Mode: $RunMode" $GLOBAL_LOG + $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 - $logFile = Join-Path $LOG_DIR ("{0}_{1}_run_result.log" -f $TEST_TYPE.ToLowerInvariant(), $TECH_STACK.ToLowerInvariant()) - Log-Line "ā„¹ļø Log file path: $logFile" $GLOBAL_LOG + # 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 @@ -93,9 +110,7 @@ try { Log-Line "========================================" $GLOBAL_LOG Log-Line "Error: $($_.Exception.Message)" $GLOBAL_LOG Log-Line "Check logs for details:" $GLOBAL_LOG - Log-Line " Global: $GLOBAL_LOG" $GLOBAL_LOG - Log-Line " Web: $WEB_LOG" $GLOBAL_LOG - Log-Line " Mobile: $MOBILE_LOG" $GLOBAL_LOG + Log-Line (" Run Log: {0}" -f (Get-RunLogFile)) $GLOBAL_LOG Log-Line "========================================" $GLOBAL_LOG throw } diff --git a/win/user-interaction.ps1 b/win/user-interaction.ps1 index c7cd33b..307bb8e 100644 --- a/win/user-interaction.ps1 +++ b/win/user-interaction.ps1 @@ -187,17 +187,20 @@ function Resolve-Test-Type { if ($RunMode -match "--silent" -or $RunMode -match "--debug") { if (-not $CliValue) { $CliValue = $env:TEST_TYPE } if ([string]::IsNullOrWhiteSpace($CliValue)) { throw "TEST_TYPE is required in silent/debug mode." } - $script:TEST_TYPE = (Get-Culture).TextInfo.ToTitleCase($CliValue.ToLowerInvariant()) + $candidate = (Get-Culture).TextInfo.ToTitleCase($CliValue.ToLowerInvariant()) + if ($candidate -notin @("Web","App")) { + throw "TEST_TYPE must be either 'Web' or 'App'." + } + $script:TEST_TYPE = $candidate return } $choice = Show-ClickChoice -Title "Testing Type" ` -Prompt "What do you want to run?" ` - -Choices @("Web","App","Both") ` + -Choices @("Web","App") ` -DefaultChoice "Web" if ([string]::IsNullOrWhiteSpace($choice)) { throw "No testing type selected" } $script:TEST_TYPE = $choice - Log-Line "āœ… Selected Testing Type: $script:TEST_TYPE" $GLOBAL_LOG } function Resolve-Tech-Stack { @@ -209,7 +212,11 @@ function Resolve-Tech-Stack { if (-not $CliValue) { $CliValue = $env:TECH_STACK } if ([string]::IsNullOrWhiteSpace($CliValue)) { throw "TECH_STACK is required in silent/debug mode." } $textInfo = (Get-Culture).TextInfo - $script:TECH_STACK = $textInfo.ToTitleCase($CliValue.ToLowerInvariant()) + $candidate = $textInfo.ToTitleCase($CliValue.ToLowerInvariant()) + if ($candidate -notin @("Java","Python","NodeJS")) { + throw "TECH_STACK must be one of: Java, Python, NodeJS." + } + $script:TECH_STACK = $candidate return } @@ -219,7 +226,6 @@ function Resolve-Tech-Stack { -DefaultChoice "Java" if ([string]::IsNullOrWhiteSpace($choice)) { throw "No tech stack selected" } $script:TECH_STACK = $choice - Log-Line "āœ… Selected Tech Stack: $script:TECH_STACK" $GLOBAL_LOG } function Ask-User-TestUrl { @@ -368,9 +374,8 @@ function Perform-NextSteps-BasedOnTestType { "^App$|^app$" { Ask-And-Upload-App -RunMode $RunMode -CliPath $AppPath -CliPlatform $AppPlatform } - "^Both$|^both$" { - Ask-User-TestUrl -RunMode $RunMode -CliValue $TestUrl - Ask-And-Upload-App -RunMode $RunMode -CliPath $AppPath -CliPlatform $AppPlatform + default { + throw "Unsupported TEST_TYPE: $TestType. Allowed values: Web, App." } } } From fc5bf4d3d23f3b2bbb7b3f591863ab73a927cfc2 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 4 Dec 2025 12:56:05 +0530 Subject: [PATCH 17/36] only run app nodejs --- .github/workflows/test-scripts.yml | 72 +++++++++++++++++++----------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 85e0c07..91b19c6 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -185,6 +185,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: @@ -276,43 +281,60 @@ jobs: $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" + + Write-Host "================================" + Write-Host "Testing: $scriptPath --silent app nodejs" + Write-Host "================================" + + & $scriptPath --silent app nodejs 2>&1 | Tee-Object -FilePath $logPath -Append + $exitCode = $LASTEXITCODE + + if ($exitCode -eq 0) { + Write-Host "āœ… app / nodejs completed (exit code: $exitCode)" + } else { + Write-Host "āš ļø app / nodejs exited with code: $exitCode" + if (Test-Path $logPath) { + Write-Host "Log output (last 20 lines):" + Get-Content -Path $logPath -Tail 20 + } + exit 1 + } - foreach ($config in $testConfigs) { - $testType = $config[0] - $techStack = $config[1] + # $logRoot = Join-Path $env:TEMP "now-tests" + # New-Item -ItemType Directory -Force -Path $logRoot | Out-Null - Write-Host "================================" - Write-Host "Testing: $scriptPath --silent $testType $techStack" - Write-Host "================================" + # foreach ($config in $testConfigs) { + # $testType = $config[0] + # $techStack = $config[1] - $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" + # Write-Host "================================" + # Write-Host "Testing: $scriptPath --silent $testType $techStack" + # Write-Host "================================" - & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append - $exitCode = $LASTEXITCODE + # $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" - if ($exitCode -eq 0) { - Write-Host "āœ… $testType / $techStack completed (exit code: $exitCode)" - } else { - Write-Host "āš ļø $testType / $techStack exited with code: $exitCode" - $overallFailed = $true + # & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append + # $exitCode = $LASTEXITCODE - if (Test-Path $logPath) { - Write-Host "Log output (last 20 lines):" - Get-Content -Path $logPath -Tail 20 - } - } - } + # 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 ($overallFailed) { Write-Error "One or more configurations failed." From a80340e0cffba2ddfd0c96d25cde61a0c39f7981 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 4 Dec 2025 14:42:39 +0530 Subject: [PATCH 18/36] logs for app node --- win/env-setup-run.ps1 | 110 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 7119f47..64bacfb 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -398,11 +398,16 @@ $platformYamlIos function Setup-Mobile-NodeJS { param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - $REPO = "now-webdriverio-appium-app-browserstack" + $REPO = "now-webdriverio-appium-app-browserstack" $TARGET = Join-Path $GLOBAL_DIR $REPO + Log-Section "šŸ› DEBUG: Setup-Mobile-NodeJS (App / NodeJS)" $GLOBAL_LOG + Log-Line "ā„¹ļø Repo name: $REPO" $GLOBAL_LOG + Log-Line "ā„¹ļø Target clone directory: $TARGET" $GLOBAL_LOG + New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null if (Test-Path $TARGET) { + Log-Line "ā„¹ļø Cleaning existing target directory: $TARGET" $GLOBAL_LOG Remove-Item -Path $TARGET -Recurse -Force } @@ -410,37 +415,109 @@ function Setup-Mobile-NodeJS { Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) $testDir = Join-Path $TARGET "test" + Log-Line "ā„¹ļø Test directory (working directory for npm): $testDir" $GLOBAL_LOG + 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) + # ---- Node / npm environment diagnostics ---- + Log-Section "šŸ” NodeJS Environment Diagnostics" $GLOBAL_LOG + + $nodeCmd = Get-Command node -ErrorAction SilentlyContinue + $npmCmd = Get-Command npm -ErrorAction SilentlyContinue + + if ($nodeCmd) { + Log-Line "ā„¹ļø node.exe path: $($nodeCmd.Source)" $GLOBAL_LOG + } else { + Log-Line "āš ļø node.exe not found in PATH" $GLOBAL_LOG + } + + if ($npmCmd) { + Log-Line "ā„¹ļø npm.cmd path: $($npmCmd.Source)" $GLOBAL_LOG + } else { + Log-Line "āš ļø npm not found in PATH" $GLOBAL_LOG + } + + Log-Line "ā„¹ļø Running 'node --version' for debug" $GLOBAL_LOG + [void](Invoke-External -Exe "node" -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) + + Log-Line "ā„¹ļø Running 'npm --version' for debug" $GLOBAL_LOG + [void](Invoke-External -Exe "npm" -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) + + # ---- npm install ---- + Log-Section "šŸ“¦ npm install (mobile / nodejs)" $GLOBAL_LOG + Log-Line "āš™ļø About to run: cmd.exe /c npm install" $GLOBAL_LOG + Log-Line "ā„¹ļø Working directory: $testDir" $GLOBAL_LOG + $npmInstallStart = Get-Date + $npmInstallExit = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir + $npmInstallEnd = Get-Date + $npmInstallDuration = [int]($npmInstallEnd - $npmInstallStart).TotalSeconds + + Log-Line "ā„¹ļø npm install exit code: $npmInstallExit (duration: ${npmInstallDuration}s)" $GLOBAL_LOG + if ($npmInstallExit -ne 0) { + Log-Line "āŒ npm install failed with exit code $npmInstallExit. See $LogFile for details." $GLOBAL_LOG + throw "npm install failed (exit $npmInstallExit)" + } + Log-Line "āœ… Dependencies installed" $GLOBAL_LOG - # Generate capabilities JSON and set as environment variable (like Mac) + # ---- Capabilities / env setup ---- + Log-Section "āš™ļø Generating capabilities & setting environment variables" $GLOBAL_LOG $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" + $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-Section "🧾 Validate Environment Variables (Mobile / NodeJS)" $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 + Log-Line "ā„¹ļø Platforms JSON (caps): $capsJson" $GLOBAL_LOG + + # For additional safety, dump the relevant npm script from package.json + $pkgPath = Join-Path $testDir "package.json" + if (Test-Path $pkgPath) { + try { + $pkgRaw = Get-Content $pkgPath -Raw | ConvertFrom-Json + if ($pkgRaw.scripts.test) { + Log-Line "ā„¹ļø package.json 'test' script: $($pkgRaw.scripts.test)" $GLOBAL_LOG + } else { + Log-Line "āš ļø package.json has no 'test' script defined" $GLOBAL_LOG + } + } catch { + Log-Line "āš ļø Failed to parse package.json for logging: $($_.Exception.Message)" $GLOBAL_LOG + } + } else { + Log-Line "āš ļø package.json not found at $pkgPath" $GLOBAL_LOG + } + # ---- npm run test ---- Print-TestsRunningSection -Command "npm run test" - [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) + Log-Line "ā„¹ļø About to run tests: cmd.exe /c npm run test" $GLOBAL_LOG + Log-Line "ā„¹ļø Working directory for tests: $testDir" $GLOBAL_LOG + + $testStart = Get-Date + $testExit = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir + $testEnd = Get-Date + $testDuration = [int]($testEnd - $testStart).TotalSeconds + + Log-Line "ā„¹ļø npm run test exit code: $testExit (duration: ${testDuration}s)" $GLOBAL_LOG + + if ($testExit -eq 0) { + Log-Line "āœ… npm run test exited cleanly (exit code 0)" $GLOBAL_LOG + } else { + Log-Line "āŒ npm run test exited with non-zero code $testExit. Check $LogFile for details." $GLOBAL_LOG + } + Log-Line "ā„¹ļø Run Test command completed." $GLOBAL_LOG } finally { @@ -449,6 +526,7 @@ function Setup-Mobile-NodeJS { } } + # ===== Helper Functions ===== function Report-BStackLocalStatus { param([bool]$LocalFlag) From cbca0ec9156e21a4c0d5befe92e77102cdd5a231 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 4 Dec 2025 14:47:32 +0530 Subject: [PATCH 19/36] logs for app node --- win/env-setup-run.ps1 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 64bacfb..e12c3b7 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -427,22 +427,20 @@ function Setup-Mobile-NodeJS { if ($nodeCmd) { Log-Line "ā„¹ļø node.exe path: $($nodeCmd.Source)" $GLOBAL_LOG + Log-Line "ā„¹ļø Running 'node --version' for debug" $GLOBAL_LOG + [void](Invoke-External -Exe $nodeCmd.Source -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) } else { Log-Line "āš ļø node.exe not found in PATH" $GLOBAL_LOG } if ($npmCmd) { Log-Line "ā„¹ļø npm.cmd path: $($npmCmd.Source)" $GLOBAL_LOG + Log-Line "ā„¹ļø Running 'npm --version' for debug" $GLOBAL_LOG + [void](Invoke-External -Exe $npmCmd.Source -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) } else { Log-Line "āš ļø npm not found in PATH" $GLOBAL_LOG } - Log-Line "ā„¹ļø Running 'node --version' for debug" $GLOBAL_LOG - [void](Invoke-External -Exe "node" -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) - - Log-Line "ā„¹ļø Running 'npm --version' for debug" $GLOBAL_LOG - [void](Invoke-External -Exe "npm" -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) - # ---- npm install ---- Log-Section "šŸ“¦ npm install (mobile / nodejs)" $GLOBAL_LOG Log-Line "āš™ļø About to run: cmd.exe /c npm install" $GLOBAL_LOG From 4f57d4629694822ce7c13172fb5339f4b6ecc1fd Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 4 Dec 2025 15:04:51 +0530 Subject: [PATCH 20/36] timeout changes for appnode --- win/common-utils.ps1 | 47 ++++++++++++++++++++++++++++++++++--------- win/env-setup-run.ps1 | 15 ++++++++++++-- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 105bc69..c4ea4a9 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -124,8 +124,10 @@ function Invoke-External { [Parameter(Mandatory)][string]$Exe, [Parameter()][string[]]$Arguments = @(), [string]$LogFile, - [string]$WorkingDirectory + [string]$WorkingDirectory, + [int]$TimeoutSeconds = 0 # 0 = no timeout (current behaviour) ) + $psi = New-Object System.Diagnostics.ProcessStartInfo $exeToRun = $Exe $argLine = ($Arguments | ForEach-Object { if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } }) -join ' ' @@ -142,8 +144,9 @@ function Invoke-External { $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) { $psi.WorkingDirectory = (Get-Location).Path } else { @@ -155,7 +158,9 @@ function Invoke-External { if ($LogFile) { $logDir = Split-Path -Parent $LogFile - if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } + if ($logDir -and !(Test-Path $logDir)) { + New-Item -ItemType Directory -Path $logDir | Out-Null + } $stdoutAction = { if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { @@ -169,12 +174,24 @@ function Invoke-External { } $stdoutEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $stdoutAction -MessageData $LogFile - $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile + $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile [void]$p.Start() $p.BeginOutputReadLine() $p.BeginErrorReadLine() - $p.WaitForExit() + + if ($TimeoutSeconds -gt 0) { + $waitMs = $TimeoutSeconds * 1000 + if (-not $p.WaitForExit($waitMs)) { + try { + Log-Line "āš ļø Process '$Exe $argLine' exceeded timeout of $TimeoutSeconds seconds. Killing it..." $GLOBAL_LOG + } catch {} + try { $p.Kill() } catch {} + $p.WaitForExit() + } + } else { + $p.WaitForExit() + } Unregister-Event -SourceIdentifier $stdoutEvent.Name Unregister-Event -SourceIdentifier $stderrEvent.Name @@ -182,14 +199,26 @@ function Invoke-External { Remove-Job -Id $stderrEvent.Id -Force } else { [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() + + if ($TimeoutSeconds -gt 0) { + $waitMs = $TimeoutSeconds * 1000 + if (-not $p.WaitForExit($waitMs)) { + try { $p.Kill() } catch {} + $p.WaitForExit() + } + $stdout = $p.StandardOutput.ReadToEnd() + $stderr = $p.StandardError.ReadToEnd() + } else { + $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 diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index e12c3b7..ea77729 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -500,8 +500,19 @@ function Setup-Mobile-NodeJS { # ---- npm run test ---- Print-TestsRunningSection -Command "npm run test" - Log-Line "ā„¹ļø About to run tests: cmd.exe /c npm run test" $GLOBAL_LOG - Log-Line "ā„¹ļø Working directory for tests: $testDir" $GLOBAL_LOG + + $testTimeout = 300 # 15 minutes hard cap for CI + Log-Line "ā„¹ļø Starting 'npm run test' with timeout of $testTimeout seconds..." $GLOBAL_LOG + + $exit = Invoke-External ` + -Exe "cmd.exe" ` + -Arguments @("/c","npm","run","test") ` + -LogFile $LogFile ` + -WorkingDirectory $testDir ` + -TimeoutSeconds $testTimeout + + Log-Line "ā„¹ļø 'npm run test' finished with exit code: $exit" $GLOBAL_LOG + Log-Line "ā„¹ļø Run Test command completed." $GLOBAL_LOG $testStart = Get-Date $testExit = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir From 2d635fb8d6aff6b4f87290540e59a8c39cf7ea44 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 20:46:06 +0530 Subject: [PATCH 21/36] win changes for 5 parallel in silent mode --- win/common-utils.ps1 | 47 +++----------- win/env-setup-run.ps1 | 143 ++++++++++-------------------------------- win/run.ps1 | 2 +- 3 files changed, 42 insertions(+), 150 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index c4ea4a9..105bc69 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -124,10 +124,8 @@ function Invoke-External { [Parameter(Mandatory)][string]$Exe, [Parameter()][string[]]$Arguments = @(), [string]$LogFile, - [string]$WorkingDirectory, - [int]$TimeoutSeconds = 0 # 0 = no timeout (current behaviour) + [string]$WorkingDirectory ) - $psi = New-Object System.Diagnostics.ProcessStartInfo $exeToRun = $Exe $argLine = ($Arguments | ForEach-Object { if ($_ -match '\s') { '"{0}"' -f $_ } else { $_ } }) -join ' ' @@ -144,9 +142,8 @@ function Invoke-External { $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true - + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) { $psi.WorkingDirectory = (Get-Location).Path } else { @@ -158,9 +155,7 @@ function Invoke-External { if ($LogFile) { $logDir = Split-Path -Parent $LogFile - if ($logDir -and !(Test-Path $logDir)) { - New-Item -ItemType Directory -Path $logDir | Out-Null - } + if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } $stdoutAction = { if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { @@ -174,24 +169,12 @@ function Invoke-External { } $stdoutEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $stdoutAction -MessageData $LogFile - $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile + $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile [void]$p.Start() $p.BeginOutputReadLine() $p.BeginErrorReadLine() - - if ($TimeoutSeconds -gt 0) { - $waitMs = $TimeoutSeconds * 1000 - if (-not $p.WaitForExit($waitMs)) { - try { - Log-Line "āš ļø Process '$Exe $argLine' exceeded timeout of $TimeoutSeconds seconds. Killing it..." $GLOBAL_LOG - } catch {} - try { $p.Kill() } catch {} - $p.WaitForExit() - } - } else { - $p.WaitForExit() - } + $p.WaitForExit() Unregister-Event -SourceIdentifier $stdoutEvent.Name Unregister-Event -SourceIdentifier $stderrEvent.Name @@ -199,26 +182,14 @@ function Invoke-External { Remove-Job -Id $stderrEvent.Id -Force } else { [void]$p.Start() - - if ($TimeoutSeconds -gt 0) { - $waitMs = $TimeoutSeconds * 1000 - if (-not $p.WaitForExit($waitMs)) { - try { $p.Kill() } catch {} - $p.WaitForExit() - } - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - } else { - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() - } + $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 diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index ea77729..982bd1b 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -27,7 +27,7 @@ function Setup-Web-Java { Report-BStackLocalStatus -LocalFlag $UseLocal Log-Line "🧩 Generating YAML config (browserstack.yml)" $GLOBAL_LOG - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB + $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $localFlag = if ($UseLocal) { "true" } else { "false" } $yamlContent = @" @@ -112,7 +112,7 @@ function Setup-Web-Python { Report-BStackLocalStatus -LocalFlag $UseLocal $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" - $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB + $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $localFlag = if ($UseLocal) { "true" } else { "false" } @" @@ -239,7 +239,7 @@ function Setup-Mobile-Java { $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY $env:BROWSERSTACK_CONFIG_FILE = ".\browserstack.yml" - $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $localFlag = if ($UseLocal) { "true" } else { "false" } # Write complete browserstack.yml (not just append) @@ -327,7 +327,7 @@ function Setup-Mobile-Python { # Generate platform YAMLs $script:APP_PLATFORM = "android" - $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $platformYamlAndroid = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $androidYmlPath = Join-Path $TARGET "android\browserstack.yml" @" userName: $BROWSERSTACK_USERNAME @@ -343,7 +343,7 @@ $platformYamlAndroid "@ | Set-Content $androidYmlPath $script:APP_PLATFORM = "ios" - $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $platformYamlIos = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $iosYmlPath = Join-Path $TARGET "ios\browserstack.yml" @" userName: $BROWSERSTACK_USERNAME @@ -398,16 +398,11 @@ $platformYamlIos function Setup-Mobile-NodeJS { param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - $REPO = "now-webdriverio-appium-app-browserstack" + $REPO = "now-webdriverio-appium-app-browserstack" $TARGET = Join-Path $GLOBAL_DIR $REPO - Log-Section "šŸ› DEBUG: Setup-Mobile-NodeJS (App / NodeJS)" $GLOBAL_LOG - Log-Line "ā„¹ļø Repo name: $REPO" $GLOBAL_LOG - Log-Line "ā„¹ļø Target clone directory: $TARGET" $GLOBAL_LOG - New-Item -ItemType Directory -Path $GLOBAL_DIR -Force | Out-Null if (Test-Path $TARGET) { - Log-Line "ā„¹ļø Cleaning existing target directory: $TARGET" $GLOBAL_LOG Remove-Item -Path $TARGET -Recurse -Force } @@ -415,118 +410,37 @@ function Setup-Mobile-NodeJS { Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile (Get-RunLogFile) $testDir = Join-Path $TARGET "test" - Log-Line "ā„¹ļø Test directory (working directory for npm): $testDir" $GLOBAL_LOG - Push-Location $testDir try { - # ---- Node / npm environment diagnostics ---- - Log-Section "šŸ” NodeJS Environment Diagnostics" $GLOBAL_LOG - - $nodeCmd = Get-Command node -ErrorAction SilentlyContinue - $npmCmd = Get-Command npm -ErrorAction SilentlyContinue - - if ($nodeCmd) { - Log-Line "ā„¹ļø node.exe path: $($nodeCmd.Source)" $GLOBAL_LOG - Log-Line "ā„¹ļø Running 'node --version' for debug" $GLOBAL_LOG - [void](Invoke-External -Exe $nodeCmd.Source -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) - } else { - Log-Line "āš ļø node.exe not found in PATH" $GLOBAL_LOG - } - - if ($npmCmd) { - Log-Line "ā„¹ļø npm.cmd path: $($npmCmd.Source)" $GLOBAL_LOG - Log-Line "ā„¹ļø Running 'npm --version' for debug" $GLOBAL_LOG - [void](Invoke-External -Exe $npmCmd.Source -Arguments @("--version") -LogFile $LogFile -WorkingDirectory $testDir) - } else { - Log-Line "āš ļø npm not found in PATH" $GLOBAL_LOG - } - - # ---- npm install ---- - Log-Section "šŸ“¦ npm install (mobile / nodejs)" $GLOBAL_LOG - Log-Line "āš™ļø About to run: cmd.exe /c npm install" $GLOBAL_LOG - Log-Line "ā„¹ļø Working directory: $testDir" $GLOBAL_LOG - $npmInstallStart = Get-Date - $npmInstallExit = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","install") -LogFile $LogFile -WorkingDirectory $testDir - $npmInstallEnd = Get-Date - $npmInstallDuration = [int]($npmInstallEnd - $npmInstallStart).TotalSeconds - - Log-Line "ā„¹ļø npm install exit code: $npmInstallExit (duration: ${npmInstallDuration}s)" $GLOBAL_LOG - if ($npmInstallExit -ne 0) { - Log-Line "āŒ npm install failed with exit code $npmInstallExit. See $LogFile for details." $GLOBAL_LOG - throw "npm install failed (exit $npmInstallExit)" - } - + 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 - # ---- Capabilities / env setup ---- - Log-Section "āš™ļø Generating capabilities & setting environment variables" $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" + $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 (Mobile / NodeJS)" $GLOBAL_LOG + 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 JSON (caps): $capsJson" $GLOBAL_LOG - - # For additional safety, dump the relevant npm script from package.json - $pkgPath = Join-Path $testDir "package.json" - if (Test-Path $pkgPath) { - try { - $pkgRaw = Get-Content $pkgPath -Raw | ConvertFrom-Json - if ($pkgRaw.scripts.test) { - Log-Line "ā„¹ļø package.json 'test' script: $($pkgRaw.scripts.test)" $GLOBAL_LOG - } else { - Log-Line "āš ļø package.json has no 'test' script defined" $GLOBAL_LOG - } - } catch { - Log-Line "āš ļø Failed to parse package.json for logging: $($_.Exception.Message)" $GLOBAL_LOG - } - } else { - Log-Line "āš ļø package.json not found at $pkgPath" $GLOBAL_LOG - } + Log-Line "ā„¹ļø Platforms: $capsJson" $GLOBAL_LOG - # ---- npm run test ---- Print-TestsRunningSection -Command "npm run test" - - $testTimeout = 300 # 15 minutes hard cap for CI - Log-Line "ā„¹ļø Starting 'npm run test' with timeout of $testTimeout seconds..." $GLOBAL_LOG - - $exit = Invoke-External ` - -Exe "cmd.exe" ` - -Arguments @("/c","npm","run","test") ` - -LogFile $LogFile ` - -WorkingDirectory $testDir ` - -TimeoutSeconds $testTimeout - - Log-Line "ā„¹ļø 'npm run test' finished with exit code: $exit" $GLOBAL_LOG - Log-Line "ā„¹ļø Run Test command completed." $GLOBAL_LOG - - $testStart = Get-Date - $testExit = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir - $testEnd = Get-Date - $testDuration = [int]($testEnd - $testStart).TotalSeconds - - Log-Line "ā„¹ļø npm run test exit code: $testExit (duration: ${testDuration}s)" $GLOBAL_LOG - - if ($testExit -eq 0) { - Log-Line "āœ… npm run test exited cleanly (exit code 0)" $GLOBAL_LOG - } else { - Log-Line "āŒ npm run test exited with non-zero code $testExit. Check $LogFile for details." $GLOBAL_LOG - } - + [void](Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir) Log-Line "ā„¹ļø Run Test command completed." $GLOBAL_LOG } finally { @@ -535,7 +449,6 @@ function Setup-Mobile-NodeJS { } } - # ===== Helper Functions ===== function Report-BStackLocalStatus { param([bool]$LocalFlag) @@ -597,7 +510,8 @@ function Identify-RunStatus-NodeJS { function Setup-Environment { param( [Parameter(Mandatory)][string]$SetupType, - [Parameter(Mandatory)][string]$TechStack + [Parameter(Mandatory)][string]$TechStack, + [string]$RunMode = "--interactive" ) Log-Section "šŸ“¦ Project Setup" $GLOBAL_LOG @@ -609,6 +523,12 @@ function Setup-Environment { $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 @@ -660,9 +580,10 @@ function Setup-Environment { function Run-Setup { param( [string]$TestType, - [string]$TechStack + [string]$TechStack, + [string]$RunMode = "--interactive" ) - Setup-Environment -SetupType $TestType -TechStack $TechStack + Setup-Environment -SetupType $TestType -TechStack $TechStack -RunMode $RunMode } diff --git a/win/run.ps1 b/win/run.ps1 index f547f33..8003d2b 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -101,7 +101,7 @@ try { Log-Line "ā„¹ļø Starting $TEST_TYPE setup for $TECH_STACK" $GLOBAL_LOG # Run the setup - Run-Setup -TestType $TEST_TYPE -TechStack $TECH_STACK + Run-Setup -TestType $TEST_TYPE -TechStack $TECH_STACK -RunMode $RunMode } catch { Log-Line " " $GLOBAL_LOG From 911dd8a177e5d4bb5275d2ff4b22f10436e8fc21 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 20:46:35 +0530 Subject: [PATCH 22/36] mac changes in win-changes branch done by samiran --- mac/common-utils.sh | 19 ++++++++++++++----- mac/env-setup-run.sh | 2 +- mac/user-interaction.sh | 4 ---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/mac/common-utils.sh b/mac/common-utils.sh index 359c0eb..e41f4f8 100644 --- a/mac/common-utils.sh +++ b/mac/common-utils.sh @@ -231,7 +231,7 @@ fetch_plan_details() { local web_unauthorized=false local mobile_unauthorized=false - if [[ "$test_type" == "web" || "$test_type" == "both" ]]; then + if [[ "$test_type" == "web" ]]; then RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api.browserstack.com/automate/plan.json) HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1) RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d') @@ -246,7 +246,7 @@ fetch_plan_details() { fi fi - if [[ "$test_type" == "app" || "$test_type" == "both" ]]; then + if [[ "$test_type" == "app" ]]; then RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json) HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1) RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d') @@ -264,11 +264,21 @@ fetch_plan_details() { log_info "Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)" if [[ "$test_type" == "web" && "$web_unauthorized" == true ]] || \ - [[ "$test_type" == "app" && "$mobile_unauthorized" == true ]] || \ - [[ "$test_type" == "both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then + [[ "$test_type" == "app" && "$mobile_unauthorized" == true ]]; then log_msg_to "āŒ Unauthorized to fetch required plan(s). Exiting." exit 1 fi + + if [[ "$RUN_MODE" == *"--silent"* ]]; then + if [[ "$test_type" == "web" ]]; then + TEAM_PARALLELS_MAX_ALLOWED_WEB=5 + export TEAM_PARALLELS_MAX_ALLOWED_WEB=5 + else + 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)" + fi } # Function to check if IP is private @@ -447,4 +457,3 @@ detect_os() { export NOW_OS=$response } - diff --git a/mac/env-setup-run.sh b/mac/env-setup-run.sh index 3ad7c5d..398ce24 100644 --- a/mac/env-setup-run.sh +++ b/mac/env-setup-run.sh @@ -545,4 +545,4 @@ detect_setup_python_env() { source .venv/bin/activate fi log_success "Virtual environment created and activated." -} +} \ No newline at end of file diff --git a/mac/user-interaction.sh b/mac/user-interaction.sh index 24b9c24..d671e50 100644 --- a/mac/user-interaction.sh +++ b/mac/user-interaction.sh @@ -137,10 +137,6 @@ perform_next_steps_based_on_test_type() { "app") get_upload_app ;; - "both") - get_test_url - get_upload_app - ;; esac } From 90f18b886551cdb3e5562877ec5e6b0226444d9d Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 20:46:54 +0530 Subject: [PATCH 23/36] updated workflow --- .github/workflows/test-scripts.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 91b19c6..70792ae 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -281,6 +281,11 @@ jobs: $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" $testConfigs = @( + @("web", "java"), + @("app", "java"), + @("web", "python"), + @("app", "python"), + @("web", "nodejs"), @("app", "nodejs") ) @@ -527,15 +532,17 @@ jobs: test-summary: name: Test Summary runs-on: ubuntu-latest - needs: [test-windows] + needs: [test-mac, test-linux, test-windows] if: always() steps: - name: Check test results run: | echo "=== Test Results Summary ===" + echo "macOS Tests: ${{ needs.test-mac.result }}" + echo "Linux Tests: ${{ needs.test-linux.result }}" echo "Windows Tests: ${{ needs.test-windows.result }}" - if [ "${{ needs.test-windows.result }}" = "failure" ]; then + if [ "${{ needs.test-mac.result }}" = "failure" ] || [ "${{ needs.test-linux.result }}" = "failure" ] || [ "${{ needs.test-windows.result }}" = "failure" ]; then echo "āŒ Some tests failed" exit 1 fi @@ -547,4 +554,3 @@ jobs: echo "āœ… All script validations passed successfully!" echo "- mac/run.sh and supporting scripts validated on macOS and Linux" echo "- win/run.ps1 and supporting scripts validated on Windows" - echo "- win/run.ps1 and supporting scripts validated on Windows" From 87972e6efa518617d5c18085c0903aadc00f515f Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 20:49:56 +0530 Subject: [PATCH 24/36] undo if:false for linux & mac --- .github/workflows/test-scripts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 70792ae..504607c 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 @@ -380,7 +380,7 @@ jobs: test-linux: name: Test mac/run.sh on Linux - if: false + # if: false runs-on: ubuntu-latest timeout-minutes: 15 environment: BrowserStack From d380434589d0268e6f5645a5a83985ba5ec16f78 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 22:29:16 +0530 Subject: [PATCH 25/36] windows GHA run all --- .github/workflows/test-scripts.yml | 62 ++++++++++-------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 504607c..5ddc157 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -292,54 +292,32 @@ jobs: $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" - Write-Host "================================" - Write-Host "Testing: $scriptPath --silent app nodejs" - Write-Host "================================" + foreach ($config in $testConfigs) { + $testType = $config[0] + $techStack = $config[1] - & $scriptPath --silent app nodejs 2>&1 | Tee-Object -FilePath $logPath -Append - $exitCode = $LASTEXITCODE + Write-Host "================================" + Write-Host "Testing: $scriptPath --silent $testType $techStack" + Write-Host "================================" - if ($exitCode -eq 0) { - Write-Host "āœ… app / nodejs completed (exit code: $exitCode)" - } else { - Write-Host "āš ļø app / nodejs exited with code: $exitCode" - if (Test-Path $logPath) { - Write-Host "Log output (last 20 lines):" - Get-Content -Path $logPath -Tail 20 - } - exit 1 - } - - # $logRoot = Join-Path $env:TEMP "now-tests" - # New-Item -ItemType Directory -Force -Path $logRoot | Out-Null + $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" - # foreach ($config in $testConfigs) { - # $testType = $config[0] - # $techStack = $config[1] + & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append + $exitCode = $LASTEXITCODE - # Write-Host "================================" - # Write-Host "Testing: $scriptPath --silent $testType $techStack" - # Write-Host "================================" + if ($exitCode -eq 0) { + Write-Host "āœ… $testType / $techStack completed (exit code: $exitCode)" + } else { + Write-Host "āš ļø $testType / $techStack exited with code: $exitCode" + $overallFailed = $true - # $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" - - # & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append - # $exitCode = $LASTEXITCODE - - # 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 ($overallFailed) { Write-Error "One or more configurations failed." From c1a5acc2e8694c7e0ef0c191aee418eb800738f0 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Fri, 5 Dec 2025 22:59:52 +0530 Subject: [PATCH 26/36] windows build name changes --- win/common-utils.ps1 | 2 ++ win/env-setup-run.ps1 | 36 ++++++++++++++++++------------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 105bc69..2a97751 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -3,6 +3,8 @@ # ===== Global Variables ===== $script:WORKSPACE_DIR = Join-Path $env:USERPROFILE ".browserstack" $script:PROJECT_FOLDER = "NOW" +$script:NOW_OS = "windows" + $script:GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER $script:LOG_DIR = Join-Path $GLOBAL_DIR "logs" diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 982bd1b..bc99213 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -35,8 +35,8 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: testng browserstackLocal: $localFlag -buildName: now-windows-web-java-testng -projectName: NOW-Web-Test +buildName: now-$NOW_OS-web-java-testng +projectName: now-$NOW_OS-web percy: true accessibility: true platforms: @@ -50,7 +50,7 @@ parallelsPerPlatform: $ParallelsPerPlatform # 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 "ā„¹ļø BrowserStack Build: now-$NOW_OS-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 @@ -120,8 +120,8 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: pytest browserstackLocal: $localFlag -buildName: now-windows-web-python-pytest -projectName: NOW-Web-Test +buildName: now-$NOW_OS-web-python-pytest +projectName: now-$NOW_OS-web percy: true accessibility: true platforms: @@ -134,7 +134,7 @@ parallelsPerPlatform: $ParallelsPerPlatform # 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 "ā„¹ļø BrowserStack Build: now-$NOW_OS-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 @@ -188,8 +188,8 @@ function Setup-Web-NodeJS { $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" + $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-web-nodejs-wdio" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-web" # Validate Environment Variables Log-Section "Validate Environment Variables" $GLOBAL_LOG @@ -248,8 +248,8 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: testng browserstackLocal: $localFlag -buildName: now-windows-app-java-testng -projectName: NOW-Mobile-Test +buildName: now-$NOW_OS-app-java-testng +projectName: now-$NOW_OS-app parallelsPerPlatform: $ParallelsPerPlatform app: $APP_URL platforms: @@ -262,7 +262,7 @@ $platforms # 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 "ā„¹ļø BrowserStack Build: now-$NOW_OS-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 @@ -334,8 +334,8 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: pytest browserstackLocal: $localFlag -buildName: now-windows-app-python-pytest -projectName: NOW-Mobile-Test +buildName: now-$NOW_OS-app-python-pytest +projectName: now-$NOW_OS-app parallelsPerPlatform: $ParallelsPerPlatform app: $APP_URL platforms: @@ -350,8 +350,8 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: pytest browserstackLocal: $localFlag -buildName: now-windows-app-python-pytest -projectName: NOW-Mobile-Test +buildName: now-$NOW_OS-app-python-pytest +projectName: now-$NOW_OS-app parallelsPerPlatform: $ParallelsPerPlatform app: $APP_URL platforms: @@ -370,7 +370,7 @@ $platformYamlIos # 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 "ā„¹ļø BrowserStack Build: now-$NOW_OS-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 @@ -425,8 +425,8 @@ function Setup-Mobile-NodeJS { $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_BUILD_NAME = "now-$NOW_OS-app-nodejs-wdio" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-app" $env:BROWSERSTACK_LOCAL = "true" # Validate Environment Variables From 69435f739e4a2d04216ea2689a95f0450bce8cfa Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 03:54:11 +0530 Subject: [PATCH 27/36] dynamic allocation, only edit platforms, bug fixes --- win/common-utils.ps1 | 45 +++- win/device-machine-allocation.ps1 | 387 +++++++++++------------------- win/env-setup-run.ps1 | 122 ++++------ 3 files changed, 232 insertions(+), 322 deletions(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 2a97751..b5de6a0 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -5,7 +5,6 @@ $script:WORKSPACE_DIR = Join-Path $env:USERPROFILE ".browserstack" $script:PROJECT_FOLDER = "NOW" $script:NOW_OS = "windows" - $script:GLOBAL_DIR = Join-Path $WORKSPACE_DIR $PROJECT_FOLDER $script:LOG_DIR = Join-Path $GLOBAL_DIR "logs" $script:GLOBAL_LOG = "" @@ -112,6 +111,50 @@ function Invoke-GitClone { } } +function Reset-BrowserStackConfigFile { + param( + [Parameter(Mandatory)][string]$RepoRoot, + [Parameter(Mandatory)][string]$RelativePath + ) + $gitDir = Join-Path $RepoRoot ".git" + if (!(Test-Path $gitDir)) { return } + $normalized = $RelativePath -replace '\\','/' + try { + [void](Invoke-External -Exe "git" -Arguments @("checkout","--",$normalized) -LogFile $GLOBAL_LOG -WorkingDirectory $RepoRoot) + } catch { + Log-Line "āš ļø Unable to reset $RelativePath via git checkout ($($_.Exception.Message))" $GLOBAL_LOG + } +} + +function Set-BrowserStackPlatformsSection { + param( + [Parameter(Mandatory)][string]$RepoRoot, + [Parameter(Mandatory)][string]$RelativeConfigPath, + [Parameter(Mandatory)][string]$PlatformsYaml + ) + + $configPath = Join-Path $RepoRoot $RelativeConfigPath + if (!(Test-Path $configPath)) { + throw "browserstack config not found: $configPath" + } + + Reset-BrowserStackConfigFile -RepoRoot $RepoRoot -RelativePath $RelativeConfigPath + + $normalizedPlatforms = ($PlatformsYaml -replace "`r","").TrimEnd("`n") + $blockBuilder = New-Object System.Text.StringBuilder + [void]$blockBuilder.AppendLine("") + [void]$blockBuilder.AppendLine("platforms:") + if (-not [string]::IsNullOrWhiteSpace($normalizedPlatforms)) { + foreach ($line in ($normalizedPlatforms -split "`n")) { + [void]$blockBuilder.AppendLine($line) + } + } + + $appendText = ($blockBuilder.ToString() -replace "`n","`r`n") + Add-Content -Path $configPath -Value $appendText + Log-Line "āœ… Updated platforms in $RelativeConfigPath" $GLOBAL_LOG +} + function Set-ContentNoBom { param( [Parameter(Mandatory)][string]$Path, diff --git a/win/device-machine-allocation.ps1 b/win/device-machine-allocation.ps1 index 35c7a08..a4068fa 100644 --- a/win/device-machine-allocation.ps1 +++ b/win/device-machine-allocation.ps1 @@ -1,213 +1,161 @@ # 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" +# ===== Pattern lists (mirrors mac/device-machine-allocation.sh) ===== +$WEB_PLATFORM_PATTERNS = @( + "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", + "ios|iPhone 1[234567]*", + "android|Samsung Galaxy S*", + "ios|iPad Air*", + "android|Samsung Galaxy Tab*", + "android|Samsung Galaxy M*", + "android|Google Pixel [56789]*", + "android|Vivo Y*", + "android|Oppo*", + "ios|iPhone SE*", + "ios|iPad Pro*", + "android|Samsung Galaxy A*", + "android|Google Pixel 10*", + "android|OnePlus *", + "android|Vivo V*", + "android|Xiaomi *", + "android|Huawei *" ) -# 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_PLATFORM_PATTERNS = @( + "ios|iPhone 1[234567]*", + "android|Samsung Galaxy S*", + "ios|iPad Air*", + "android|Samsung Galaxy Tab*", + "android|Samsung Galaxy M*", + "android|Google Pixel [56789]*", + "android|Vivo Y*", + "android|Oppo*", + "ios|iPad Pro*", + "android|Samsung Galaxy A*", + "android|Google Pixel 10*", + "android|OnePlus *", + "android|Vivo V*", + "android|Xiaomi *", + "android|Huawei *" ) -# MOBILE_ALL combines the tiers -$MOBILE_ALL = @() -$MOBILE_ALL += $MOBILE_TIER1 -$MOBILE_ALL += $MOBILE_TIER2 -$MOBILE_ALL += $MOBILE_TIER3 -$MOBILE_ALL += $MOBILE_TIER4 +function Get-BrowserVersionTag { + param([int]$Position) + if ($Position -le 0) { return "latest" } + $mod = $Position % 4 + if ($mod -eq 0) { return "latest" } + return "latest-$mod" +} -# ===== 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 +function Build-YamlFromPatterns { + param( + [string[]]$SourceList, + [int]$Count, + [string]$FilterPrefix + ) + if (-not $SourceList -or $SourceList.Count -eq 0 -or $Count -le 0) { return "" } - 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() - } + $filter = if ([string]::IsNullOrWhiteSpace($FilterPrefix) -or $FilterPrefix -eq "all") { $null } else { $FilterPrefix.ToLowerInvariant() } + $sb = New-Object System.Text.StringBuilder + $added = 0 + $index = 0 + + while ($added -lt $Count) { + $entry = $SourceList[$index % $SourceList.Count] + $index++ + if ([string]::IsNullOrWhiteSpace($entry)) { continue } + + $parts = $entry.Split('|',2) + $prefix = $parts[0].Trim() + $suffix = if ($parts.Length -gt 1) { $parts[1].Trim() } else { "" } + + if ($filter -and $prefix.ToLowerInvariant() -ne $filter) { continue } + + $added++ + if ($prefix -in @("ios","android")) { + [void]$sb.AppendLine(" - platformName: $prefix") + [void]$sb.AppendLine(" deviceName: $suffix") + } else { + $browserVersion = Get-BrowserVersionTag $added + [void]$sb.AppendLine(" - os: $prefix") + [void]$sb.AppendLine(" browserName: $suffix") + [void]$sb.AppendLine(" browserVersion: $browserVersion") } } return $sb.ToString() } -function Generate-Mobile-Platforms-Yaml { +function Build-JsonFromPatterns { + param( + [string[]]$SourceList, + [int]$Count, + [string]$FilterPrefix + ) + if (-not $SourceList -or $SourceList.Count -eq 0 -or $Count -le 0) { return "[]" } + + $filter = if ([string]::IsNullOrWhiteSpace($FilterPrefix) -or $FilterPrefix -eq "all") { $null } else { $FilterPrefix.ToLowerInvariant() } + $items = New-Object System.Collections.Generic.List[object] + $added = 0 + $index = 0 + + while ($added -lt $Count) { + $entry = $SourceList[$index % $SourceList.Count] + $index++ + if ([string]::IsNullOrWhiteSpace($entry)) { continue } + + $parts = $entry.Split('|',2) + $prefix = $parts[0].Trim() + $suffix = if ($parts.Length -gt 1) { $parts[1].Trim() } else { "" } + + if ($filter -and $prefix.ToLowerInvariant() -ne $filter) { continue } + + $added++ + if ($prefix -in @("ios","android")) { + $items.Add([pscustomobject]@{ + "bstack:options" = @{ + platformName = $prefix + deviceName = $suffix + } + }) + } else { + $browserVersion = Get-BrowserVersionTag $added + $items.Add([pscustomobject]@{ + browserName = $suffix + browserVersion = $browserVersion + 'bstack:options' = @{ + os = $prefix + } + }) + } + } + return ($items | ConvertTo-Json -Depth 5 -Compress) +} + +# ===== Generators ===== +function Generate-Web-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 } - } + return (Build-YamlFromPatterns -SourceList $WEB_PLATFORM_PATTERNS -Count $max -FilterPrefix $null) +} - [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-Platforms-Yaml { + param([int]$MaxTotalParallels) + $max = [Math]::Floor($MaxTotalParallels * $PARALLEL_PERCENTAGE) + if ($max -lt 1) { $max = 1 } + $filter = if ([string]::IsNullOrWhiteSpace($APP_PLATFORM) -or $APP_PLATFORM -eq "all") { $null } else { $APP_PLATFORM.ToLowerInvariant() } + return (Build-YamlFromPatterns -SourceList $MOBILE_PLATFORM_PATTERNS -Count $max -FilterPrefix $filter) } function Generate-Mobile-Caps-Json { @@ -221,64 +169,15 @@ 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 + $filter = if ([string]::IsNullOrWhiteSpace($APP_PLATFORM) -or $APP_PLATFORM -eq "all") { $null } else { $APP_PLATFORM.ToLowerInvariant() } + return (Build-JsonFromPatterns -SourceList $MOBILE_PLATFORM_PATTERNS -Count $max -FilterPrefix $filter) } 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 + return (Build-JsonFromPatterns -SourceList $WEB_PLATFORM_PATTERNS -Count $max -FilterPrefix $null) } diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index bc99213..0712855 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -25,27 +25,19 @@ function Setup-Web-Java { } Report-BStackLocalStatus -LocalFlag $UseLocal - + $UseLocal = $true # BROWSERSTACK_LOCAL ISSUE 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-$NOW_OS-web-java-testng -projectName: now-$NOW_OS-web -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ - - Set-Content "browserstack.yml" -Value $yamlContent - Log-Line "āœ… Created browserstack.yml in root directory" $GLOBAL_LOG + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + $env:BROWSERSTACK_LOCAL = $localFlag + $env:BSTACK_PLATFORMS = $platforms + $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-web-java-testng" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-web" # Validate Environment Variables Log-Section "Validate Environment Variables" $GLOBAL_LOG @@ -90,6 +82,7 @@ function Setup-Web-Python { Push-Location $TARGET try { + $UseLocal = $true if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } $venv = Join-Path $TARGET "venv" if (!(Test-Path $venv)) { @@ -115,21 +108,12 @@ function Setup-Web-Python { $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-$NOW_OS-web-python-pytest -projectName: now-$NOW_OS-web -percy: true -accessibility: true -platforms: -$platforms -parallelsPerPlatform: $ParallelsPerPlatform -"@ | Set-Content "browserstack.yml" - - Log-Line "āœ… Updated browserstack.yml with platforms and credentials" $GLOBAL_LOG + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + $env:BSTACK_PLATFORMS = $platforms + $env:BROWSERSTACK_LOCAL = $localFlag + $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-web-python-pytest" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-web" # Validate Environment Variables Log-Section "Validate Environment Variables" $GLOBAL_LOG @@ -229,6 +213,16 @@ function Setup-Mobile-Java { Push-Location $TARGET try { + $UseLocal = $true + $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform + $localFlag = "true" + $configRelativePath = if ($APP_PLATFORM -eq "all" -or $APP_PLATFORM -eq "android") { + "android\testng-examples\browserstack.yml" + } else { + "ios\testng-examples\browserstack.yml" + } + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath $configRelativePath -PlatformsYaml $platforms + if ($APP_PLATFORM -eq "all" -or $APP_PLATFORM -eq "android") { Set-Location "android\testng-examples" } else { @@ -237,25 +231,12 @@ function Setup-Mobile-Java { $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + $env:BROWSERSTACK_APP = $APP_URL + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + $env:BROWSERSTACK_LOCAL = $localFlag $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-$NOW_OS-app-java-testng -projectName: now-$NOW_OS-app -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platforms -"@ - $yamlContent | Set-Content -Path $env:BROWSERSTACK_CONFIG_FILE -Encoding UTF8 + $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-app-java-testng" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-app" Report-BStackLocalStatus -LocalFlag $UseLocal @@ -323,40 +304,16 @@ function Setup-Mobile-Python { $env:BROWSERSTACK_APP = $APP_URL $originalPlatform = $APP_PLATFORM - $localFlag = if ($UseLocal) { "true" } else { "false" } + $localFlag = "true" # 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-$NOW_OS-app-python-pytest -projectName: now-$NOW_OS-app -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlAndroid -"@ | Set-Content $androidYmlPath + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "android\browserstack.yml" -PlatformsYaml $platformYamlAndroid $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-$NOW_OS-app-python-pytest -projectName: now-$NOW_OS-app -parallelsPerPlatform: $ParallelsPerPlatform -app: $APP_URL -platforms: -$platformYamlIos -"@ | Set-Content $iosYmlPath + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "ios\browserstack.yml" -PlatformsYaml $platformYamlIos $script:APP_PLATFORM = $originalPlatform Log-Line "āœ… Wrote platform YAMLs" $GLOBAL_LOG @@ -367,6 +324,14 @@ $platformYamlIos Report-BStackLocalStatus -LocalFlag $UseLocal + $env:BSTACK_PARALLELS = $ParallelsPerPlatform + $env:BROWSERSTACK_APP = $APP_URL + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + $env:BROWSERSTACK_LOCAL = $localFlag + $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-app-python-pytest" + $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-app" + # Validate Environment Variables Log-Section "Validate Environment Variables" $GLOBAL_LOG Log-Line "ā„¹ļø BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG @@ -412,6 +377,7 @@ function Setup-Mobile-NodeJS { $testDir = Join-Path $TARGET "test" Push-Location $testDir try { + $UseLocal = $true 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) @@ -429,6 +395,8 @@ function Setup-Mobile-NodeJS { $env:BROWSERSTACK_PROJECT_NAME = "now-$NOW_OS-app" $env:BROWSERSTACK_LOCAL = "true" + Report-BStackLocalStatus -LocalFlag $UseLocal + # Validate Environment Variables Log-Section "Validate Environment Variables" $GLOBAL_LOG Log-Line "ā„¹ļø BrowserStack Username: $BROWSERSTACK_USERNAME" $GLOBAL_LOG From aee464ddc1e5378d96fd8e3b4b878f24edb41420 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 09:15:39 +0530 Subject: [PATCH 28/36] only running windows --- .github/workflows/test-scripts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 5ddc157..21a004b 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 @@ -358,7 +358,7 @@ jobs: test-linux: name: Test mac/run.sh on Linux - # if: false + if: false runs-on: ubuntu-latest timeout-minutes: 15 environment: BrowserStack From 7e9237fce24628367f7ff38c74e68bfeaf0a771a Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 12:35:31 +0530 Subject: [PATCH 29/36] add debugging logs for hanging issue --- .github/workflows/test-scripts.yml | 246 ++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 21a004b..452e5eb 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -177,6 +177,8 @@ jobs: runs-on: windows-latest timeout-minutes: 25 environment: BrowserStack + env: + ACTIONS_STEP_DEBUG: true defaults: run: shell: powershell @@ -200,6 +202,16 @@ jobs: $PSVersionTable.PSVersion Write-Host "āœ… PowerShell version check complete" + - name: Enable PowerShell debug logging + run: | + Write-Host "Enabling PowerShell debug and verbose logging..." + $DebugPreference = "Continue" + $VerbosePreference = "Continue" + $ErrorActionPreference = "Continue" + Write-Host "āœ… Debug logging enabled" + Write-Debug "Debug logging is now active" + Write-Verbose "Verbose logging is now active" + - name: Validate PowerShell script syntax run: | Write-Host "Validating win/run.ps1 syntax..." @@ -225,13 +237,15 @@ jobs: - name: Run PSScriptAnalyzer run: | + $DebugPreference = "Continue" + $VerbosePreference = "Continue" Write-Host "Installing PSScriptAnalyzer if needed..." if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) { - Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -Scope CurrentUser + Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -Scope CurrentUser -Verbose } Write-Host "Running PSScriptAnalyzer..." - Invoke-ScriptAnalyzer -Path "win" -Recurse -ReportSummary -ErrorAction Continue + Invoke-ScriptAnalyzer -Path "win" -Recurse -ReportSummary -ErrorAction Continue -Verbose Write-Host "āœ… PSScriptAnalyzer analysis complete (continuing even if issues are found)" - name: Check script file encoding @@ -259,6 +273,47 @@ jobs: if (Get-Command git.exe -ErrorAction SilentlyContinue) { Write-Host "āœ… git found" } else { Write-Host "āš ļø git not found" } Write-Host "āœ… PowerShell dependencies verified" + - name: Check initial system resources + run: | + Write-Host "=== Initial System Resources ===" + + # Get system memory info + $os = Get-CimInstance Win32_OperatingSystem + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2) + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + + Write-Host "Total Physical Memory: $totalMemoryGB GB" + Write-Host "Free Physical Memory: $freeMemoryGB GB" + Write-Host "Used Physical Memory: $usedMemoryGB GB ($memoryPercent%)" + + # Get CPU info + $cpu = Get-CimInstance Win32_Processor + Write-Host "CPU Name: $($cpu.Name)" + Write-Host "CPU Cores: $($cpu.NumberOfCores)" + Write-Host "CPU Logical Processors: $($cpu.NumberOfLogicalProcessors)" + + # Get current CPU usage (average over 3 samples) + Write-Host "Measuring CPU usage..." + $cpuSamples = @() + for ($i = 0; $i -lt 3; $i++) { + $cpuSample = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples[0].CookedValue + $cpuSamples += $cpuSample + Start-Sleep -Seconds 1 + } + $avgCpu = [math]::Round(($cpuSamples | Measure-Object -Average).Average, 2) + Write-Host "Current CPU Usage: $avgCpu%" + + # Get disk space + $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $freeDiskGB = [math]::Round($disk.FreeSpace / 1GB, 2) + $totalDiskGB = [math]::Round($disk.Size / 1GB, 2) + $usedDiskGB = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 2) + Write-Host "C: Drive Free Space: $freeDiskGB GB / $totalDiskGB GB" + + Write-Host "āœ… Initial resource check complete" + - name: Integration Test - Silent Mode Execution if: success() env: @@ -266,19 +321,28 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} TURL: https://bstackdemo.com run: | + $DebugPreference = "Continue" + $VerbosePreference = "Continue" + $ErrorActionPreference = "Continue" Write-Host "Running integration tests in silent mode..." + Write-Debug "Debug logging enabled for integration tests" + Write-Verbose "Verbose logging enabled for integration tests" # Use defaults if secrets are missing (for local / dry runs) $BrowserStackUsername = if ($env:BROWSERSTACK_USERNAME) { $env:BROWSERSTACK_USERNAME } else { "test_user" } $BrowserStackAccessKey = if ($env:BROWSERSTACK_ACCESS_KEY) { $env:BROWSERSTACK_ACCESS_KEY } else { "test_key" } $TestUrl = $env:TURL + Write-Debug "BrowserStack Username: $BrowserStackUsername" + Write-Verbose "Test URL: $TestUrl" + $env:BROWSERSTACK_USERNAME = $BrowserStackUsername $env:BROWSERSTACK_ACCESS_KEY = $BrowserStackAccessKey $env:TURL = $TestUrl # Absolute path is safer in CI $scriptPath = Join-Path $env:GITHUB_WORKSPACE "win\run.ps1" + Write-Debug "Script path: $scriptPath" $testConfigs = @( @("web", "java"), @@ -289,9 +353,43 @@ jobs: @("app", "nodejs") ) + # Function to get system resources + function Get-SystemResources { + $os = Get-CimInstance Win32_OperatingSystem + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2) + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + + $cpuSample = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue + $cpuUsage = [math]::Round($cpuSample, 2) + + # Get PowerShell process memory + $pwshProcess = Get-Process -Id $PID -ErrorAction SilentlyContinue + $pwshMemoryMB = if ($pwshProcess) { [math]::Round($pwshProcess.WorkingSet64 / 1MB, 2) } else { 0 } + + return @{ + TotalMemoryGB = $totalMemoryGB + FreeMemoryGB = $freeMemoryGB + UsedMemoryGB = $usedMemoryGB + MemoryPercent = $memoryPercent + CpuUsage = $cpuUsage + PwshMemoryMB = $pwshMemoryMB + } + } + $overallFailed = $false $logRoot = Join-Path $env:TEMP "now-tests" + $resourceLogPath = Join-Path $logRoot "resource_usage.log" New-Item -ItemType Directory -Force -Path $logRoot | Out-Null + + # Log initial resources + $initialResources = Get-SystemResources + Write-Host "=== Initial Resources Before Tests ===" + Write-Host "Memory: $($initialResources.UsedMemoryGB) GB / $($initialResources.TotalMemoryGB) GB ($($initialResources.MemoryPercent)%)" + Write-Host "CPU: $($initialResources.CpuUsage)%" + Write-Host "PowerShell Memory: $($initialResources.PwshMemoryMB) MB" + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - INITIAL - Memory: $($initialResources.UsedMemoryGB)GB/$($initialResources.TotalMemoryGB)GB ($($initialResources.MemoryPercent)%), CPU: $($initialResources.CpuUsage)%, Pwsh: $($initialResources.PwshMemoryMB)MB" | Out-File -FilePath $resourceLogPath -Append foreach ($config in $testConfigs) { $testType = $config[0] @@ -300,11 +398,66 @@ jobs: Write-Host "================================" Write-Host "Testing: $scriptPath --silent $testType $techStack" Write-Host "================================" + Write-Debug "Test type: $testType, Tech stack: $techStack" + + # Get resources before test + $resourcesBefore = Get-SystemResources + Write-Host "--- Resources Before Test ---" + Write-Host "Memory: $($resourcesBefore.UsedMemoryGB) GB / $($resourcesBefore.TotalMemoryGB) GB ($($resourcesBefore.MemoryPercent)%)" + Write-Host "CPU: $($resourcesBefore.CpuUsage)%" + Write-Host "PowerShell Memory: $($resourcesBefore.PwshMemoryMB) MB" + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - BEFORE [$testType/$techStack] - Memory: $($resourcesBefore.UsedMemoryGB)GB/$($resourcesBefore.TotalMemoryGB)GB ($($resourcesBefore.MemoryPercent)%), CPU: $($resourcesBefore.CpuUsage)%, Pwsh: $($resourcesBefore.PwshMemoryMB)MB" | Out-File -FilePath $resourceLogPath -Append $logPath = Join-Path $logRoot "run_test_${testType}_${techStack}.log" + Write-Verbose "Log path: $logPath" + + # Start background resource monitoring + $monitorJob = Start-Job -ScriptBlock { + param($logFile, $testName) + $endTime = (Get-Date).AddMinutes(10) # Monitor for max 10 minutes + while ((Get-Date) -lt $endTime) { + try { + $os = Get-CimInstance Win32_OperatingSystem + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + $cpuSample = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue + $cpuUsage = [math]::Round($cpuSample, 2) + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - MONITOR [$testName] - Memory: $usedMemoryGB GB/$totalMemoryGB GB ($memoryPercent%), CPU: $cpuUsage%" | Out-File -FilePath $logFile -Append + } catch { + # Ignore errors in monitoring + } + Start-Sleep -Seconds 30 # Sample every 30 seconds + } + } -ArgumentList $resourceLogPath, "${testType}_${techStack}" + Write-Debug "Executing script: $scriptPath --silent $testType $techStack" + $startTime = Get-Date & $scriptPath --silent $testType $techStack 2>&1 | Tee-Object -FilePath $logPath -Append $exitCode = $LASTEXITCODE + $endTime = Get-Date + $duration = $endTime - $startTime + Write-Debug "Script exit code: $exitCode" + + # Stop monitoring job + Stop-Job -Job $monitorJob -ErrorAction SilentlyContinue + Remove-Job -Job $monitorJob -ErrorAction SilentlyContinue + + # Get resources after test + Start-Sleep -Seconds 2 # Brief pause to let system settle + $resourcesAfter = Get-SystemResources + Write-Host "--- Resources After Test ---" + Write-Host "Memory: $($resourcesAfter.UsedMemoryGB) GB / $($resourcesAfter.TotalMemoryGB) GB ($($resourcesAfter.MemoryPercent)%)" + Write-Host "CPU: $($resourcesAfter.CpuUsage)%" + Write-Host "PowerShell Memory: $($resourcesAfter.PwshMemoryMB) MB" + Write-Host "Test Duration: $($duration.TotalSeconds) seconds" + + $memoryDelta = [math]::Round($resourcesAfter.UsedMemoryGB - $resourcesBefore.UsedMemoryGB, 2) + $cpuDelta = [math]::Round($resourcesAfter.CpuUsage - $resourcesBefore.CpuUsage, 2) + Write-Host "Memory Change: $memoryDelta GB" + Write-Host "CPU Change: $cpuDelta%" + + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - AFTER [$testType/$techStack] - Memory: $($resourcesAfter.UsedMemoryGB)GB/$($resourcesAfter.TotalMemoryGB)GB ($($resourcesAfter.MemoryPercent)%), CPU: $($resourcesAfter.CpuUsage)%, Pwsh: $($resourcesAfter.PwshMemoryMB)MB, Duration: $([math]::Round($duration.TotalSeconds, 2))s, MemoryDelta: ${memoryDelta}GB, CpuDelta: ${cpuDelta}%" | Out-File -FilePath $resourceLogPath -Append if ($exitCode -eq 0) { Write-Host "āœ… $testType / $techStack completed (exit code: $exitCode)" @@ -325,26 +478,109 @@ jobs: } Write-Host "āœ… All integration tests completed successfully" + + # Log final resources + $finalResources = Get-SystemResources + Write-Host "=== Final Resources After All Tests ===" + Write-Host "Memory: $($finalResources.UsedMemoryGB) GB / $($finalResources.TotalMemoryGB) GB ($($finalResources.MemoryPercent)%)" + Write-Host "CPU: $($finalResources.CpuUsage)%" + Write-Host "PowerShell Memory: $($finalResources.PwshMemoryMB) MB" + + $totalMemoryDelta = [math]::Round($finalResources.UsedMemoryGB - $initialResources.UsedMemoryGB, 2) + $totalCpuDelta = [math]::Round($finalResources.CpuUsage - $initialResources.CpuUsage, 2) + Write-Host "Total Memory Change: $totalMemoryDelta GB" + Write-Host "Total CPU Change: $totalCpuDelta%" + + "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - FINAL - Memory: $($finalResources.UsedMemoryGB)GB/$($finalResources.TotalMemoryGB)GB ($($finalResources.MemoryPercent)%), CPU: $($finalResources.CpuUsage)%, Pwsh: $($finalResources.PwshMemoryMB)MB, TotalMemoryDelta: ${totalMemoryDelta}GB, TotalCpuDelta: ${totalCpuDelta}%" | Out-File -FilePath $resourceLogPath -Append + + Write-Host "Resource usage log saved to: $resourceLogPath" + + - name: Check final system resources + if: always() + run: | + Write-Host "=== Final System Resources ===" + + # Get system memory info + $os = Get-CimInstance Win32_OperatingSystem + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2) + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + + Write-Host "Total Physical Memory: $totalMemoryGB GB" + Write-Host "Free Physical Memory: $freeMemoryGB GB" + Write-Host "Used Physical Memory: $usedMemoryGB GB ($memoryPercent%)" + + # Get current CPU usage + Write-Host "Measuring CPU usage..." + $cpuSamples = @() + for ($i = 0; $i -lt 3; $i++) { + $cpuSample = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples[0].CookedValue + $cpuSamples += $cpuSample + Start-Sleep -Seconds 1 + } + $avgCpu = [math]::Round(($cpuSamples | Measure-Object -Average).Average, 2) + Write-Host "Current CPU Usage: $avgCpu%" + + # Get top processes by memory + Write-Host "`n=== Top 10 Processes by Memory Usage ===" + Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 | Format-Table -Property Name, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet64 / 1MB, 2)}}, CPU, Id -AutoSize + + # Get top processes by CPU + Write-Host "=== Top 10 Processes by CPU Usage ===" + Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 | Format-Table -Property Name, CPU, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet64 / 1MB, 2)}}, Id -AutoSize + + Write-Host "āœ… Final resource check complete" - name: Sync BrowserStack logs to workspace (Windows) if: always() run: | + $DebugPreference = "Continue" + $VerbosePreference = "Continue" $dest = Join-Path $env:GITHUB_WORKSPACE "bs-logs" - New-Item -ItemType Directory -Force -Path $dest | Out-Null + Write-Debug "Destination directory: $dest" + New-Item -ItemType Directory -Force -Path $dest -Verbose | Out-Null $bsLogPath = Join-Path $env:USERPROFILE ".browserstack\NOW\logs" $tempLogDir = Join-Path $env:TEMP "now-tests" + Write-Debug "BrowserStack log path: $bsLogPath" + Write-Debug "Temp log directory: $tempLogDir" if (Test-Path $bsLogPath) { Write-Host "Copying logs from $bsLogPath" - Copy-Item -Path (Join-Path $bsLogPath "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue + Write-Verbose "Copying logs from $bsLogPath to $dest" + Copy-Item -Path (Join-Path $bsLogPath "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue -Verbose } else { Write-Host "No logs found at $bsLogPath" + Write-Debug "BrowserStack logs directory does not exist" } if (Test-Path $tempLogDir) { Write-Host "Copying integration logs from $tempLogDir" - Copy-Item -Path (Join-Path $tempLogDir "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue + Write-Verbose "Copying integration logs from $tempLogDir to $dest" + Copy-Item -Path (Join-Path $tempLogDir "*") -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue -Verbose + } else { + Write-Debug "Temp log directory does not exist" + } + + # Copy resource usage log if it exists + $resourceLogPath = Join-Path $tempLogDir "resource_usage.log" + if (Test-Path $resourceLogPath) { + Write-Host "Copying resource usage log" + Copy-Item -Path $resourceLogPath -Destination $dest -Force -ErrorAction SilentlyContinue -Verbose + } + + - name: Display resource usage summary + if: always() + run: | + $resourceLogPath = Join-Path $env:TEMP "now-tests\resource_usage.log" + if (Test-Path $resourceLogPath) { + Write-Host "=== Resource Usage Summary ===" + Write-Host "`nFull resource usage log:" + Get-Content -Path $resourceLogPath + Write-Host "`nāœ… Resource usage summary displayed" + } else { + Write-Host "Resource usage log not found at $resourceLogPath" } - name: Upload BrowserStack Logs as Artifacts From 9654913b6f2f9db9d63eeae0f2c32060aa88b85a Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 13:15:45 +0530 Subject: [PATCH 30/36] silent mode without additional logs --- win/common-utils.ps1 | 7 +++++++ win/logging-utils.ps1 | 21 ++++++++++++++++++++- win/run.ps1 | 5 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index b5de6a0..998d2f3 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -288,6 +288,13 @@ function Invoke-Py { function Show-Spinner { param([Parameter(Mandatory)][System.Diagnostics.Process]$Process) + + # Skip spinner in silent mode + if (Get-SilentMode) { + $Process.WaitForExit() + return + } + $spin = @('|','/','-','\') $i = 0 $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") diff --git a/win/logging-utils.ps1 b/win/logging-utils.ps1 index 68da254..991fbfd 100644 --- a/win/logging-utils.ps1 +++ b/win/logging-utils.ps1 @@ -4,6 +4,10 @@ if (-not (Get-Variable -Name NOW_RUN_LOG_FILE -Scope Script -ErrorAction Silentl $script:NOW_RUN_LOG_FILE = "" } +if (-not (Get-Variable -Name SILENT_MODE -Scope Script -ErrorAction SilentlyContinue)) { + $script:SILENT_MODE = $false +} + function Set-RunLogFile { param([string]$Path) $script:NOW_RUN_LOG_FILE = $Path @@ -18,6 +22,15 @@ function Get-RunLogFile { return $script:NOW_RUN_LOG_FILE } +function Set-SilentMode { + param([bool]$Enabled) + $script:SILENT_MODE = $Enabled +} + +function Get-SilentMode { + return $script:SILENT_MODE +} + function Log-Line { param( [Parameter(Mandatory=$true)][AllowEmptyString()][string]$Message, @@ -29,7 +42,13 @@ function Log-Line { $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") $line = "[$ts] $Message" - Write-Host $line + + # Only write to console if not in silent mode + if (-not $script:SILENT_MODE) { + Write-Host $line + } + + # Always write to log file if ($DestFile) { $dir = Split-Path -Parent $DestFile if ($dir -and !(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } diff --git a/win/run.ps1 b/win/run.ps1 index 8003d2b..0905d5f 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -32,6 +32,11 @@ $script:PSScriptRootResolved = Split-Path -Parent $MyInvocation.MyCommand.Path # ===== Main flow (baseline steps then run) ===== try { + # Set silent mode early if --silent is detected + if ($RunMode -match "--silent") { + Set-SilentMode -Enabled $true + } + # Get test type and tech stack before logging if ($RunMode -match "--silent|--debug") { $textInfo = (Get-Culture).TextInfo From 64e4245b5dd3ead3346d7eb850c9ce8e787bdc71 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 15:35:28 +0530 Subject: [PATCH 31/36] test timeout. additional logging. GHA timeout to 15 --- .github/workflows/test-scripts.yml | 2 +- win/common-utils.ps1 | 224 +++++++++++++++++++++++++++-- win/env-setup-run.ps1 | 91 ++++++++++-- 3 files changed, 293 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 452e5eb..9110743 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -175,7 +175,7 @@ jobs: test-windows: name: Test win/run.ps1 on Windows runs-on: windows-latest - timeout-minutes: 25 + timeout-minutes: 15 environment: BrowserStack env: ACTIONS_STEP_DEBUG: true diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 998d2f3..6b5bb63 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -169,7 +169,8 @@ function Invoke-External { [Parameter(Mandatory)][string]$Exe, [Parameter()][string[]]$Arguments = @(), [string]$LogFile, - [string]$WorkingDirectory + [string]$WorkingDirectory, + [int]$TimeoutSeconds = 0 # 0 means no timeout ) $psi = New-Object System.Diagnostics.ProcessStartInfo $exeToRun = $Exe @@ -202,14 +203,24 @@ function Invoke-External { $logDir = Split-Path -Parent $LogFile if ($logDir -and !(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } + # Simplified event handlers - write directly to reduce CPU overhead + # Batching adds complexity and may not help if output is sparse $stdoutAction = { if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { - Add-Content -Path $Event.MessageData -Value $EventArgs.Data + try { + Add-Content -Path $Event.MessageData -Value $EventArgs.Data -ErrorAction SilentlyContinue + } catch { + # Silently ignore write errors to prevent event handler from blocking + } } } $stderrAction = { if (-not [string]::IsNullOrEmpty($EventArgs.Data)) { - Add-Content -Path $Event.MessageData -Value $EventArgs.Data + try { + Add-Content -Path $Event.MessageData -Value $EventArgs.Data -ErrorAction SilentlyContinue + } catch { + # Silently ignore write errors to prevent event handler from blocking + } } } @@ -217,19 +228,210 @@ function Invoke-External { $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $stderrAction -MessageData $LogFile [void]$p.Start() + $startTime = Get-Date $p.BeginOutputReadLine() $p.BeginErrorReadLine() - $p.WaitForExit() + + # Log process start + if ($LogFile) { + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process started: PID=$($p.Id), Command=$exeToRun $argLine" + } + + # Wait with timeout support and periodic status checks + if ($TimeoutSeconds -gt 0) { + $checkInterval = 30 # Check every 30 seconds + $lastOutputTime = Get-Date + $lastLogCheck = Get-Date + $lastResourceCheck = Get-Date + $highCpuCount = 0 + $highCpuThreshold = 90 # Warn if CPU > 90% + $consecutiveHighCpuLimit = 3 # Warn after 3 consecutive high CPU checks + + # Use WaitForExit with timeout, but check periodically for status + $timeoutMs = $TimeoutSeconds * 1000 + $remainingMs = $timeoutMs + + while (-not $p.HasExited -and $remainingMs -gt 0) { + $checkMs = [Math]::Min($checkInterval * 1000, $remainingMs) + if (-not $p.WaitForExit($checkMs)) { + $remainingMs -= $checkMs + $elapsed = ((Get-Date) - $startTime).TotalSeconds + + # Monitor CPU and memory usage every 30 seconds + if (((Get-Date) - $lastResourceCheck).TotalSeconds -ge 30) { + try { + # Get process CPU usage + $proc = Get-Process -Id $p.Id -ErrorAction SilentlyContinue + if ($proc) { + $procCpu = [math]::Round($proc.CPU, 2) + $procMemoryMB = [math]::Round($proc.WorkingSet64 / 1MB, 2) + + # Get system CPU usage + $sysCpu = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue + $sysCpuPercent = [math]::Round($sysCpu, 2) + + # Get system memory + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + if ($os) { + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + + # Log resource usage + if ($LogFile) { + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Resource check - Process CPU: ${procCpu}s, Process Memory: ${procMemoryMB}MB, System CPU: ${sysCpuPercent}%, System Memory: ${usedMemoryGB}GB/${totalMemoryGB}GB ($memoryPercent%)" + } + + # Warn if CPU is consistently high + if ($sysCpuPercent -gt $highCpuThreshold) { + $highCpuCount++ + if ($highCpuCount -ge $consecutiveHighCpuLimit) { + $warningMsg = "āš ļø High CPU usage detected: ${sysCpuPercent}% (threshold: ${highCpuThreshold}%). This may cause performance issues or apparent hangs." + if ($LogFile) { + Add-Content -Path $LogFile -Value "[WARNING] $warningMsg" + } + Log-Line $warningMsg $GLOBAL_LOG + $highCpuCount = 0 # Reset counter after warning + } + } else { + $highCpuCount = 0 # Reset counter if CPU drops + } + + # Warn if memory is high (>85%) + if ($memoryPercent -gt 85) { + $memWarningMsg = "āš ļø High memory usage detected: ${memoryPercent}%. System may be under memory pressure." + if ($LogFile) { + Add-Content -Path $LogFile -Value "[WARNING] $memWarningMsg" + } + Log-Line $memWarningMsg $GLOBAL_LOG + } + } + } + } catch { + # Silently continue if resource monitoring fails + } + $lastResourceCheck = Get-Date + } + + # Check if log file has been updated recently (indicates process is producing output) + if ($LogFile -and (Test-Path $LogFile)) { + $logLastWrite = (Get-Item $LogFile).LastWriteTime + if ($logLastWrite -gt $lastOutputTime) { + $lastOutputTime = $logLastWrite + } + + # Log status every minute + if (((Get-Date) - $lastLogCheck).TotalSeconds -ge 60) { + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process still running... Elapsed: $([math]::Round($elapsed, 0))s, Remaining: $([math]::Round($remainingMs/1000, 0))s" + $lastLogCheck = Get-Date + } + } + } else { + break # Process exited + } + } + + # Check if we timed out + if (-not $p.HasExited) { + # Log final resource state before timeout + try { + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + if ($os) { + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + $sysCpu = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue + $sysCpuPercent = [math]::Round($sysCpu, 2) + + if ($LogFile) { + Add-Content -Path $LogFile -Value "[TIMEOUT] Final resource state - System CPU: ${sysCpuPercent}%, System Memory: ${usedMemoryGB}GB/${totalMemoryGB}GB ($memoryPercent%)" + } + + # Check if high CPU might have contributed to timeout + if ($sysCpuPercent -gt $highCpuThreshold) { + $cpuWarning = "āš ļø High CPU usage (${sysCpuPercent}%) detected at timeout. This may have contributed to the timeout." + if ($LogFile) { + Add-Content -Path $LogFile -Value "[TIMEOUT] $cpuWarning" + } + Log-Line $cpuWarning $GLOBAL_LOG + } + } + } catch { + # Ignore resource check errors during timeout + } + + $errorMsg = "Command timed out after $TimeoutSeconds seconds: $exeToRun $argLine" + Add-Content -Path $LogFile -Value "[TIMEOUT] $errorMsg" + Log-Line "āŒ $errorMsg" $GLOBAL_LOG + try { + if (-not $p.HasExited) { + $p.Kill() + Start-Sleep -Milliseconds 500 + if (-not $p.HasExited) { + Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue + } + } + } catch { + Log-Line "āš ļø Error killing timed-out process: $($_.Exception.Message)" $GLOBAL_LOG + } + Unregister-Event -SourceIdentifier $stdoutEvent.Name -ErrorAction SilentlyContinue + Unregister-Event -SourceIdentifier $stderrEvent.Name -ErrorAction SilentlyContinue + Remove-Job -Id $stdoutEvent.Id -Force -ErrorAction SilentlyContinue + Remove-Job -Id $stderrEvent.Id -Force -ErrorAction SilentlyContinue + throw $errorMsg + } + } else { + $p.WaitForExit() + } + + # Log process completion with final resource state + if ($LogFile) { + $endTime = Get-Date + $duration = ($endTime - $startTime).TotalSeconds + + # Log final resource state + try { + $proc = Get-Process -Id $p.Id -ErrorAction SilentlyContinue + $procCpu = if ($proc) { [math]::Round($proc.CPU, 2) } else { "N/A" } + $procMemoryMB = if ($proc) { [math]::Round($proc.WorkingSet64 / 1MB, 2) } else { "N/A" } + + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + if ($os) { + $usedMemoryGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2) + $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) + $memoryPercent = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) + $sysCpu = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue + $sysCpuPercent = [math]::Round($sysCpu, 2) + + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process completed: ExitCode=$($p.ExitCode), Duration=$([math]::Round($duration, 2))s" + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Final resources - Process CPU: ${procCpu}s, Process Memory: ${procMemoryMB}MB, System CPU: ${sysCpuPercent}%, System Memory: ${usedMemoryGB}GB/${totalMemoryGB}GB ($memoryPercent%)" + } else { + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process completed: ExitCode=$($p.ExitCode), Duration=$([math]::Round($duration, 2))s" + } + } catch { + Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process completed: ExitCode=$($p.ExitCode), Duration=$([math]::Round($duration, 2))s" + } + } - Unregister-Event -SourceIdentifier $stdoutEvent.Name - Unregister-Event -SourceIdentifier $stderrEvent.Name - Remove-Job -Id $stdoutEvent.Id -Force - Remove-Job -Id $stderrEvent.Id -Force + Unregister-Event -SourceIdentifier $stdoutEvent.Name -ErrorAction SilentlyContinue + Unregister-Event -SourceIdentifier $stderrEvent.Name -ErrorAction SilentlyContinue + Remove-Job -Id $stdoutEvent.Id -Force -ErrorAction SilentlyContinue + Remove-Job -Id $stderrEvent.Id -Force -ErrorAction SilentlyContinue } else { [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() + + if ($TimeoutSeconds -gt 0) { + if (-not $p.WaitForExit($TimeoutSeconds * 1000)) { + $p.Kill() + throw "Command timed out after $TimeoutSeconds seconds: $exeToRun $argLine" + } + $stdout = $p.StandardOutput.ReadToEnd() + $stderr = $p.StandardError.ReadToEnd() + } else { + $stdout = $p.StandardOutput.ReadToEnd() + $stderr = $p.StandardError.ReadToEnd() + $p.WaitForExit() + } } return $p.ExitCode diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 0712855..270d449 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -56,8 +56,19 @@ function Setup-Web-Java { 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 + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + try { + $exitCode = Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + } + throw + } } finally { Pop-Location @@ -127,8 +138,21 @@ function Setup-Web-Python { $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 + + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + try { + $exitCode = Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + Log-Line "āš ļø This may indicate tests are still running on BrowserStack but the local process timed out." $GLOBAL_LOG + } + throw + } } finally { Pop-Location @@ -187,8 +211,19 @@ function Setup-Web-NodeJS { 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 + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + try { + $exitCode = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + } + throw + } } finally { Pop-Location @@ -261,8 +296,19 @@ function Setup-Mobile-Java { 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 + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + try { + $exitCode = Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + } + throw + } } finally { Pop-Location @@ -345,13 +391,23 @@ function Setup-Mobile-Python { $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" Print-TestsRunningSection -Command "cd $runDirName && browserstack-sdk pytest -s bstack_sample.py" + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG Push-Location $runDir try { - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path) + $exitCode = Invoke-External -Exe $sdk -Arguments @('pytest','-s','bstack_sample.py') -LogFile $LogFile -WorkingDirectory (Get-Location).Path -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + Log-Line "āš ļø This may indicate tests are still running on BrowserStack but the local process timed out." $GLOBAL_LOG + } + throw } finally { Pop-Location } - Log-Line "ā„¹ļø Run Test command completed." $GLOBAL_LOG } finally { Pop-Location @@ -408,8 +464,19 @@ function Setup-Mobile-NodeJS { 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 + $testTimeout = 300 + Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + try { + $exitCode = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir -TimeoutSeconds $testTimeout + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + } catch { + $errorMsg = $_.Exception.Message + Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + if ($errorMsg -match "timed out") { + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG + } + throw + } } finally { Pop-Location From 1cfeec63f23e3b8ab8018e00d7ada7f10cc8e570 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 15:51:32 +0530 Subject: [PATCH 32/36] debug web-python --- .github/workflows/test-scripts.yml | 6 +-- win/common-utils.ps1 | 68 +++++++++++++++++++++++++++--- win/env-setup-run.ps1 | 52 ++++++++++++++++++++++- 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 9110743..ecc2d60 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -16,7 +16,7 @@ jobs: name: Test mac/run.sh on macOS if: false runs-on: macos-latest - timeout-minutes: 15 + timeout-minutes: 8 environment: BrowserStack steps: - name: Checkout code @@ -103,8 +103,8 @@ jobs: # Test configurations test_configs=( - "web java" - "app java" + # "web java" + # "app java" "web python" "app python" "web nodejs" diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 6b5bb63..3a5796f 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -229,14 +229,53 @@ function Invoke-External { [void]$p.Start() $startTime = Get-Date - $p.BeginOutputReadLine() - $p.BeginErrorReadLine() + $processId = $p.Id - # Log process start + # Log process start immediately (before BeginOutputReadLine to ensure it's logged) + $startLogMsg = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process started: PID=$processId, Command=$exeToRun $argLine" if ($LogFile) { - Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process started: PID=$($p.Id), Command=$exeToRun $argLine" + try { + Add-Content -Path $LogFile -Value $startLogMsg -ErrorAction Stop + } catch { + # If log file write fails, try to log to GLOBAL_LOG + if ($GLOBAL_LOG) { + Log-Line "āš ļø Failed to write to log file $LogFile, error: $($_.Exception.Message)" $GLOBAL_LOG + } + } + } + + # Also log to GLOBAL_LOG for visibility + if ($GLOBAL_LOG) { + Log-Line "ā„¹ļø External process started: PID=$processId, Command=$exeToRun" $GLOBAL_LOG + } + + # Verify process is actually running and log initial state + Start-Sleep -Milliseconds 200 + if ($p.HasExited) { + $exitCode = $p.ExitCode + $errorMsg = "Process exited immediately with code $exitCode: $exeToRun $argLine" + if ($LogFile) { + Add-Content -Path $LogFile -Value "[ERROR] $errorMsg" + } + throw $errorMsg + } + + # Log process state verification + try { + $procInfo = Get-Process -Id $processId -ErrorAction SilentlyContinue + if ($procInfo) { + $procStateMsg = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process verified running: PID=$processId, CPU=$([math]::Round($procInfo.CPU, 2))s, Memory=$([math]::Round($procInfo.WorkingSet64/1MB, 2))MB" + if ($LogFile) { + Add-Content -Path $LogFile -Value $procStateMsg + } + } + } catch { + # Ignore process info errors } + $p.BeginOutputReadLine() + $p.BeginErrorReadLine() + # Wait with timeout support and periodic status checks if ($TimeoutSeconds -gt 0) { $checkInterval = 30 # Check every 30 seconds @@ -314,18 +353,37 @@ function Invoke-External { } # Check if log file has been updated recently (indicates process is producing output) + $hasOutput = $false if ($LogFile -and (Test-Path $LogFile)) { $logLastWrite = (Get-Item $LogFile).LastWriteTime if ($logLastWrite -gt $lastOutputTime) { $lastOutputTime = $logLastWrite + $hasOutput = $true } # Log status every minute if (((Get-Date) - $lastLogCheck).TotalSeconds -ge 60) { - Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process still running... Elapsed: $([math]::Round($elapsed, 0))s, Remaining: $([math]::Round($remainingMs/1000, 0))s" + $statusMsg = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Process still running... Elapsed: $([math]::Round($elapsed, 0))s, Remaining: $([math]::Round($remainingMs/1000, 0))s" + if (-not $hasOutput -and $elapsed -gt 60) { + $statusMsg += " [WARNING: No output detected in last 60+ seconds - process may be hung]" + } + Add-Content -Path $LogFile -Value $statusMsg $lastLogCheck = Get-Date } } + + # Warn if process has been running for 2+ minutes with no output + if (-not $hasOutput -and $elapsed -ge 120) { + $noOutputWarning = "āš ļø Process has been running for $([math]::Round($elapsed, 0))s with no output. This may indicate the process is hung." + if ($LogFile) { + Add-Content -Path $LogFile -Value "[WARNING] $noOutputWarning" + } + if ($GLOBAL_LOG) { + Log-Line $noOutputWarning $GLOBAL_LOG + } + # Reset check to avoid spam + $lastOutputTime = Get-Date + } } else { break # Process exited } diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 270d449..8e3cc29 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -137,19 +137,67 @@ function Setup-Web-Python { $platforms -split "`n" | ForEach-Object { if ($_.Trim()) { Log-Line " $_" $GLOBAL_LOG } } $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" + + # Verify SDK exists before attempting to run + if (-not (Test-Path $sdk)) { + throw "BrowserStack SDK not found at: $sdk" + } + Log-Line "ā„¹ļø SDK path verified: $sdk" $GLOBAL_LOG + + # Check if BrowserStack Local binary might be needed + $bsLocalPath = Join-Path $venv "Scripts\BrowserStackLocal.exe" + if ($UseLocal -and -not (Test-Path $bsLocalPath)) { + Log-Line "āš ļø BrowserStack Local binary not found at: $bsLocalPath" $GLOBAL_LOG + Log-Line "āš ļø Tests may hang if BrowserStack Local is required but missing." $GLOBAL_LOG + } + Print-TestsRunningSection -Command "browserstack-sdk pytest -s tests/bstack-sample-test.py" $testTimeout = 300 Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG + Log-Line "ā„¹ļø Working directory: $TARGET" $GLOBAL_LOG + Log-Line "ā„¹ļø Log file: $LogFile" $GLOBAL_LOG + + # Log environment variables that might affect execution + Log-Line "ā„¹ļø Environment check - BROWSERSTACK_USERNAME: $($env:BROWSERSTACK_USERNAME -ne $null)" $GLOBAL_LOG + Log-Line "ā„¹ļø Environment check - BROWSERSTACK_ACCESS_KEY: $($env:BROWSERSTACK_ACCESS_KEY -ne $null)" $GLOBAL_LOG + Log-Line "ā„¹ļø Environment check - BROWSERSTACK_LOCAL: $($env:BROWSERSTACK_LOCAL)" $GLOBAL_LOG + try { + $startTime = Get-Date + Log-Line "ā„¹ļø Invoking external command at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')..." $GLOBAL_LOG + $exitCode = Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/bstack-sample-test.py') -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout - Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode" $GLOBAL_LOG + + $endTime = Get-Date + $duration = ($endTime - $startTime).TotalSeconds + Log-Line "ā„¹ļø Run Test command completed with exit code: $exitCode (Duration: $([math]::Round($duration, 2))s)" $GLOBAL_LOG } catch { $errorMsg = $_.Exception.Message - Log-Line "āŒ Test execution failed: $errorMsg" $GLOBAL_LOG + $endTime = Get-Date + $duration = ($endTime - $startTime).TotalSeconds + Log-Line "āŒ Test execution failed after $([math]::Round($duration, 2))s: $errorMsg" $GLOBAL_LOG + + # Check if process is still running (might indicate hang) + try { + $runningProcesses = Get-Process -Name "browserstack-sdk" -ErrorAction SilentlyContinue + if ($runningProcesses) { + Log-Line "āš ļø Warning: browserstack-sdk processes still running: $($runningProcesses.Id -join ', ')" $GLOBAL_LOG + } + + # Check for BrowserStack Local processes + $bsLocalProcesses = Get-Process -Name "BrowserStackLocal" -ErrorAction SilentlyContinue + if ($bsLocalProcesses) { + Log-Line "ā„¹ļø BrowserStack Local processes running: $($bsLocalProcesses.Id -join ', ')" $GLOBAL_LOG + } + } catch { + # Ignore process check errors + } + if ($errorMsg -match "timed out") { Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG Log-Line "āš ļø This may indicate tests are still running on BrowserStack but the local process timed out." $GLOBAL_LOG + Log-Line "āš ļø Check the log file for any output from browserstack-sdk: $LogFile" $GLOBAL_LOG } throw } From 5daaec6ec5e344f1c6709e33702daf478a1435b2 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 15:53:57 +0530 Subject: [PATCH 33/36] debug web-python --- win/common-utils.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index 3a5796f..fe59da7 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -253,7 +253,7 @@ function Invoke-External { Start-Sleep -Milliseconds 200 if ($p.HasExited) { $exitCode = $p.ExitCode - $errorMsg = "Process exited immediately with code $exitCode: $exeToRun $argLine" + $errorMsg = "Process exited immediately with code ${exitCode}: ${exeToRun} ${argLine}" if ($LogFile) { Add-Content -Path $LogFile -Value "[ERROR] $errorMsg" } From 41d3a90d08d076291b23d2a869962c33b273f5a1 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 16:12:07 +0530 Subject: [PATCH 34/36] python fix --- .github/workflows/test-scripts.yml | 12 ++++++------ win/common-utils.ps1 | 30 +++++++++++++++++++++++++++++- win/env-setup-run.ps1 | 10 ++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index ecc2d60..7de5bef 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -16,7 +16,7 @@ jobs: name: Test mac/run.sh on macOS if: false runs-on: macos-latest - timeout-minutes: 8 + timeout-minutes: 15 environment: BrowserStack steps: - name: Checkout code @@ -103,8 +103,8 @@ jobs: # Test configurations test_configs=( - # "web java" - # "app java" + "web java" + "app java" "web python" "app python" "web nodejs" @@ -175,7 +175,7 @@ jobs: test-windows: name: Test win/run.ps1 on Windows runs-on: windows-latest - timeout-minutes: 15 + timeout-minutes: 8 environment: BrowserStack env: ACTIONS_STEP_DEBUG: true @@ -345,8 +345,8 @@ jobs: Write-Debug "Script path: $scriptPath" $testConfigs = @( - @("web", "java"), - @("app", "java"), + # @("web", "java"), + # @("app", "java"), @("web", "python"), @("app", "python"), @("web", "nodejs"), diff --git a/win/common-utils.ps1 b/win/common-utils.ps1 index fe59da7..cc611c8 100644 --- a/win/common-utils.ps1 +++ b/win/common-utils.ps1 @@ -130,7 +130,8 @@ function Set-BrowserStackPlatformsSection { param( [Parameter(Mandatory)][string]$RepoRoot, [Parameter(Mandatory)][string]$RelativeConfigPath, - [Parameter(Mandatory)][string]$PlatformsYaml + [Parameter(Mandatory)][string]$PlatformsYaml, + [switch]$IsWebTest # Flag to indicate if this is a web test (to remove app field) ) $configPath = Join-Path $RepoRoot $RelativeConfigPath @@ -140,6 +141,33 @@ function Set-BrowserStackPlatformsSection { Reset-BrowserStackConfigFile -RepoRoot $RepoRoot -RelativePath $RelativeConfigPath + # For web tests, remove any app field that might cause SDK to treat it as App Automate + if ($IsWebTest) { + $content = Get-Content -Path $configPath -Raw + if ($content) { + # Remove app: lines (with various indentation levels) + $lines = $content -split "`r?`n" + $filteredLines = @() + $skipNext = $false + foreach ($line in $lines) { + # Skip lines that are app: or app: + if ($line -match '^\s*app\s*:') { + $skipNext = $true + continue + } + # Skip continuation lines if we just skipped an app line + if ($skipNext -and ($line -match '^\s+[^-]' -or [string]::IsNullOrWhiteSpace($line))) { + continue + } + $skipNext = $false + $filteredLines += $line + } + $cleanedContent = $filteredLines -join "`r`n" + Set-ContentNoBom -Path $configPath -Value $cleanedContent + Log-Line "ā„¹ļø Removed any app field from $RelativeConfigPath for web test" $GLOBAL_LOG + } + } + $normalizedPlatforms = ($PlatformsYaml -replace "`r","").TrimEnd("`n") $blockBuilder = New-Object System.Text.StringBuilder [void]$blockBuilder.AppendLine("") diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 8e3cc29..73b88b8 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -30,9 +30,11 @@ function Setup-Web-Java { $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $localFlag = if ($UseLocal) { "true" } else { "false" } - Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms -IsWebTest $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + # Explicitly clear BROWSERSTACK_APP for web tests to prevent SDK from treating this as App Automate + Remove-Item Env:BROWSERSTACK_APP -ErrorAction SilentlyContinue $env:BSTACK_PARALLELS = $ParallelsPerPlatform $env:BROWSERSTACK_LOCAL = $localFlag $env:BSTACK_PLATFORMS = $platforms @@ -108,6 +110,8 @@ function Setup-Web-Python { $env:PATH = (Join-Path $venv 'Scripts') + ";" + $env:PATH $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + # Explicitly clear BROWSERSTACK_APP for web tests to prevent SDK from treating this as App Automate + Remove-Item Env:BROWSERSTACK_APP -ErrorAction SilentlyContinue if (Test-DomainPrivate) { $UseLocal = $true @@ -119,7 +123,7 @@ function Setup-Web-Python { $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $ParallelsPerPlatform $localFlag = if ($UseLocal) { "true" } else { "false" } - Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms + Set-BrowserStackPlatformsSection -RepoRoot $TARGET -RelativeConfigPath "browserstack.yml" -PlatformsYaml $platforms -IsWebTest $env:BSTACK_PARALLELS = $ParallelsPerPlatform $env:BSTACK_PLATFORMS = $platforms $env:BROWSERSTACK_LOCAL = $localFlag @@ -242,6 +246,8 @@ function Setup-Web-NodeJS { $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + # Explicitly clear BROWSERSTACK_APP for web tests to prevent SDK from treating this as App Automate + Remove-Item Env:BROWSERSTACK_APP -ErrorAction SilentlyContinue $localFlagStr = if ($UseLocal) { "true" } else { "false" } $env:BROWSERSTACK_LOCAL = $localFlagStr $env:BROWSERSTACK_BUILD_NAME = "now-$NOW_OS-web-nodejs-wdio" From 527e69e78608e82a86d4ff55a79cc71d16d74c76 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 16:24:14 +0530 Subject: [PATCH 35/36] python fix --- win/env-setup-run.ps1 | 55 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 73b88b8..8a8cf51 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -58,7 +58,7 @@ function Setup-Web-Java { Log-Line "āœ… Dependencies installed" $GLOBAL_LOG Print-TestsRunningSection -Command "mvn test -P sample-test" - $testTimeout = 300 + $testTimeout = 600 # 10 minutes to allow for test execution + cleanup Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG try { $exitCode = Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout @@ -157,7 +157,9 @@ function Setup-Web-Python { Print-TestsRunningSection -Command "browserstack-sdk pytest -s tests/bstack-sample-test.py" - $testTimeout = 300 + # Increased timeout to 600 seconds (10 minutes) to allow for test execution + cleanup + # Tests may complete but SDK hangs waiting for BrowserStack Local cleanup + $testTimeout = 600 Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG Log-Line "ā„¹ļø Working directory: $TARGET" $GLOBAL_LOG Log-Line "ā„¹ļø Log file: $LogFile" $GLOBAL_LOG @@ -199,9 +201,44 @@ function Setup-Web-Python { } if ($errorMsg -match "timed out") { - Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Check BrowserStack dashboard for test status." $GLOBAL_LOG - Log-Line "āš ļø This may indicate tests are still running on BrowserStack but the local process timed out." $GLOBAL_LOG - Log-Line "āš ļø Check the log file for any output from browserstack-sdk: $LogFile" $GLOBAL_LOG + Log-Line "āš ļø Test execution timed out after $testTimeout seconds. Checking if tests completed successfully..." $GLOBAL_LOG + + # Check if tests actually completed successfully despite timeout + if ($LogFile -and (Test-Path $LogFile)) { + $logContent = Get-Content -Path $LogFile -Raw + $passedMatches = ([regex]::Matches($logContent, '(\d+)\s+passed')).Count + $failedMatches = ([regex]::Matches($logContent, '(\d+)\s+failed')).Count + $errorMatches = ([regex]::Matches($logContent, '(\d+)\s+error')).Count + + if ($passedMatches -gt 0) { + $totalPassed = 0 + foreach ($match in ([regex]::Matches($logContent, '(\d+)\s+passed'))) { + $totalPassed += [int]$match.Groups[1].Value + } + + Log-Line "ā„¹ļø Found test completion indicators in logs: $passedMatches 'passed' message(s), total tests passed: $totalPassed" $GLOBAL_LOG + Log-Line "ā„¹ļø Tests appear to have completed successfully but SDK is hanging during cleanup." $GLOBAL_LOG + + # Check for BrowserStack Local processes that might be preventing cleanup + $bsLocalProcesses = Get-Process -Name "BrowserStackLocal" -ErrorAction SilentlyContinue + if ($bsLocalProcesses) { + Log-Line "āš ļø BrowserStack Local processes still running (PIDs: $($bsLocalProcesses.Id -join ', ')) - SDK may be waiting for them to shut down." $GLOBAL_LOG + Log-Line "ā„¹ļø This is a known issue where BrowserStack Local cleanup can hang. Tests completed successfully on BrowserStack." $GLOBAL_LOG + } + + Log-Line "āœ… Tests completed successfully on BrowserStack. Timeout occurred during SDK cleanup phase." $GLOBAL_LOG + Log-Line "ā„¹ļø Verify test results on BrowserStack dashboard: https://automation.browserstack.com/" $GLOBAL_LOG + + # Tests passed but SDK cleanup hung - this is a known issue + # We'll still throw but with a message indicating tests passed + $errorMsg = "Tests completed successfully but SDK cleanup timed out. Check BrowserStack dashboard for results." + } + } else { + Log-Line "āš ļø No test completion indicators found. Tests may still be running." $GLOBAL_LOG + } + } + + Log-Line "āš ļø Check the log file for details: $LogFile" $GLOBAL_LOG } throw } @@ -265,7 +302,7 @@ function Setup-Web-NodeJS { Log-Line " $caps" $GLOBAL_LOG Print-TestsRunningSection -Command "npm run test" - $testTimeout = 300 + $testTimeout = 600 # 10 minutes to allow for test execution + cleanup Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG try { $exitCode = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $TARGET -TimeoutSeconds $testTimeout @@ -350,7 +387,7 @@ function Setup-Mobile-Java { Log-Line "āœ… Dependencies installed" $GLOBAL_LOG Print-TestsRunningSection -Command "mvn test -P sample-test" - $testTimeout = 300 + $testTimeout = 600 # 10 minutes to allow for test execution + cleanup Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG try { $exitCode = Invoke-External -Exe $mvn -Arguments @("test","-P","sample-test") -LogFile $LogFile -WorkingDirectory (Get-Location).Path -TimeoutSeconds $testTimeout @@ -445,7 +482,7 @@ function Setup-Mobile-Python { $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" Print-TestsRunningSection -Command "cd $runDirName && browserstack-sdk pytest -s bstack_sample.py" - $testTimeout = 300 + $testTimeout = 600 # 10 minutes to allow for test execution + cleanup Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG Push-Location $runDir try { @@ -518,7 +555,7 @@ function Setup-Mobile-NodeJS { Log-Line "ā„¹ļø Platforms: $capsJson" $GLOBAL_LOG Print-TestsRunningSection -Command "npm run test" - $testTimeout = 300 + $testTimeout = 600 # 10 minutes to allow for test execution + cleanup Log-Line "ā„¹ļø Starting test execution with timeout of $testTimeout seconds..." $GLOBAL_LOG try { $exitCode = Invoke-External -Exe "cmd.exe" -Arguments @("/c","npm","run","test") -LogFile $LogFile -WorkingDirectory $testDir -TimeoutSeconds $testTimeout From eb683565f2087135b8a87cb05c2d0c08e19c526d Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Sat, 6 Dec 2025 16:34:56 +0530 Subject: [PATCH 36/36] python fix --- win/env-setup-run.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/win/env-setup-run.ps1 b/win/env-setup-run.ps1 index 8a8cf51..3061013 100644 --- a/win/env-setup-run.ps1 +++ b/win/env-setup-run.ps1 @@ -232,7 +232,6 @@ function Setup-Web-Python { # Tests passed but SDK cleanup hung - this is a known issue # We'll still throw but with a message indicating tests passed $errorMsg = "Tests completed successfully but SDK cleanup timed out. Check BrowserStack dashboard for results." - } } else { Log-Line "āš ļø No test completion indicators found. Tests may still be running." $GLOBAL_LOG } @@ -241,9 +240,9 @@ function Setup-Web-Python { Log-Line "āš ļø Check the log file for details: $LogFile" $GLOBAL_LOG } throw - } + } # End of inner catch block - } finally { + } finally { # End of outer try block, start of finally Pop-Location Set-Location (Join-Path $WORKSPACE_DIR $PROJECT_FOLDER) }