From d2504b4c5776e743f7a202b9483da659bf28872d Mon Sep 17 00:00:00 2001 From: zenithstack Date: Fri, 31 Oct 2025 16:21:27 +0530 Subject: [PATCH] updated .sh file --- mac.sh | 1778 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mac_os.sh | 885 -------------------------- 2 files changed, 1778 insertions(+), 885 deletions(-) create mode 100755 mac.sh delete mode 100644 mac_os.sh diff --git a/mac.sh b/mac.sh new file mode 100755 index 0000000..ebd7d29 --- /dev/null +++ b/mac.sh @@ -0,0 +1,1778 @@ +#!/bin/bash +set -o pipefail + +# ===== Global Variables ===== +WORKSPACE_DIR="$HOME/.browserstack" +PROJECT_FOLDER="NOW" + +BROWSERSTACK_USERNAME="" +BROWSERSTACK_ACCESS_KEY="" +TEST_TYPE="" # Web / App / Both +TECH_STACK="" # Java / Python / JS + +PARALLEL_PERCENTAGE=1.00 + +WEB_PLAN_FETCHED=false +MOBILE_PLAN_FETCHED=false +TEAM_PARALLELS_MAX_ALLOWED_WEB=0 +TEAM_PARALLELS_MAX_ALLOWED_MOBILE=0 + +# URL handling +DEFAULT_TEST_URL="https://bstackdemo.com" +CX_TEST_URL="$DEFAULT_TEST_URL" + +# Global vars +APP_URL="" +APP_PLATFORM="" # ios | android | all + + +# ===== Error Patterns ===== +WEB_SETUP_ERRORS=("") +WEB_LOCAL_ERRORS=("") + +MOBILE_SETUP_ERRORS=("") +MOBILE_LOCAL_ERRORS=("") + +# ===== Example Platform Templates (replace with your full lists if available) ===== +WEB_PLATFORM_TEMPLATES=( + "Windows|10|Chrome" + "Windows|10|Firefox" + "Windows|11|Edge" + "Windows|11|Chrome" + "Windows|8|Chrome" + "OS X|Monterey|Safari" + "OS X|Monterey|Chrome" + "OS X|Ventura|Chrome" + "OS X|Big Sur|Safari" + "OS X|Catalina|Firefox" +) + + +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" +) + +# Tier 2 – Up to 40 parallels +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" +) + +# Tier 3 – Up to 16 parallels +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" +) + +# Tier 4 – Up to 5 parallels +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=( + # Tier 1 + "ios|iPhone 15|17" + "ios|iPhone 15 Pro|17" + "ios|iPhone 16|18" + "android|Samsung Galaxy S25|15" + "android|Samsung Galaxy S24|14" + + # Tier 2 + "ios|iPhone 14 Pro|16" + "ios|iPhone 14|16" + "ios|iPad Air 13 2025|18" + "android|Samsung Galaxy S23|13" + "android|Samsung Galaxy S22|12" + "android|Samsung Galaxy S21|11" + "android|Samsung Galaxy Tab S10 Plus|15" + + # Tier 3 + "ios|iPhone 13 Pro Max|15" + "ios|iPhone 13|15" + "ios|iPhone 12 Pro|14" + "ios|iPhone 12 Pro|17" + "ios|iPhone 12|17" + "ios|iPhone 12|14" + "ios|iPhone 12 Pro Max|16" + "ios|iPhone 13 Pro|15" + "ios|iPhone 13 Mini|15" + "ios|iPhone 16 Pro|18" + "ios|iPad 9th|15" + "ios|iPad Pro 12.9 2020|14" + "ios|iPad Pro 12.9 2020|16" + "ios|iPad 8th|16" + "android|Samsung Galaxy S22 Ultra|12" + "android|Samsung Galaxy S21|12" + "android|Samsung Galaxy S21 Ultra|11" + "android|Samsung Galaxy S20|10" + "android|Samsung Galaxy M32|11" + "android|Samsung Galaxy 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" + + # Tier 4 + "ios|iPhone 15 Pro Max|17" + "ios|iPhone 15 Pro Max|26" + "ios|iPhone 15|26" + "ios|iPhone 15 Plus|17" + "ios|iPhone 14 Pro|26" + "ios|iPhone 14|18" + "ios|iPhone 14|26" + "ios|iPhone 13 Pro Max|18" + "ios|iPhone 13|16" + "ios|iPhone 13|17" + "ios|iPhone 13|18" + "ios|iPhone 12 Pro|18" + "ios|iPhone 14 Pro Max|16" + "ios|iPhone 14 Plus|16" + "ios|iPhone 11|13" + "ios|iPhone 8|11" + "ios|iPhone 7|10" + "ios|iPhone 17 Pro Max|26" + "ios|iPhone 17 Pro|26" + "ios|iPhone 17 Air|26" + "ios|iPhone 17|26" + "ios|iPhone 16e|18" + "ios|iPhone 16 Pro Max|18" + "ios|iPhone 16 Plus|18" + "ios|iPhone SE 2020|16" + "ios|iPhone SE 2022|15" + "ios|iPad Air 4|14" + "ios|iPad 9th|18" + "ios|iPad Air 5|26" + "ios|iPad Pro 11 2021|18" + "ios|iPad Pro 13 2024|17" + "ios|iPad Pro 12.9 2021|14" + "ios|iPad Pro 12.9 2021|17" + "ios|iPad Pro 11 2024|17" + "ios|iPad Air 6|17" + "ios|iPad Pro 12.9 2022|16" + "ios|iPad Pro 11 2022|16" + "ios|iPad 10th|16" + "ios|iPad Air 13 2025|26" + "ios|iPad Pro 11 2020|13" + "ios|iPad Pro 11 2020|16" + "ios|iPad 8th|14" + "ios|iPad Mini 2021|15" + "ios|iPad Pro 12.9 2018|12" + "ios|iPad 6th|11" + "android|Samsung Galaxy S23 Ultra|13" + "android|Samsung Galaxy S22 Plus|12" + "android|Samsung Galaxy S21 Plus|11" + "android|Samsung Galaxy S20 Ultra|10" + "android|Samsung Galaxy S25 Ultra|15" + "android|Samsung Galaxy S24 Ultra|14" + "android|Samsung Galaxy M52|11" + "android|Samsung Galaxy A52|11" + "android|Samsung Galaxy A51|10" + "android|Samsung Galaxy A11|10" + "android|Samsung Galaxy A10|9" + "android|Samsung Galaxy Tab A9 Plus|14" + "android|Samsung Galaxy Tab S9|13" + "android|Samsung Galaxy Tab S7|10" + "android|Samsung Galaxy Tab S7|11" + "android|Samsung Galaxy Tab S6|9" + "android|Google Pixel 9|16" + "android|Google Pixel 10 Pro XL|16" + "android|Google Pixel 10 Pro|16" + "android|Google Pixel 10|16" + "android|Google Pixel 9 Pro XL|15" + "android|Google Pixel 9 Pro|15" + "android|Google Pixel 6 Pro|12" + "android|Google Pixel 6 Pro|15" + "android|Google Pixel 8 Pro|14" + "android|Google Pixel 7 Pro|13" + "android|Google Pixel 5|11" + "android|OnePlus 13R|15" + "android|OnePlus 12R|14" + "android|OnePlus 11R|13" + "android|OnePlus 9|11" + "android|OnePlus 8|10" + "android|Motorola Moto G71 5G|11" + "android|Motorola Moto G9 Play|10" + "android|Vivo V21|11" + "android|Oppo A96|11" + "android|Oppo Reno 3 Pro|10" + "android|Xiaomi Redmi Note 11|11" + "android|Xiaomi Redmi Note 9|10" + "android|Huawei P30|9" +) + + + + +APP_URL="" +APP_PLATFORM="" # ios | android | all + + + +# ===== Log files (runtime only; created on first write) ===== +# ===== Log files (per-run) ===== +LOG_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/logs" +GLOBAL="$LOG_DIR/global.log" +WEB_LOG_FILE="$LOG_DIR/web_run_result.log" +MOBILE_LOG_FILE="$LOG_DIR/mobile_run_result.log" + +# Ensure log directory exists +mkdir -p "$LOG_DIR" + +# Clear old logs to start fresh +: > "$GLOBAL" +: > "$WEB_LOG_FILE" +: > "$MOBILE_LOG_FILE" + +# ===== Logging helper (runtime timestamped logging) ===== +# Usage: log_msg_to "message" "$DEST_FILE" (DEST_FILE optional; prints to console always) +log_msg_to() { + local message="$1" + local dest_file="$2" # optional + local ts + ts="$(date +"%Y-%m-%d %H:%M:%S")" + local line="[$ts] $message" + + # print to console + echo "$line" + + # write to dest file if provided + if [ -n "$dest_file" ]; then + mkdir -p "$(dirname "$dest_file")" + echo "$line" >> "$dest_file" + fi +} + +# ===== validate_prereqs shim (keeps compatibility with older code) ===== +validate_prereqs() { + # For backwards compatibility call validate_tech_stack_installed + validate_tech_stack_installed +} + +# ===== Functions: baseline interactions ===== +setup_workspace() { + local full_path="$WORKSPACE_DIR/$PROJECT_FOLDER" + if [ ! -d "$full_path" ]; then + mkdir -p "$full_path" + log_msg_to "✅ Created Onboarding workspace: $full_path" "$GLOBAL" + else + log_msg_to "ℹ️ Onboarding Workspace already exists: $full_path" "$GLOBAL" + fi +} + +ask_browserstack_credentials() { + # Prompt username + BROWSERSTACK_USERNAME=$(osascript -e 'Tell application "System Events" to display dialog "Enter your BrowserStack Username:" default answer "" with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + if [ -z "$BROWSERSTACK_USERNAME" ]; then + log_msg_to "❌ Username empty" "$GLOBAL" + exit 1 + fi + + # Prompt access key (hidden) + BROWSERSTACK_ACCESS_KEY=$(osascript -e 'Tell application "System Events" to display dialog "Enter your BrowserStack Access Key:" default answer "" with hidden answer with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + if [ -z "$BROWSERSTACK_ACCESS_KEY" ]; then + log_msg_to "❌ Access Key empty" "$GLOBAL" + exit 1 + fi + + log_msg_to "✅ BrowserStack credentials captured (access key hidden)" "$GLOBAL" +} + +# ask_test_type() { +# TEST_TYPE=$(osascript -e 'Tell application "System Events" to display dialog "Select testing type:" buttons {"Web", "App", "Both"} default button "Web" with title "Testing Type"' \ +# -e 'button returned of result') +# log_msg_to "✅ Selected Testing Type: $TEST_TYPE" "$GLOBAL" +# } + +ask_tech_stack() { + TECH_STACK=$(osascript -e 'Tell application "System Events" to display dialog "Select installed tech stack:" buttons {"Java", "Python"} default button "Java" with title "Tech Stack"' \ + -e 'button returned of result') + log_msg_to "✅ Selected Tech Stack: $TECH_STACK" "$GLOBAL" +} + +# ask_tech_stack() { +# TECH_STACK=$(osascript -e 'Tell application "System Events" to display dialog "Select installed tech stack:" buttons {"Java", "Python"} default button "Java" with title "Tech Stack"' \ +# -e 'button returned of result') +# log_msg_to "✅ Selected Tech Stack: $TECH_STACK" "$GLOBAL" +# } + + + +validate_tech_stack_installed() { + log_msg_to "ℹ️ Checking prerequisites for $TECH_STACK" "$GLOBAL" + + case "$TECH_STACK" in + Java) + log_msg_to "🔍 Checking if 'java' command exists..." "$GLOBAL" + if ! command -v java >/dev/null 2>&1; then + log_msg_to "❌ Java command not found in PATH." "$GLOBAL" + exit 1 + fi + + log_msg_to "🔍 Checking if Java runs correctly..." "$GLOBAL" + if ! JAVA_VERSION_OUTPUT=$(java -version 2>&1); then + log_msg_to "❌ Java exists but failed to run." "$GLOBAL" + exit 1 + fi + + log_msg_to "✅ Java is installed. Version details:" "$GLOBAL" + echo "$JAVA_VERSION_OUTPUT" | while read -r l; do log_msg_to " $l" "$GLOBAL"; done + ;; + Python) + log_msg_to "🔍 Checking if 'python3' command exists..." "$GLOBAL" + if ! command -v python3 >/dev/null 2>&1; then + log_msg_to "❌ Python3 command not found in PATH." "$GLOBAL" + exit 1 + fi + + log_msg_to "🔍 Checking if Python3 runs correctly..." "$GLOBAL" + if ! PYTHON_VERSION_OUTPUT=$(python3 --version 2>&1); then + log_msg_to "❌ Python3 exists but failed to run." "$GLOBAL" + exit 1 + fi + + log_msg_to "✅ Python3 is installed: $PYTHON_VERSION_OUTPUT" "$GLOBAL" + ;; + JS|JavaScript) + log_msg_to "🔍 Checking if 'node' command exists..." "$GLOBAL" + if ! command -v node >/dev/null 2>&1; then + log_msg_to "❌ Node.js command not found in PATH." "$GLOBAL" + exit 1 + fi + log_msg_to "🔍 Checking if 'npm' command exists..." "$GLOBAL" + if ! command -v npm >/dev/null 2>&1; then + log_msg_to "❌ npm command not found in PATH." "$GLOBAL" + exit 1 + fi + + log_msg_to "🔍 Checking if Node.js runs correctly..." "$GLOBAL" + if ! NODE_VERSION_OUTPUT=$(node -v 2>&1); then + log_msg_to "❌ Node.js exists but failed to run." "$GLOBAL" + exit 1 + fi + + log_msg_to "🔍 Checking if npm runs correctly..." "$GLOBAL" + if ! NPM_VERSION_OUTPUT=$(npm -v 2>&1); then + log_msg_to "❌ npm exists but failed to run." "$GLOBAL" + exit 1 + fi + + log_msg_to "✅ Node.js is installed: $NODE_VERSION_OUTPUT" "$GLOBAL" + log_msg_to "✅ npm is installed: $NPM_VERSION_OUTPUT" "$GLOBAL" + ;; + *) + log_msg_to "❌ Unknown tech stack selected: $TECH_STACK" "$GLOBAL" + exit 1 + ;; + esac + + log_msg_to "✅ Prerequisites validated for $TECH_STACK" "$GLOBAL" +} + +# ===== Ask user for test URL via UI prompt ===== +ask_user_for_test_url() { + CX_TEST_URL=$(osascript -e 'Tell application "System Events" to display dialog "Enter the URL you want to test with BrowserStack:\n(Leave blank for default: '"$DEFAULT_TEST_URL"')" default answer "" with title "Test URL Setup" buttons {"OK"} default button "OK"' \ + -e 'text returned of result') + + if [ -n "$CX_TEST_URL" ]; then + log_msg_to "🌐 Using custom test URL: $CX_TEST_URL" "$PRE_RUN_LOG_FILE" + else + CX_TEST_URL="$DEFAULT_TEST_URL" + log_msg_to "⚠️ No URL entered. Falling back to default: $CX_TEST_URL" "$PRE_RUN_LOG_FILE" + fi +} + +ask_and_upload_app() { + APP_FILE_PATH=$(osascript -e 'POSIX path of (choose file with prompt "📱 Please select your .apk or .ipa app file to upload to BrowserStack, If No App Selected then Defualt Browserstack app will be used automatically")') + + if [ -z "$APP_FILE_PATH" ]; then + log_msg_to "⚠️ No app selected. Using default sample app: bs://sample.app" "$GLOBAL" + APP_URL="bs://sample.app" + APP_PLATFORM="all" + return + fi + + # Detect platform + if [[ "$APP_FILE_PATH" == *.apk ]]; then + APP_PLATFORM="android" + elif [[ "$APP_FILE_PATH" == *.ipa ]]; then + APP_PLATFORM="ios" + else + log_msg_to "❌ Unsupported file type. Only .apk or .ipa allowed." "$GLOBAL" + exit 1 + fi + + # Upload app + log_msg_to "⬆️ Uploading $APP_FILE_PATH to BrowserStack..." "$GLOBAL" + UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$APP_FILE_PATH") + + APP_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) + + if [ -z "$APP_URL" ]; then + log_msg_to "❌ Upload failed. Response: $UPLOAD_RESPONSE" "$GLOBAL" + exit 1 + fi + + log_msg_to "✅ App uploaded successfully: $APP_URL" "$GLOBAL" +} + +ask_test_type() { + TEST_TYPE=$(osascript -e 'Tell application "System Events" to display dialog "Select testing type:" buttons {"Web", "App", "Both"} default button "Web" with title "Testing Type"' \ + -e 'button returned of result') + log_msg_to "✅ Selected Testing Type: $TEST_TYPE" "$GLOBAL" + + case "$TEST_TYPE" in + "Web") + ask_user_for_test_url + ;; + "App") + ask_and_upload_app + ;; + "Both") + ask_user_for_test_url + ask_and_upload_app + ;; + esac +} + + +# ===== Dynamic config generators ===== +generate_web_platforms_yaml() { + local max_total_parallels=$1 + local max + max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) + [ -z "$max" ] && max=0 + local yaml="" + local count=0 + + for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do + IFS="|" read -r os osVersion browserName <<< "$template" + for version in latest latest-1 latest-2; do + yaml+=" - os: $os + osVersion: $osVersion + browserName: $browserName + browserVersion: $version +" + count=$((count + 1)) + if [ "$count" -ge "$max" ]; then + echo "$yaml" + return + fi + done + done + + echo "$yaml" +} + +# generate_mobile_platforms_yaml() { +# local max_total_parallels=$1 +# local max +# max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) +# [ -z "$max" ] && max=0 +# local yaml="" +# local count=0 + +# for template in "${MOBILE_DEVICE_TEMPLATES[@]}"; do +# IFS="|" read -r platformName deviceName platformVersion <<< "$template" +# yaml+=" - platformName: $platformName +# deviceName: $deviceName +# platformVersion: '${platformVersion}.0' +# " +# count=$((count + 1)) +# if [ "$count" -ge "$max" ]; then +# echo "$yaml" +# return +# fi +# done + +# echo "$yaml" +# } + + + +# ask_and_upload_app() { +# APP_FILE_PATH=$(osascript -e 'POSIX path of (choose file with prompt "📱 Please select your .apk or .ipa app file to upload to BrowserStack")') + +# if [ -z "$APP_FILE_PATH" ]; then +# log_msg_to "⚠️ No app selected. Using default sample app: bs://sample.app" "$GLOBAL" +# APP_URL="bs://sample.app" +# APP_PLATFORM="all" +# return +# fi + +# # Detect platform +# if [[ "$APP_FILE_PATH" == *.apk ]]; then +# APP_PLATFORM="android" +# elif [[ "$APP_FILE_PATH" == *.ipa ]]; then +# APP_PLATFORM="ios" +# else +# log_msg_to "❌ Unsupported file type. Only .apk or .ipa allowed." "$GLOBAL" +# exit 1 +# fi + +# # Upload app +# log_msg_to "⬆️ Uploading $APP_FILE_PATH to BrowserStack..." "$GLOBAL" +# UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ +# -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ +# -F "file=@$APP_FILE_PATH") + +# APP_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4) + +# if [ -z "$APP_URL" ]; then +# log_msg_to "❌ Upload failed. Response: $UPLOAD_RESPONSE" "$GLOBAL" +# exit 1 +# fi + +# log_msg_to "✅ App uploaded successfully: $APP_URL" "$GLOBAL" +# } + +# generate_mobile_platforms_yaml() { +# local max_total_parallels=$1 +# local yaml="" +# local count=0 + +# # Select tier based on parallel count +# if (( max_total_parallels >= 80 )); then +# devices=("${MOBILE_TIER1[@]}") +# elif (( max_total_parallels >= 40 )); then +# devices=("${MOBILE_TIER2[@]}") +# elif (( max_total_parallels >= 16 )); then +# devices=("${MOBILE_TIER3[@]}") +# else +# devices=("${MOBILE_TIER4[@]}") +# fi + +# # Filter devices by platform and limit by max_total_parallels +# for template in "${devices[@]}"; do +# IFS="|" read -r platformName deviceName platformVersion <<< "$template" + +# # Skip if platform mismatch +# if [[ "$APP_PLATFORM" == "ios" && "$platformName" != "ios" ]]; then +# continue +# fi +# if [[ "$APP_PLATFORM" == "android" && "$platformName" != "android" ]]; then +# continue +# fi + +# yaml+=" - platformName: $platformName +# deviceName: $deviceName +# platformVersion: ${platformVersion}.0 +# " + +# count=$((count + 1)) +# if (( count >= max_total_parallels )); then +# break +# fi +# done + +# echo "$yaml" +# } + +generate_mobile_platforms_yaml() { + local max_total_parallels=$1 + local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) + + # fallback if bc result is empty or zero + if [ -z "$max" ] || [ "$max" -lt 1 ]; then + max=1 + fi + + local yaml="" + local count=0 + + for template in "${MOBILE_ALL[@]}"; do + IFS="|" read -r platformName deviceName platformVersion <<< "$template" + + # Apply platform filter + if [ -n "$APP_PLATFORM" ]; then + if [[ "$APP_PLATFORM" == "ios" && "$platformName" != "ios" ]]; then + continue + fi + if [[ "$APP_PLATFORM" == "android" && "$platformName" != "android" ]]; then + continue + fi + fi + + yaml+=" - platformName: $platformName + deviceName: $deviceName + platformVersion: '${platformVersion}.0' +" + count=$((count + 1)) + if [ "$count" -ge "$max" ]; then + echo "$yaml" + return + fi + done + + echo "$yaml" +} + + + + + +generate_web_caps_json() { + local max_total_parallels=$1 + local max + max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) + [ "$max" -lt 1 ] && max=1 # fallback to minimum 1 + + local json="" + local count=0 + + for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do + IFS="|" read -r os osVersion browserName <<< "$template" + for version in latest latest-1 latest-2; do + json+="{ + \"browserName\": \"$browserName\", + \"browserVersion\": \"$version\", + \"bstack:options\": { + \"os\": \"$os\", + \"osVersion\": \"$osVersion\" + } + }," + count=$((count + 1)) + if [ "$count" -ge "$max" ]; then + json="${json%,}" # strip trailing comma + echo "$json" + return + fi + done + done + + # Fallback in case not enough combinations + json="${json%,}" + echo "$json" +} + +generate_mobile_caps_json() { + local max_total=$1 + local count=0 + local usage_file="/tmp/device_usage.txt" + : > "$usage_file" + + local json="[" + for template in "${MOBILE_DEVICE_TEMPLATES[@]}"; do + IFS="|" read -r platformName deviceName baseVersion <<< "$template" + local usage + usage=$(grep -Fxc "$deviceName" "$usage_file") + + if [ "$usage" -ge 5 ]; then + continue + fi + + json="${json}{ + \"bstack:options\": { + \"deviceName\": \"${deviceName}\", + \"osVersion\": \"${baseVersion}.0\" + } + }," + + echo "$deviceName" >> "$usage_file" + count=$((count + 1)) + if [ "$count" -ge "$max_total" ]; then + break + fi + done + + json="${json%,}]" + echo "$json" + rm -f "$usage_file" +} + +# ===== Fetch plan details (writes to GLOBAL) ===== +fetch_plan_details() { + log_msg_to "ℹ️ Fetching BrowserStack Plan Details..." "$GLOBAL" + local web_unauthorized=false + local mobile_unauthorized=false + + if [[ "$TEST_TYPE" == "Web" || "$TEST_TYPE" == "Both" ]]; then + RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api.browserstack.com/automate/plan.json) + HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1) + RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d') + if [ "$HTTP_CODE_WEB" == "200" ]; then + WEB_PLAN_FETCHED=true + TEAM_PARALLELS_MAX_ALLOWED_WEB=$(echo "$RESPONSE_WEB_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') + log_msg_to "✅ Web Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_WEB" "$GLOBAL" + else + log_msg_to "❌ Web Testing Plan fetch failed ($HTTP_CODE_WEB)" "$GLOBAL" + [ "$HTTP_CODE_WEB" == "401" ] && web_unauthorized=true + fi + fi + + if [[ "$TEST_TYPE" == "App" || "$TEST_TYPE" == "Both" ]]; then + RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json) + HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1) + RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d') + if [ "$HTTP_CODE_MOBILE" == "200" ]; then + MOBILE_PLAN_FETCHED=true + TEAM_PARALLELS_MAX_ALLOWED_MOBILE=$(echo "$RESPONSE_MOBILE_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') + log_msg_to "✅ Mobile App Testing Plan fetched: Team max parallel sessions = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" "$GLOBAL" + else + log_msg_to "❌ Mobile App Testing Plan fetch failed ($HTTP_CODE_MOBILE)" "$GLOBAL" + [ "$HTTP_CODE_MOBILE" == "401" ] && mobile_unauthorized=true + fi + fi + + if [[ "$TEST_TYPE" == "Web" && "$web_unauthorized" == true ]] || \ + [[ "$TEST_TYPE" == "App" && "$mobile_unauthorized" == true ]] || \ + [[ "$TEST_TYPE" == "Both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then + log_msg_to "❌ Unauthorized to fetch required plan(s). Exiting." "$GLOBAL" + exit 1 + fi +} + + + +# setup_web_java_tested() { +# local local_flag=$1 +# local parallels=$2 +# local log_file=$3 + +# REPO="testng-browserstack" +# TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + +# mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + +# # Clone repo if not present +# if [ ! -d "$TARGET_DIR" ]; then +# log_msg_to "📦 Cloning repo $REPO into $TARGET_DIR" "$GLOBAL" +# git clone https://github.com/browserstack/testng-browserstack.git "$TARGET_DIR" +# else +# log_msg_to "📂 Repo $REPO already exists at $TARGET_DIR, skipping clone." "$GLOBAL" +# fi + +# cd "$TARGET_DIR" || return 1 + +# validate_prereqs || return 1 + +# # Export credentials for Maven to use +# export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" +# export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + +# # Run BrowserStack SDK archetype setup +# log_msg_to "⚙️ Setting up BrowserStack SDK project" "$GLOBAL" +# mvn archetype:generate -B \ +# -DarchetypeGroupId=com.browserstack \ +# -DarchetypeArtifactId=browserstack-sdk-archetype-setup \ +# -DarchetypeVersion=1.1 \ +# -DgroupId=com.browserstack \ +# -DartifactId=testng-browserstack \ +# -Dversion=1.0 \ +# -DBROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" \ +# -DBROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" \ +# -DBROWSERSTACK_PROJECT_REPO="$REPO" >> "$log_file" 2>&1 || true + +# # cd testng-browserstack || return 1 + +# # Update base URL in the Java code (replacing https://www.bstackdemo.com) +# sed -i.bak "s|https://www.bstackdemo.com|$CX_TEST_URL|g" $(grep -rl "https://www.bstackdemo.com" .) + +# # Log local flag status +# if [ "$local_flag" = "true" ]; then +# log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$GLOBAL" +# else +# log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." "$GLOBAL" +# fi + +# # Generate platforms YAML +# platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") + +# # Overwrite YAML config at the root level +# cat > browserstack.yml <> "$log_file" 2>&1 || true + +# cd "$WORKSPACE_DIR/$PROJECT_FOLDER" +# return 0 +# } + + +setup_web_java() { + local local_flag=$1 + local parallels=$2 + + REPO="testng-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + + # === 1️⃣ Clone Repo === + if [ ! -d "$TARGET_DIR" ]; then + log_msg_to "📦 Cloning repo $REPO into $TARGET_DIR" "$GLOBAL" "$WEB_LOG_FILE" + git clone https://github.com/browserstack/testng-browserstack.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true + else + log_msg_to "📂 Repo $REPO already exists at $TARGET_DIR, skipping clone." "$GLOBAL" "$WEB_LOG_FILE" + fi + + cd "$TARGET_DIR" || return 1 + validate_prereqs || return 1 + + # === 2️⃣ SDK Setup === + log_msg_to "⚙️ Setting up BrowserStack SDK project" "$GLOBAL" "$WEB_LOG_FILE" + mvn archetype:generate -B \ + -DarchetypeGroupId=com.browserstack \ + -DarchetypeArtifactId=browserstack-sdk-archetype-setup \ + -DarchetypeVersion=1.1 \ + -DgroupId=com.browserstack \ + -DartifactId=testng-browserstack \ + -Dversion=1.0 \ + -DBROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" \ + -DBROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" \ + -DBROWSERSTACK_PROJECT_REPO="$REPO" >> "$WEB_LOG_FILE" 2>&1 || true + + # cd testng-browserstack || return 1 + + # === 3️⃣ Update Base URL === + if grep -qr "https://www.bstackdemo.com" .; then + log_msg_to "🌐 Updating base URL to $CX_TEST_URL" "$GLOBAL" "$WEB_LOG_FILE" + sed -i.bak "s|https://www.bstackdemo.com|$CX_TEST_URL|g" $(grep -rl "https://www.bstackdemo.com" .) + fi + + # === 4️⃣ Local Flag === + if [ "$local_flag" = "true" ]; then + log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" + else + log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" + fi + + # === 5️⃣ YAML Setup === + log_msg_to "🧩 Generating YAML config (bstack.yml)" "$GLOBAL" "$WEB_LOG_FILE" + platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") + cat > browserstack.yml <> "$WEB_LOG_FILE" 2>&1 || true + + log_msg_to "🚀 Running 'mvn test -P sample-test'" "$GLOBAL" "$WEB_LOG_FILE" + mvn test -P sample-test >> "$WEB_LOG_FILE" 2>&1 || true + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" + return 0 +} + + + +setup_web_python() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="pytest-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + if [ ! -d "$TARGET_DIR" ]; then + git clone https://github.com/browserstack/$REPO.git "$TARGET_DIR" + log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$PRE_RUN_LOG_FILE" + else + log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" "$PRE_RUN_LOG_FILE" + fi + + cd "$TARGET_DIR" || return 1 + + validate_prereqs || return 1 + + # Setup Python venv + if [ ! -d "venv" ]; then + python3 -m venv venv + log_msg_to "✅ Created Python virtual environment" "$PRE_RUN_LOG_FILE" + fi + # shellcheck disable=SC1091 + source venv/bin/activate + pip install -r requirements.txt >> "$log_file" 2>&1 + + # Export credentials for pytest to use + export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" + export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + + # Update YAML at root level (browserstack.yml) + export BROWSERSTACK_CONFIG_FILE="browserstack.yml" + platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") + + cat > browserstack.yml < tests/bstack-sample-test.py <<'PYEOF' +import pytest + +def test_universal_browserstack_check(selenium): + + # 1. Navigate to the website + selenium.get('https://bstackdemo.com/') + + # 2. Use universal functions to get page data (no locators) + page_title = selenium.title + current_url = selenium.current_url + page_source = selenium.page_source + + # 3. Log the captured data to BrowserStack using 'annotate' + # This will appear in the "Text Logs" section of your Automate session + log_data = f"Page Title: {page_title} | Current URL: {current_url}" + selenium.execute_script( + 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' + ) + + # 4. Perform simple, locator-free assertions + assert len(page_source) > 100 # Checks that the page has content + + # 5. Set the final session status to 'passed' + selenium.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "Page loaded and title verified!"}}' + ) +PYEOF + + # Update base URL in the new sample test + # sed -i.bak "s|https://bstackdemo.com/|$CX_TEST_URL|g" tests/bstack-sample-test.py || true + sed -i.bak "s|https://bstackdemo.com|$CX_TEST_URL|g" tests/bstack-sample-test.py || true + log_msg_to "🌐 Updated base URL in tests/bstack-sample-test.py to: $CX_TEST_URL" "$PRE_RUN_LOG_FILE" + + # Run tests + log_msg_to "⚠️ Running tests with local=$local_flag" "$PRE_RUN_LOG_FILE" + browserstack-sdk pytest -s tests/bstack-sample-test.py >> "$log_file" 2>&1 || true + + # Copy first 200 lines of logs for visibility + [ -f "$log_file" ] && sed -n '1,200p' "$log_file" | while read -r l; do + log_msg_to "web (py): $l" "$PRE_RUN_LOG_FILE" + done + + return 0 +} + + + + +setup_web_js() { + local local_flag=$1 + local parallels=$2 + + REPO="webdriverio-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" + + # === 1️⃣ Clone Repo === + if [ ! -d "$TARGET_DIR" ]; then + log_msg_to "📦 Cloning repo $REPO (branch tra) into $TARGET_DIR" "$GLOBAL" "$WEB_LOG_FILE" + git clone -b tra https://github.com/browserstack/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true + else + log_msg_to "📂 Repo $REPO already exists at $TARGET_DIR, skipping clone." "$GLOBAL" "$WEB_LOG_FILE" + fi + + cd "$TARGET_DIR" || return 1 + validate_prereqs || return 1 + + # === 2️⃣ Install Dependencies === + log_msg_to "⚙️ Running 'npm install'" "$GLOBAL" "$WEB_LOG_FILE" + npm install >> "$WEB_LOG_FILE" 2>&1 || true + + # # === 3️⃣ Update Base URL === + # if grep -qr "https://www.bstackdemo.com" .; then + # log_msg_to "🌐 Updating base URL to $CX_TEST_URL" "$GLOBAL" "$WEB_LOG_FILE" + # sed -i.bak "s|https://www.bstackdemo.com|$CX_TEST_URL|g" $(grep -rl "https://www.bstackdemo.com" .) + # fi + + # === 4️⃣ Generate Capabilities JSON === + log_msg_to "🧩 Generating browser/OS capabilities" "$GLOBAL" "$WEB_LOG_FILE" + local caps_json + caps_json=$(generate_web_caps_json "$parallels") + + # === 5️⃣ Determine buildIdentifier based on local === + if [ "$local_flag" = true ]; then + BUILD_ID="#${BUILD_NUMBER}-local" + else + BUILD_ID="#${BUILD_NUMBER}-remote" + fi + + cat > conf/base.conf.js < tests/specs/test.js < { + it("add product to cart", async () => { + await browser.url("$CX_TEST_URL"); + + await browser.waitUntil( + async () => (await browser.getTitle()).match(/StackDemo/i), + { timeout: 5000, timeoutMsg: "Title didn't match with BrowserStack" } + ); + + await browser.waitUntil( + async () => (await productInCart.getText()).match(productOnScreenText), + { timeout: 5000 } + ); + }); +}); +EOF + + + + # === 6️⃣ Create conf/test.conf.js using template === +log_msg_to "🛠️ Creating conf/test.conf.js configuration file" "$GLOBAL" "$WEB_LOG_FILE" + +if [ "$local_flag" = true ]; then + # BUILD_ID="#${BUILD_NUMBER}-localOn" + cat > conf/test.conf.js < conf/test.conf.js <> "$WEB_LOG_FILE" 2>&1 || true + + # === 9️⃣ Wrap Up === + log_msg_to "✅ Web JS setup and test execution completed successfully." "$GLOBAL" "$WEB_LOG_FILE" + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" + return 0 +} + + + + + +# ===== Web wrapper with retry logic (writes runtime logs to WEB_LOG_FILE) ===== +setup_web() { + log_msg_to "Starting Web setup for $TECH_STACK" "$WEB_LOG_FILE" + + local local_flag=true + local attempt=1 + local success=false + local log_file="$WEB_LOG_FILE" + # don't pre-create; file will be created on first write by log_msg_to or command output redirection + + local total_parallels + total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) + [ -z "$total_parallels" ] && total_parallels=1 + local parallels_per_platform + # parallels_per_platform=$(( (total_parallels + 1) / 2 )) + parallels_per_platform=$total_parallels + + + while [ "$attempt" -le 2 ]; do + log_msg_to "[Web Setup Attempt $attempt] browserstackLocal: $local_flag" "$WEB_LOG_FILE" + case "$TECH_STACK" in + Java) setup_web_java "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" ;; + Python) setup_web_python "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" ;; + JS|JavaScript) setup_web_js "$local_flag" "$parallels_per_platform" "$WEB_LOG_FILE" ;; + *) log_msg_to "Unknown TECH_STACK: $TECH_STACK" "$WEB_LOG_FILE"; return 1 ;; + esac + + LOG_CONTENT=$(<"$WEB_LOG_FILE" 2>/dev/null || true) + LOCAL_FAILURE=false + SETUP_FAILURE=false + + for pattern in "${WEB_LOCAL_ERRORS[@]}"; do + echo "$LOG_CONTENT" | grep -qiE "$pattern" && LOCAL_FAILURE=true && break + done + + for pattern in "${WEB_SETUP_ERRORS[@]}"; do + echo "$LOG_CONTENT" | grep -qiE "$pattern" && SETUP_FAILURE=true && break + done + + if echo "$LOG_CONTENT" | grep -qiE "https://[a-zA-Z0-9./?=_-]*browserstack\.com"; then + success=true + fi + + if [ "$success" = true ]; then + log_msg_to "✅ Web setup succeeded" "$WEB_LOG_FILE" + break + elif [ "$LOCAL_FAILURE" = true ] && [ "$attempt" -eq 1 ]; then + local_flag=false + attempt=$((attempt + 1)) + log_msg_to "⚠️ Web test failed due to Local tunnel error. Retrying without browserstackLocal..." "$WEB_LOG_FILE" + elif [ "$SETUP_FAILURE" = true ]; then + log_msg_to "❌ Web test failed due to setup error. Check logs at: $WEB_LOG_FILE" "$WEB_LOG_FILE" + break + else + log_msg_to "❌ Web setup ended without success; check $WEB_LOG_FILE for details" "$WEB_LOG_FILE" + break + fi + done +} + + + + +setup_mobile_python() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="pytest-appium-app-browserstack" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + # Clone repo if not present + if [ ! -d "$TARGET_DIR" ]; then + git clone https://github.com/browserstack/$REPO.git "$TARGET_DIR" + log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$PRE_RUN_LOG_FILE" + else + log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" "$PRE_RUN_LOG_FILE" + fi + + cd "$TARGET_DIR" || return 1 + + # Create & activate venv (if not exists) + if [ ! -d "venv" ]; then + python3 -m venv venv + fi + # shellcheck disable=SC1091 + source venv/bin/activate + + # Install dependencies + pip install -r requirements.txt >> "$log_file" 2>&1 + + # Export credentials + export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" + export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + + # Prepare platform-specific YAMLs in android/ and ios/ + local original_platform="$APP_PLATFORM" + + APP_PLATFORM="android" + local platform_yaml_android + platform_yaml_android=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") + cat > android/browserstack.yml < ios/browserstack.yml < android/bstack_sample.py <<'PYEOF' +import pytest + + +@pytest.mark.usefixtures('setWebdriver') +class TestUniversalAppCheck: + + def test_app_health_check(self): + + # 1. Get initial app and device state (no locators) + initial_package = self.driver.current_package + initial_activity = self.driver.current_activity + initial_orientation = self.driver.orientation + + # 2. Log the captured data to BrowserStack using 'annotate' + log_data = f"Initial State: Package='{initial_package}', Activity='{initial_activity}', Orientation='{initial_orientation}'" + self.driver.execute_script( + 'browserstack_executor: {"action": "annotate", "arguments": {"data": "' + log_data + '", "level": "info"}}' + ) + + # 3. Perform a locator-free action: change device orientation + self.driver.orientation = 'LANDSCAPE' + + # 4. Perform locator-free assertions + assert self.driver.orientation == 'LANDSCAPE' + + # 5. Log the successful state change + self.driver.execute_script( + 'browserstack_executor: {"action": "annotate", "arguments": {"data": "Successfully changed orientation to LANDSCAPE", "level": "info"}}' + ) + + # 6. Set the final session status to 'passed' + self.driver.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "App state verified and orientation changed!"}}' + ) +PYEOF + + cp android/bstack_sample.py ios/bstack_sample.py + + # Decide which directory to run based on APP_PLATFORM (default to android) + local run_dir="android" + if [ "$APP_PLATFORM" = "ios" ]; then + run_dir="ios" + fi + + # Log local flag status + if [ "$local_flag" = "true" ]; then + log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$PRE_RUN_LOG_FILE" + else + log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." "$PRE_RUN_LOG_FILE" + fi + + # Run pytest with BrowserStack SDK from the chosen platform directory + log_msg_to "🚀 Running 'cd $run_dir && browserstack-sdk pytest -s bstack_sample.py'" "$PRE_RUN_LOG_FILE" + ( + cd "$run_dir" && browserstack-sdk pytest -s bstack_sample.py >> "$log_file" 2>&1 || true + ) + + # Copy first 200 lines of logs for visibility + [ -f "$log_file" ] && sed -n '1,200p' "$log_file" | while read -r l; do + log_msg_to "mobile (python): $l" "$PRE_RUN_LOG_FILE" + done + + deactivate + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" + return 0 +} + + +setup_mobile_java() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="browserstack-examples-appium-testng" + TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + + if [ ! -d "$TARGET_DIR" ]; then + git clone https://github.com/BrowserStackCE/$REPO.git "$TARGET_DIR" + log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$GLOBAL" "$MOBILE_LOG_FILE" + else + log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" "$GLOBAL" "$MOBILE_LOG_FILE" + fi + + # Update pom.xml → browserstack-java-sdk version to LATEST + pom_file="$TARGET_DIR/pom.xml" + if [ -f "$pom_file" ]; then + sed -i.bak '/browserstack-java-sdk<\/artifactId>/,/<\/dependency>/ s|.*|LATEST|' "$pom_file" + log_msg_to "🔧 Updated browserstack-java-sdk version to LATEST in pom.xml" "$GLOBAL" "$MOBILE_LOG_FILE" + fi + + cd "$TARGET_DIR" || return 1 + validate_prereqs || return 1 + + # Export credentials for Maven + export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" + export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + + # Update TestBase.java → switch AppiumDriver to AndroidDriver + testbase_file=$(find src -name "TestBase.java" | head -n 1) + if [ -f "$testbase_file" ]; then + sed -i.bak 's/new AppiumDriver(/new AndroidDriver(/g' "$testbase_file" + log_msg_to "🔧 Updated driver initialization in $testbase_file to use AndroidDriver" "$GLOBAL" "$MOBILE_LOG_FILE" + fi + + # YAML config path + export BROWSERSTACK_CONFIG_FILE="src/test/resources/conf/capabilities/browserstack-parallel.yml" + platform_yaml=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") + + cat > "$BROWSERSTACK_CONFIG_FILE" < "/src/test/java/com/browserstack/test/suites/e2e/OrderTest.java" <> "$log_file" 2>&1 || true + + # Then run actual test suite + log_msg_to "🚀 Running 'mvn clean test -P bstack-parallel -Dtest=OrderTest'" "$GLOBAL" "$MOBILE_LOG_FILE" + mvn clean test -P bstack-parallel -Dtest=OrderTest >> "$log_file" 2>&1 || true + + # Copy first 200 lines of logs for visibility + [ -f "$log_file" ] && sed -n '1,200p' "$log_file" | while read -r l; do + log_msg_to "mobile (java): $l" "$GLOBAL" "$MOBILE_LOG_FILE" + done + + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" + return 0 +} + + + + +setup_mobile_js() { + local local_flag=$1 + local parallels=$2 + local log_file=$3 + + REPO="webdriverio-appium-app-browserstack" + if [ ! -d "$REPO" ]; then + git clone -b sdk https://github.com/browserstack/$REPO + fi + cd "$REPO/android/" || return 1 + + validate_prereqs || return 1 + npm install >> "$log_file" 2>&1 || true + cd "examples/run-parallel-test" || return 1 + caps_file="parallel.conf.js" + + if sed --version >/dev/null 2>&1; then + sed -i "s/\(maxInstances:\)[[:space:]]*[0-9]\+/\1 $parallels/" "$caps_file" || true + else + sed -i '' "s/\(maxInstances:\)[[:space:]]*[0-9]\+/\1 $parallels/" "$caps_file" || true + fi + + caps_json=$(generate_mobile_caps_json "$parallels") + printf "%s\n" "capabilities: $caps_json," > "$caps_file".tmp || true + mv "$caps_file".tmp "$caps_file" || true + + export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" + export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + + npm run parallel > "$log_file" 2>&1 || true + [ -f "$log_file" ] && sed -n '1,200p' "$log_file" | while read -r l; do log_msg_to "mobile: $l" "$GLOBAL"; done + return 0 +} + +# ===== Mobile wrapper with retry logic (writes runtime logs to MOBILE_LOG_FILE) ===== +setup_mobile() { + log_msg_to "Starting Mobile setup for $TECH_STACK" "$MOBILE_LOG_FILE" + + local local_flag=true + local attempt=1 + local success=false + local log_file="$MOBILE_LOG_FILE" + + local total_parallels + total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) + [ -z "$total_parallels" ] && total_parallels=1 + local parallels_per_platform + # parallels_per_platform=$(( (total_parallels + 2) / 3 )) + parallels_per_platform=$total_parallels + + while [ "$attempt" -le 2 ]; do + log_msg_to "[Mobile Setup Attempt $attempt] browserstackLocal: $local_flag" "$MOBILE_LOG_FILE" + case "$TECH_STACK" in + Java) setup_mobile_java "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" ;; + Python) setup_mobile_python "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" ;; + JS|JavaScript) setup_mobile_js "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" ;; + *) log_msg_to "Unknown TECH_STACK: $TECH_STACK" "$MOBILE_LOG_FILE"; return 1 ;; + esac + + LOG_CONTENT=$(<"$MOBILE_LOG_FILE" 2>/dev/null || true) + LOCAL_FAILURE=false + SETUP_FAILURE=false + + for pattern in "${MOBILE_LOCAL_ERRORS[@]}"; do + echo "$LOG_CONTENT" | grep -qiE "$pattern" && LOCAL_FAILURE=true && break + done + + for pattern in "${MOBILE_SETUP_ERRORS[@]}"; do + echo "$LOG_CONTENT" | grep -qiE "$pattern" && SETUP_FAILURE=true && break + done + + if echo "$LOG_CONTENT" | grep -qiE "https://[a-zA-Z0-9./?=_-]*browserstack\.com"; then + success=true + fi + + if [ "$success" = true ]; then + log_msg_to "✅ Mobile setup succeeded" "$MOBILE_LOG_FILE" + break + elif [ "$LOCAL_FAILURE" = true ] && [ "$attempt" -eq 1 ]; then + local_flag=false + attempt=$((attempt + 1)) + log_msg_to "⚠️ Mobile test failed due to Local tunnel error. Retrying without browserstackLocal..." "$MOBILE_LOG_FILE" + elif [ "$SETUP_FAILURE" = true ]; then + log_msg_to "❌ Mobile test failed due to setup error. Check logs at: $log_file" "$MOBILE_LOG_FILE" + break + else + log_msg_to "❌ Mobile setup ended without success; check $MOBILE_LOG_FILE for details" "$MOBILE_LOG_FILE" + break + fi + done +} + +# ===== Orchestration: decide what to run based on TEST_TYPE and plan fetch ===== +run_setup() { + log_msg_to "Orchestration: TEST_TYPE=$TEST_TYPE, WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED, MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED" "$GLOBAL" + + case "$TEST_TYPE" in + Web) + if [ "$WEB_PLAN_FETCHED" == true ]; then + setup_web + else + log_msg_to "⚠️ Skipping Web setup — Web plan not fetched" "$GLOBAL" + fi + ;; + App) + if [ "$MOBILE_PLAN_FETCHED" == true ]; then + setup_mobile + else + log_msg_to "⚠️ Skipping Mobile setup — Mobile plan not fetched" "$GLOBAL" + fi + ;; + Both) + local ran_any=false + if [ "$WEB_PLAN_FETCHED" == true ]; then + setup_web + ran_any=true + else + log_msg_to "⚠️ Skipping Web setup — Web plan not fetched" "$GLOBAL" + fi + if [ "$MOBILE_PLAN_FETCHED" == true ]; then + setup_mobile + ran_any=true + else + log_msg_to "⚠️ Skipping Mobile setup — Mobile plan not fetched" "$GLOBAL" + fi + if [ "$ran_any" == false ]; then + log_msg_to "❌ Both Web and Mobile setup were skipped. Exiting." "$GLOBAL" + exit 1 + fi + ;; + *) + log_msg_to "❌ Invalid TEST_TYPE: $TEST_TYPE" "$GLOBAL" + exit 1 + ;; + esac +} + +# ===== Main flow (baseline steps then run) ===== +setup_workspace +ask_browserstack_credentials +ask_test_type +ask_tech_stack +validate_tech_stack_installed +# ask_user_for_test_url +fetch_plan_details + +# Plan summary in pre-run log +log_msg_to "Plan summary: WEB_PLAN_FETCHED=$WEB_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_WEB), MOBILE_PLAN_FETCHED=$MOBILE_PLAN_FETCHED (team max=$TEAM_PARALLELS_MAX_ALLOWED_MOBILE)" "$GLOBAL" + +# Run actual setup(s) +run_setup + +# End +log_msg_to "Setup run finished" "$GLOBAL" \ No newline at end of file diff --git a/mac_os.sh b/mac_os.sh deleted file mode 100644 index e64d175..0000000 --- a/mac_os.sh +++ /dev/null @@ -1,885 +0,0 @@ -#!/bin/bash - -# Define colors and symbols -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color -TICK="${GREEN}✅${NC}" -CROSS="${RED}❌${NC}" - -SHOW_LOGS=false -if [[ "$1" == "--zlogs" ]]; then - SHOW_LOGS=true -fi - - - -BSS_ROOT="$HOME/.browserstack" -BSS_SETUP_DIR="$BSS_ROOT/browserstackSampleSetup" -LOG_FILE="$BSS_SETUP_DIR/bstackOnboardingLogs.log" -> "$LOG_FILE" # Clear previous logs - - - -log() { - local message="$1" - local timestamp - timestamp=$(date "+%Y-%m-%d %H:%M:%S") - echo -e "[$timestamp] $message" >> "$LOG_FILE" -} - - -print_checker() { - echo -e "$1" -} - - -PARALLEL_PERCENTAGE=1.00 - - -############################################# -# Step 1: Workspace Setup -############################################# -setup_workspace() { - mkdir -p "$BSS_SETUP_DIR" && print_checker "$TICK Setup directory ensured at $BSS_SETUP_DIR" - cd "$BSS_SETUP_DIR" || { print_checker "$CROSS Failed to navigate to setup directory"; exit 1; } - : > "$LOG_FILE" - log "[Workspace initialized]" -} - -############################################# -# Step 2: Prompt for BrowserStack Credentials -############################################# -prompt_credentials() { - BS_USERNAME=$(osascript -e 'Tell application "System Events" to display dialog "Enter your BrowserStack Username:" default answer "" with title "BrowserStack Setup" buttons {"OK"} default button "OK"' -e 'text returned of result') - if [ -z "$BS_USERNAME" ]; then - print_checker "$CROSS Username empty" - exit 1 - fi - - BS_ACCESS_KEY=$(osascript -e 'Tell application "System Events" to display dialog "Enter your BrowserStack Access Key:" default answer "" with hidden answer with title "BrowserStack Setup" buttons {"OK"} default button "OK"' -e 'text returned of result') - if [ -z "$BS_ACCESS_KEY" ]; then - print_checker "$CROSS Access Key empty" - exit 1 - fi - - log "[Credentials entered]" -} - -############################################# -# Step 3: Prompt for Test Type and Tech Stack -############################################# -# prompt_test_options() { -# TEST_OPTION=$(osascript -e 'Tell application "System Events" to choose from list {"Web Testing", "Mobile App Testing", "Both"} with prompt "Select testing type:" default items "Web Testing"') -# [ "$TEST_OPTION" == "false" ] && print_checker "$CROSS No test option selected" && exit 1 -# TEST_OPTION=$(echo "$TEST_OPTION" | sed 's/^[{(]*//;s/[})]*$//') - -# TECH_STACK=$(osascript -e 'Tell application "System Events" to choose from list {"Java", "Python", "JavaScript"} with prompt "Select your tech stack:" default items "Java"') -# [ "$TECH_STACK" == "false" ] && print_checker "$CROSS No tech stack selected" && exit 1 -# TECH_STACK=$(echo "$TECH_STACK" | sed 's/^[{(]*//;s/[})]*$//') - -# log "[User selected: $TEST_OPTION | $TECH_STACK]" -# } - - -prompt_test_options() { - TEST_OPTION=$(osascript -e 'Tell application "System Events" to choose from list {"Web Testing", "Mobile App Testing", "Both"} with prompt "Select testing type:" default items "Web Testing"') - [ "$TEST_OPTION" == "false" ] && print_checker "$CROSS No test option selected" && exit 1 - TEST_OPTION=$(echo "$TEST_OPTION" | sed 's/^[{(]*//;s/[})]*$//') - - if [ "$TEST_OPTION" == "Mobile App Testing" ]; then - TECH_STACK=$(osascript -e 'Tell application "System Events" to choose from list {"Java", "Python"} with prompt "Select your tech stack (Mobile supports Java & Python only):" default items "Java"') - else - TECH_STACK=$(osascript -e 'Tell application "System Events" to choose from list {"Java", "Python", "JavaScript"} with prompt "Select your tech stack:" default items "Java"') - fi - - [ "$TECH_STACK" == "false" ] && print_checker "$CROSS No tech stack selected" && exit 1 - TECH_STACK=$(echo "$TECH_STACK" | sed 's/^[{(]*//;s/[})]*$//') - - log "[User selected: $TEST_OPTION | $TECH_STACK]" -} - -############################################# -# Step 4: Validate Required Tools -############################################# -############################################# -# Prerequisite Validation (strict blocking) -############################################# -validate_prereqs() { - print_checker "$INFO Checking prerequisites for $TECH_STACK..." - print_checker "📂 Checking in current working directory: $(pwd)" - - case "$TECH_STACK" in - Java) - if ! command -v java >/dev/null 2>&1; then - print_checker "$CROSS Java is not installed or not available in this environment.\n❗ Please ensure Java is installed and accessible in your PATH." - exit 1 - fi - JAVA_VERSION=$(java -version 2>&1) - print_checker "$TICK Java is installed. Version details:\n$JAVA_VERSION" - ;; - - Python) - if ! command -v python3 >/dev/null 2>&1; then - print_checker "$CROSS Python3 is not installed or not available in this environment.\n❗ Please ensure Python3 is installed and accessible in your PATH." - exit 1 - fi - PYTHON_VERSION=$(python3 --version 2>&1) - print_checker "$TICK Python3 is installed: $PYTHON_VERSION" - ;; - - JavaScript) - if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then - print_checker "$CROSS Node.js or npm is not installed or not available in this environment.\n❗ Please ensure both Node.js and npm are installed and accessible in your PATH." - exit 1 - fi - NODE_VERSION=$(node -v) - NPM_VERSION=$(npm -v) - print_checker "$TICK Node.js is installed: $NODE_VERSION" - print_checker "$TICK npm is installed: $NPM_VERSION" - ;; - esac - - print_checker "$TICK prerequisites validated for $TECH_STACK" -} - - - - -############################################# -# Step 5: Fetch Plan Details -############################################# - fetch_plan_details() { - log "[Fetching Plan Details]" - - WEB_PLAN_FETCHED=false - MOBILE_PLAN_FETCHED=false - - web_unauthorized=false - mobile_unauthorized=false - - if [[ "$TEST_OPTION" == "Web Testing" || "$TEST_OPTION" == "Both" ]]; then - RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BS_USERNAME:$BS_ACCESS_KEY" https://api.browserstack.com/automate/plan.json) - HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1) - RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d') - - if [ "$HTTP_CODE_WEB" == "200" ]; then - WEB_PLAN_FETCHED=true - TEAM_PARALLELS_MAX_ALLOWED_WEB=$(echo "$RESPONSE_WEB_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') - log "$RESPONSE_WEB_BODY" - print_checker "$TICK Web Testing Plan fetched: Max Parallels = $TEAM_PARALLELS_MAX_ALLOWED_WEB" - else - log "$RESPONSE_WEB_BODY" - print_checker "$CROSS Web Testing Plan fetch failed ($HTTP_CODE_WEB)" - if [ "$HTTP_CODE_WEB" == "401" ]; then - print_checker "${RED}⚠️ Check your credentials or Web Testing access.${NC}" - web_unauthorized=true - fi - fi - fi - - if [[ "$TEST_OPTION" == "Mobile App Testing" || "$TEST_OPTION" == "Both" ]]; then - RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BS_USERNAME:$BS_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json) - HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1) - RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d') - - if [ "$HTTP_CODE_MOBILE" == "200" ]; then - MOBILE_PLAN_FETCHED=true - TEAM_PARALLELS_MAX_ALLOWED_MOBILE=$(echo "$RESPONSE_MOBILE_BODY" | grep -o '"parallel_sessions_max_allowed":[0-9]*' | grep -o '[0-9]*') - log "$RESPONSE_MOBILE_BODY" - print_checker "$TICK Mobile App Testing Plan fetched: Max Parallels = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" - else - log "$RESPONSE_MOBILE_BODY" - print_checker "$CROSS Mobile App Testing Plan fetch failed ($HTTP_CODE_MOBILE)" - if [ "$HTTP_CODE_MOBILE" == "401" ]; then - print_checker "${RED}⚠️ Check your credentials or Mobile Testing access.${NC}" - mobile_unauthorized=true - fi - fi - fi - - # Exit Logic - if [[ "$TEST_OPTION" == "Web Testing" && "$web_unauthorized" == true ]]; then - exit 1 - elif [[ "$TEST_OPTION" == "Mobile App Testing" && "$mobile_unauthorized" == true ]]; then - exit 1 - elif [[ "$TEST_OPTION" == "Both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then - exit 1 - fi -} - - - - -PARALLEL_PERCENTAGE=1.00 - -############################################# -# Predefined Platform Templates -############################################# - -WEB_PLATFORM_TEMPLATES=( - "Windows|10|Chrome" - "Windows|10|Firefox" - "Windows|11|Edge" - "Windows|11|Chrome" - "Windows|8|Chrome" - "OS X|Monterey|Safari" - "OS X|Monterey|Chrome" - "OS X|Ventura|Chrome" - "OS X|Big Sur|Safari" - "OS X|Catalina|Firefox" -) - -MOBILE_DEVICE_TEMPLATES=( - # Samsung - "android|Samsung Galaxy S21|11" - "android|Samsung Galaxy S25|15" - "android|Samsung Galaxy S24|14" - "android|Samsung Galaxy S22|12" - "android|Samsung Galaxy S23|13" - "android|Samsung Galaxy S21|12" - "android|Samsung Galaxy Tab S10 Plus|15" - "android|Samsung Galaxy S22 Ultra|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 S9|8" - "android|Samsung Galaxy Tab S8|12" - "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 S8|7" - "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" - - # Google Pixel - "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|Google Pixel 3|9" - "android|Google Pixel 9|16" - "android|Google Pixel 6 Pro|12" - "android|Google Pixel 6 Pro|15" - "android|Google Pixel 9 Pro XL|15" - "android|Google Pixel 9 Pro|15" - "android|Google Pixel 8 Pro|14" - "android|Google Pixel 7 Pro|13" - "android|Google Pixel 5|11" - "android|Google Pixel 5|12" - "android|Google Pixel 4 XL|10" - - # Vivo - "android|Vivo Y21|11" - "android|Vivo Y50|10" - "android|Vivo V30|14" - "android|Vivo V21|11" - - # Oppo - "android|Oppo Reno 6|11" - "android|Oppo Reno 8T 5G|13" - "android|Oppo A96|11" - "android|Oppo Reno 3 Pro|10" - - # Realme - "android|Realme 8|11" - - # Motorola - "android|Motorola Moto G71 5G|11" - "android|Motorola Moto G9 Play|10" - "android|Motorola Moto G7 Play|9" - - # OnePlus - "android|OnePlus 12R|14" - "android|OnePlus 11R|13" - "android|OnePlus 9|11" - "android|OnePlus 8|10" - - # Xiaomi - "android|Xiaomi Redmi Note 13 Pro 5G|14" - "android|Xiaomi Redmi Note 12 4G|13" - "android|Xiaomi Redmi Note 11|11" - "android|Xiaomi Redmi Note 9|10" - "android|Xiaomi Redmi Note 8|9" - - # Huawei - "android|Huawei P30|9" -) - - - -############################################# -# Dynamic YAML Generators with Permutations -############################################# - -generate_web_platforms_yaml() { - local max_total_parallels=$1 - local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - local yaml="" - local count=0 - - for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do - IFS="|" read -r os osVersion browserName <<< "$template" - for version in latest latest-1 latest-2; do - yaml+=" - os: $os - osVersion: $osVersion - browserName: $browserName - browserVersion: $version -" - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - echo "$yaml" - return - fi - done - done - - echo "$yaml" -} - -generate_mobile_platforms_yaml() { - local max_total_parallels=$1 - local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - local yaml="" - local count=0 - - for template in "${MOBILE_DEVICE_TEMPLATES[@]}"; do - IFS="|" read -r platformName deviceName platformVersion <<< "$template" - yaml+=" - platformName: $platformName - deviceName: $deviceName - platformVersion: '${platformVersion}.0' -" - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - echo "$yaml" - return - fi - done - - echo "$yaml" -} - -generate_web_caps_json() { - local max_total_parallels=$1 - local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - [ "$max" -lt 1 ] && max=1 # fallback to minimum 1 - - local json="" - local count=0 - - for template in "${WEB_PLATFORM_TEMPLATES[@]}"; do - IFS="|" read -r os osVersion browserName <<< "$template" - for version in latest latest-1 latest-2; do - json+="{ - \"browserName\": \"$browserName\", - \"browserVersion\": \"$version\", - \"bstack:options\": { - \"os\": \"$os\", - \"osVersion\": \"$osVersion\" - } - }," - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - json="${json%,}" # strip trailing comma - echo "$json" - return - fi - done - done - - # Fallback in case not enough combinations - json="${json%,}" - echo "$json" -} - - -generate_mobile_caps_json() { - local max_total_parallels=$1 - local max=$(echo "$max_total_parallels * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - local json="" - local count=0 - - for template in "${MOBILE_DEVICE_TEMPLATES[@]}"; do - IFS="|" read -r _ deviceName baseVersion <<< "$template" - for delta in 0 -1; do - version=$(echo "$baseVersion + $delta" | bc) - json+="{ - \"device\": \"$deviceName\", - \"os_version\": \"$version.0\" - }," - count=$((count + 1)) - if [ "$count" -ge "$max" ]; then - json="${json%,}" - echo "$json" - return - fi - done - done - - json="${json%,}" - echo "$json" -} - - -############################################# -# Step 6: Setup Web with retry on Local true -############################################# - -# Web Errors -WEB_SETUP_ERRORS=("Error" "Build failed" "Session not created" "Cannot start test") -WEB_LOCAL_ERRORS=("Execution of" "browserstack local failed" "fail local testing" "failed to connect tunnel") - -setup_web_java() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="testng-browserstack" - [ ! -d "$REPO" ] && git clone https://github.com/browserstack/$REPO - cd "$REPO" || return - - validate_prereqs || return 1 - platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") - - cat > browserstack.yml < "$log_file" 2>&1 - cat "$log_file" >> "$LOG_FILE" -} - -setup_web_python() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="python-selenium-browserstack" - [ ! -d "$REPO" ] && git clone https://github.com/browserstack/$REPO - cd "$REPO" || return - - validate_prereqs || return 1 - python3 -m venv env && source env/bin/activate - pip3 install -r requirements.txt >> "$LOG_FILE" 2>&1 - platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") - - cat > browserstack.yml < "$log_file" 2>&1 - cat "$log_file" >> "$LOG_FILE" -} - - - -setup_web_js() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="webdriverio-browserstack" - [ ! -d "$REPO" ] && git clone https://github.com/browserstack/$REPO - cd "$REPO" || return - - validate_prereqs || return 1 - npm install >> "$LOG_FILE" 2>&1 - - local caps_file - if [ "$local_flag" = true ]; then - caps_file="conf/local-test.conf.js" - - # Insert maxInstances + commonCapabilities after 'const localConfig = {' - perl -i -pe "s|const localConfig = \{\n|const localConfig = {\n maxInstances: $TEAM_PARALLELS_MAX_ALLOWED_WEB,\n commonCapabilities: {\n 'bstack:options': {\n projectName: 'webdriverio-browserstack',\n buildName: 'browserstack build',\n buildIdentifier: '#\${BUILD_NUMBER}',\n source: 'webdriverio:sample-master:v1.2'\n }\n },\n|;" "$caps_file" - - echo "exports.config.capabilities.forEach(function (caps) {" >> "$caps_file" - echo " for (var i in exports.config.commonCapabilities)" >> "$caps_file" - echo " caps[i] = { ...caps[i], ...exports.config.commonCapabilities[i]};" >> "$caps_file" - echo "});" >> "$caps_file" - - else - caps_file="conf/test.conf.js" - - # Replace maxInstances normally - sed -i '' "s/\(maxInstances:\)[[:space:]]*[0-9]\+/\1 $TEAM_PARALLELS_MAX_ALLOWED_WEB/" "$caps_file" - fi - - # Generate capabilities - local caps_json caps_js - caps_json=$(generate_web_caps_json "$TEAM_PARALLELS_MAX_ALLOWED_WEB") - caps_js="[${caps_json}]" - - # Replace capabilities: [ ... ] block (multiline safe) - perl -0777 -i -pe "s/capabilities:\s*\[(.*?)\]/capabilities: $caps_js/s" "$caps_file" - - export BROWSERSTACK_USERNAME="$BS_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BS_ACCESS_KEY" - export BROWSERSTACK_LOCAL=$local_flag - - if [ "$local_flag" = true ]; then - npm run local > "$log_file" 2>&1 - else - npm run test > "$log_file" 2>&1 - fi - - cat "$log_file" >> "$LOG_FILE" -} - - - - - -setup_web() { - log "\n${BOLD}${BLUE}[Starting Web Setup for $TECH_STACK]${NC}" - local local_flag=true - local attempt=1 - local success=false - local run_result_log="$BSS_SETUP_DIR/web_run_result.log" - mkdir -p "$(dirname "$run_result_log")" - local run_result_log_path - run_result_log_path="$(realpath "$run_result_log")" - rm -f "$run_result_log" - - total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - parallels_per_platform=$(( (total_parallels + 1) / 2 )) - - while [ "$attempt" -le 2 ]; do - if [ "$SHOW_LOGS" = false ]; then - echo -e "\n${YELLOW}[⏳ Please hold on while we prepare the next step in the background...]${NC}" - fi - - log "[Web Setup Attempt $attempt] browserstackLocal: $local_flag" - - case "$TECH_STACK" in - Java) setup_web_java "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - Python) setup_web_python "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - JavaScript) setup_web_js "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - esac - - LOG_CONTENT=$(<"$run_result_log") - LOCAL_FAILURE=false - SETUP_FAILURE=false - - for pattern in "${WEB_LOCAL_ERRORS[@]}"; do - echo "$LOG_CONTENT" | grep -qiE "$pattern" && LOCAL_FAILURE=true && break - done - - for pattern in "${WEB_SETUP_ERRORS[@]}"; do - echo "$LOG_CONTENT" | grep -qiE "$pattern" && SETUP_FAILURE=true && break - done - - if echo "$LOG_CONTENT" | grep -qiE "https://[a-zA-Z0-9./?=_-]*browserstack\.com"; then - success=true - fi - - if [ "$success" = true ]; then - break - elif [ "$LOCAL_FAILURE" = true ] && [ "$attempt" -eq 1 ]; then - local_flag=false - attempt=$((attempt + 1)) - print_checker "$CROSS Web test failed due to Local tunnel error.\n🔁 Retrying web test without browserstackLocal..." - cd "$BSS_SETUP_DIR" - elif [ "$SETUP_FAILURE" = true ]; then - print_checker "$CROSS Web test failed due to setup error. Check logs at:\n📄 $run_result_log_path" - break - else - break - fi - done - - if [ "$success" = true ]; then - BUILD_URL=$(grep -Eo "https://[a-zA-Z0-9./?=_-]*browserstack\.com[a-zA-Z0-9./?=_-]*" "$LOG_FILE" | tail -n 1) - if [ -n "$BUILD_URL" ]; then - print_checker "$TICK Web test run completed. View your tests here:\n👉 $BUILD_URL" - else - print_checker "$TICK Web test run completed. Visit your BrowserStack dashboard to view results." - fi - else - print_checker "$CROSS Final Web setup failed.\nCheck logs at: $run_result_log_path\nIf the issue persists, retry or contact: support@browserstack.com" - fi -} - - - - - -############################################# -# Step 7: Setup Mobile with retry on Local true -############################################# - -# Mobile Errors -MOBILE_SETUP_ERRORS=("") -MOBILE_LOCAL_ERRORS=("tunnel connection error") - -setup_mobile_java() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="testng-appium-app-browserstack" - [ ! -d "$REPO" ] && git clone -b master https://github.com/browserstack/$REPO - cd "$REPO" || return - - validate_prereqs || return 1 - platform_yaml=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") - - cat > android/testng-examples/browserstack.yml < "$log_file" 2>&1 - cat "$log_file" >> "$LOG_FILE" -} - -setup_mobile_python() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="python-appium-app-browserstack" - [ ! -d "$REPO" ] && git clone https://github.com/browserstack/$REPO - cd "$REPO" || return - - validate_prereqs || return 1 - python3 -m venv env && source env/bin/activate - pip3 install -r requirements.txt && cd android >> "$LOG_FILE" 2>&1 - platform_yaml=$(generate_mobile_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") - - platform_yaml - - cat > browserstack.yml < "$log_file" 2>&1 - cat "$log_file" >> "$LOG_FILE" -} - -# setup_mobile_js() { -# local_flag=$1 -# parallels=$2 -# log_file=$3 - -# REPO="webdriverio-appium-app-browserstack" -# [ ! -d "$REPO" ] && git clone -b sdk https://github.com/browserstack/$REPO -# cd "$REPO/android" || return - -# validate_prereqs || return 1 -# npm install >> "$LOG_FILE" 2>&1 - -# export BROWSERSTACK_USERNAME="$BS_USERNAME" -# export BROWSERSTACK_ACCESS_KEY="$BS_ACCESS_KEY" -# export BROWSERSTACK_LOCAL=$local_flag - -# if [ "$local_flag" = true ]; then -# npm run local > "$log_file" 2>&1 -# else -# npm run test > "$log_file" 2>&1 -# fi - -# cat "$log_file" >> "$LOG_FILE" -# } - -setup_mobile_js() { - local_flag=$1 - parallels=$2 - log_file=$3 - - REPO="webdriverio-appium-app-browserstack" - [ ! -d "$REPO" ] && git clone -b sdk https://github.com/browserstack/$REPO - cd "$REPO/android/examples/run-parallel-test" || return - - validate_prereqs || return 1 - npm install >> "$LOG_FILE" 2>&1 - - local caps_json - caps_json=$(generate_mobile_caps_json "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE") - local conf_file="parallel.conf.js" - - jq ".maxInstances = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE | .capabilities = [$caps_json]" "$conf_file" > tmp.json && mv tmp.json "$conf_file" - - export BROWSERSTACK_USERNAME="$BS_USERNAME" - export BROWSERSTACK_ACCESS_KEY="$BS_ACCESS_KEY" - export BROWSERSTACK_LOCAL=$local_flag - - cd "../../" || return - ls - - if [ "$local_flag" = true ]; then - npm run local > "$log_file" 2>&1 - else - npm run parallel > "$log_file" 2>&1 - fi - - cat "$log_file" >> "$LOG_FILE" -} - - - -setup_mobile() { - log "\n${BOLD}${BLUE}[Starting Mobile Setup for $TECH_STACK]${NC}" - local local_flag=true - local attempt=1 - local success=false - local run_result_log="$BSS_SETUP_DIR/mobile_run_result.log" - mkdir -p "$(dirname "$run_result_log")" - local run_result_log_path - run_result_log_path="$(realpath "$run_result_log")" - rm -f "$run_result_log" - - - total_parallels=$(echo "$TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE" | bc | cut -d'.' -f1) - parallels_per_platform=$(( (total_parallels + 2) / 3 )) - - while [ "$attempt" -le 2 ]; do - if [ "$SHOW_LOGS" = false ]; then - echo -e "\n${YELLOW}[⏳ Please hold on while we prepare the next step in the background...]${NC}" - fi - - log "[Mobile Setup Attempt $attempt] browserstackLocal: $local_flag" - - case "$TECH_STACK" in - Java) setup_mobile_java "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - Python) setup_mobile_python "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - JavaScript) setup_mobile_js "$local_flag" "$parallels_per_platform" "$run_result_log" ;; - esac - - LOG_CONTENT=$(<"$run_result_log") - LOCAL_FAILURE=false - SETUP_FAILURE=false - - for pattern in "${MOBILE_LOCAL_ERRORS[@]}"; do - echo "$LOG_CONTENT" | grep -qiE "$pattern" && LOCAL_FAILURE=true && break - done - - for pattern in "${MOBILE_SETUP_ERRORS[@]}"; do - echo "$LOG_CONTENT" | grep -qiE "$pattern" && SETUP_FAILURE=true && break - done - - if echo "$LOG_CONTENT" | grep -qiE "https://[a-zA-Z0-9./?=_-]*browserstack\.com"; then - success=true - fi - - if [ "$success" = true ]; then - break - elif [ "$LOCAL_FAILURE" = true ] && [ "$attempt" -eq 1 ]; then - local_flag=false - attempt=$((attempt + 1)) - print_checker "$CROSS Mobile test failed due to Local tunnel error.\n🔁 Retrying mobile test without browserstackLocal..." - cd "$BSS_SETUP_DIR" - elif [ "$SETUP_FAILURE" = true ]; then - print_checker "$CROSS Mobile test failed due to setup error. Check logs at:\n📄 $run_result_log_path" - break - else - break - fi - done - - if [ "$success" = true ]; then - BUILD_URL=$(grep -Eo "https://[a-zA-Z0-9./?=_-]*browserstack\.com[a-zA-Z0-9./?=_-]*" "$LOG_FILE" | tail -n 1) - if [ -n "$BUILD_URL" ]; then - print_checker "$TICK Mobile test run completed. View your tests here:\n👉 $BUILD_URL" - else - print_checker "$TICK Mobile test run completed. Visit your BrowserStack dashboard to view results." - fi - else - print_checker "$CROSS Final Mobile setup failed.\nCheck logs at: $run_result_log_path\nIf the issue persists, retry or contact: support@browserstack.com" - fi -} - - - - - - -############################################# -# Step 8: Execute -############################################# -run_setup() { - if [ "$TEST_OPTION" == "Web Testing" ]; then - setup_web - - elif [ "$TEST_OPTION" == "Mobile App Testing" ]; then - setup_mobile - - elif [ "$TEST_OPTION" == "Both" ]; then - local ran_any=false - - if [ "$WEB_PLAN_FETCHED" == true ]; then - setup_web - ran_any=true - else - print_checker "${YELLOW}⚠️ Skipping Web setup as Web Testing plan was not fetched.${NC}" - fi - - if [ "$MOBILE_PLAN_FETCHED" == true ]; then - setup_mobile - ran_any=true - else - print_checker "${YELLOW}⚠️ Skipping Mobile setup as Mobile App Testing plan was not fetched.${NC}" - fi - - if [ "$ran_any" == false ]; then - print_checker "${RED}❌ Both Web and Mobile setup were skipped. Exiting.${NC}" - exit 1 - fi - fi -} - - -# Main Execution Flow - -setup_workspace -prompt_credentials -prompt_test_options -validate_prereqs -fetch_plan_details -run_setup