From edbd2700d96dceeafbc9ff085c839bd1de201a88 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Mon, 3 Nov 2025 12:37:30 +0530 Subject: [PATCH 1/8] first cut --- README.md | 2 +- mac.sh | 589 ++++++++++++------------------------------------------ 2 files changed, 131 insertions(+), 460 deletions(-) diff --git a/README.md b/README.md index 64fdc8a..b864b04 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ You can either run the script directly from the web or clone the repository and **macOS / Linux** ```bash - bash mac_os.sh + bash mac.sh ``` **Windows** diff --git a/mac.sh b/mac.sh index ebd7d29..1df1e6a 100755 --- a/mac.sh +++ b/mac.sh @@ -40,157 +40,14 @@ WEB_PLATFORM_TEMPLATES=( "Windows|11|Edge" "Windows|11|Chrome" "Windows|8|Chrome" - "OS X|Monterey|Safari" + #"OS X|Monterey|Safari" "OS X|Monterey|Chrome" "OS X|Ventura|Chrome" - "OS X|Big Sur|Safari" + #"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" @@ -352,6 +209,7 @@ mkdir -p "$LOG_DIR" : > "$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() { @@ -371,6 +229,21 @@ log_msg_to() { fi } +# Spinner function +show_spinner() { + local pid=$1 + local spin='|/-\' + local i=0 + local ts + ts="$(date +"%Y-%m-%d %H:%M:%S")" + while kill -0 "$pid" 2>/dev/null; do + i=$(( (i+1) %4 )) + printf "\r[$ts] ⏳ Processing... ${spin:$i:1}" + sleep 0.1 + done + log_msg_to "✅ Done!" +} + # ===== validate_prereqs shim (keeps compatibility with older code) ===== validate_prereqs() { # For backwards compatibility call validate_tech_stack_installed @@ -390,7 +263,7 @@ setup_workspace() { 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"' \ + BROWSERSTACK_USERNAME=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Username.\n\nNote: Locate it in your BrowserStack account profile page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ -e 'text returned of result') if [ -z "$BROWSERSTACK_USERNAME" ]; then log_msg_to "❌ Username empty" "$GLOBAL" @@ -398,7 +271,7 @@ ask_browserstack_credentials() { 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"' \ + BROWSERSTACK_ACCESS_KEY=$(osascript -e 'Tell application "System Events" to display dialog "Please enter your BrowserStack Access Key.\n\nNote: Locate it in your BrowserStack account page.\nhttps://www.browserstack.com/accounts/profile/details" default answer "" with hidden answer with title "BrowserStack Setup" buttons {"OK"} default button "OK"' \ -e 'text returned of result') if [ -z "$BROWSERSTACK_ACCESS_KEY" ]; then log_msg_to "❌ Access Key empty" "$GLOBAL" @@ -408,26 +281,13 @@ ask_browserstack_credentials() { 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"' \ + TECH_STACK=$(osascript -e 'Tell application "System Events" to display dialog "Select installed tech stack:" buttons {"Java", "Python", "NodeJS"} default button "Java" with title "Testing Framework Technology Stack"' \ -e 'button returned of result') log_msg_to "✅ Selected Tech Stack: $TECH_STACK" "$GLOBAL" } -# 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" @@ -463,7 +323,7 @@ validate_tech_stack_installed() { log_msg_to "✅ Python3 is installed: $PYTHON_VERSION_OUTPUT" "$GLOBAL" ;; - JS|JavaScript) + NodeJS) 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" @@ -596,109 +456,6 @@ generate_web_platforms_yaml() { 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 @@ -740,9 +497,6 @@ generate_mobile_platforms_yaml() { } - - - generate_web_caps_json() { local max_total_parallels=$1 local max @@ -854,91 +608,45 @@ fetch_plan_details() { fi } +# Function to check if IP is private +is_private_ip() { + case $1 in + 10.* | 192.168.* | 172.16.* | 172.17.* | 172.18.* | 172.19.* | \ + 172.20.* | 172.21.* | 172.22.* | 172.23.* | 172.24.* | 172.25.* | \ + 172.26.* | 172.27.* | 172.28.* | 172.29.* | 172.30.* | 172.31.* | "") + return 0 ;; # Private + *) + return 1 ;; # Public + esac +} +is_domain_private() { + domain=${CX_TEST_URL#*://} # remove protocol + domain=${domain%%/*} # remove everything after first "/" + log_msg_to "Website domain: $domain" + export NOW_WEB_DOMAIN="$domain" -# 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 -# } +# Resolve domain using Cloudflare DNS +IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1) + +# Determine if domain is private +if is_private_ip "$IP_ADDRESS"; then + is_cx_domain_private=0 +else + is_cx_domain_private=-1 +fi + +log_msg_to "Resolved IPs: $IP_ADDRESS" + +return $is_cx_domain_private +} setup_web_java() { local local_flag=$1 local parallels=$2 - REPO="testng-browserstack" + REPO="now-testng-browserstack" TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" @@ -946,28 +654,14 @@ setup_web_java() { # === 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 + git clone https://github.com/browserstackCE/now-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 + # 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 @@ -975,11 +669,15 @@ setup_web_java() { sed -i.bak "s|https://www.bstackdemo.com|$CX_TEST_URL|g" $(grep -rl "https://www.bstackdemo.com" .) fi + if is_domain_private; then + local_flag=true + fi + # === 4️⃣ Local Flag === if [ "$local_flag" = "true" ]; then - log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" + 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" + log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" fi # === 5️⃣ YAML Setup === @@ -990,7 +688,7 @@ userName: $BROWSERSTACK_USERNAME accessKey: $BROWSERSTACK_ACCESS_KEY framework: testng browserstackLocal: $local_flag -buildName: browserstack-sample-java-web +buildName: now-testng-java-web projectName: NOW-Web-Test percy: true accessibility: true @@ -999,46 +697,51 @@ $platform_yaml parallelsPerPlatform: $parallels EOF + + # === 6️⃣ Build and Run === log_msg_to "⚙️ Running 'mvn install -DskipTests'" "$GLOBAL" "$WEB_LOG_FILE" mvn install -DskipTests >> "$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 + log_msg_to "🚀 Running 'mvn test -P sample-test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$GLOBAL" "$WEB_LOG_FILE" + mvn test -P sample-test >> "$WEB_LOG_FILE" 2>&1 & + cmd_pid=$!|| true + + show_spinner "$cmd_pid" + wait "$cmd_pid" cd "$WORKSPACE_DIR/$PROJECT_FOLDER" return 0 } - - setup_web_python() { local local_flag=$1 local parallels=$2 local log_file=$3 - REPO="pytest-browserstack" + REPO="now-pytest-browserstack" TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" if [ ! -d "$TARGET_DIR" ]; then - git clone https://github.com/browserstack/$REPO.git "$TARGET_DIR" + git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$PRE_RUN_LOG_FILE" else - log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" "$PRE_RUN_LOG_FILE" + log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" fi cd "$TARGET_DIR" || return 1 - validate_prereqs || 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" + log_msg_to "✅ Created Python virtual environment" fi + # shellcheck disable=SC1091 source venv/bin/activate - pip install -r requirements.txt >> "$log_file" 2>&1 + pip3 install -r requirements.txt >> "$WEB_LOG_FILE" 2>&1 # Export credentials for pytest to use export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" @@ -1048,6 +751,17 @@ setup_web_python() { export BROWSERSTACK_CONFIG_FILE="browserstack.yml" platform_yaml=$(generate_web_platforms_yaml "$TEAM_PARALLELS_MAX_ALLOWED_WEB") + if is_domain_private; then + local_flag=true + fi + + # === 4️⃣ Local Flag === + if [ "$local_flag" = "true" ]; then + log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" + else + log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$GLOBAL" "$WEB_LOG_FILE" + fi + 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 + log_msg_to "✅ Updated root-level browserstack.yml with platforms and credentials." - # 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" + log_msg_to "🌐 Updated base URL in tests/bstack-sample-test.py to: $CX_TEST_URL" + + log_msg_to "🚀 Running 'browserstack-sdk pytest -s tests/bstack-sample-test.py'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$GLOBAL" "$WEB_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 + browserstack-sdk pytest -s tests/bstack-sample-test.py >> "$WEB_LOG_FILE" 2>&1 & + cmd_pid=$!|| 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 + show_spinner "$cmd_pid" + wait "$cmd_pid" + cd "$WORKSPACE_DIR/$PROJECT_FOLDER" return 0 } - - setup_web_js() { local local_flag=$1 local parallels=$2 @@ -1138,11 +822,6 @@ setup_web_js() { 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" @@ -1253,7 +932,6 @@ exports.config.capabilities.forEach(function (caps) { EOF else - # BUILD_ID="#${BUILD_NUMBER}-localOff" cat > conf/test.conf.js </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 + if (grep -qiE "BUILD FAILURE" "$WEB_LOG_FILE"); then + success=false fi if [ "$success" = true ]; then - log_msg_to "✅ Web setup succeeded" "$WEB_LOG_FILE" + 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 @@ -1374,8 +1043,6 @@ setup_web() { } - - setup_mobile_python() { local local_flag=$1 local parallels=$2 @@ -1493,6 +1160,10 @@ PYEOF run_dir="ios" fi + if is_domain_private; then + local_flag=true + fi + # Log local flag status if [ "$local_flag" = "true" ]; then log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$PRE_RUN_LOG_FILE" @@ -1595,11 +1266,15 @@ public class OrderTest extends TestBase { } EOF + if is_domain_private; then + local_flag=true + fi + # Log local flag status if [ "$local_flag" = "true" ]; then - log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$GLOBAL" "$MOBILE_LOG_FILE" + log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$GLOBAL" "$MOBILE_LOG_FILE" else - log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." "$GLOBAL" "$MOBILE_LOG_FILE" + log_msg_to "✅ BrowserStack Local is DISABLED for this run." "$GLOBAL" "$MOBILE_LOG_FILE" fi # Run Maven install first @@ -1620,8 +1295,6 @@ EOF } - - setup_mobile_js() { local local_flag=$1 local parallels=$2 @@ -1672,12 +1345,12 @@ setup_mobile() { # parallels_per_platform=$(( (total_parallels + 2) / 3 )) parallels_per_platform=$total_parallels - while [ "$attempt" -le 2 ]; do + while [ "$attempt" -le 1 ]; 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" ;; + NodeJS) 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 @@ -1771,8 +1444,6 @@ 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 +log_msg_to "Setup run finished." "$GLOBAL" \ No newline at end of file From 15bfbac80edcd4eec1cc03b33a7b36641fb46056 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Mon, 3 Nov 2025 19:52:06 +0530 Subject: [PATCH 2/8] java sample test done --- README.md | 21 +++++++++++++++++++-- mac.sh | 26 ++++---------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b864b04..0a3ff03 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This utility automates the entire first-mile experience — from credential inpu | 🪵 Logging for Debugging | Saves all raw logs under `~/.browserstack/bstack_onboarding.log` for support and debugging purposes. | | 🖥️ (Planned) UI Framework Picker | Allows customers to select their preferred test framework via an interactive UI. | -## How to Use +## How to Use? You can either run the script directly from the web or clone the repository and run it locally. @@ -24,7 +24,7 @@ You can either run the script directly from the web or clone the repository and 1. Clone the repository: ```bash - git clone https://github.com/http-heading/browserstack-now.git + git clone https://github.com/BrowserStackCE/browserstack-now.git ``` 2. Navigate to the directory: ```bash @@ -60,4 +60,21 @@ To run the onboarding utility on Windows without cloning, execute the following ```powershell iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/http-heading/browserstack-now/main/windows.ps1')) ``` + +## Identifying and sharing the log files + +The NOW framework creates the log files in this folder. To seek assistance from the BrowserStack team after running this Github repository, please share a zip of the logs with the BrowserStack team in toucn with you. + +### NOW Framework Logs + +#### macOS / Linux +``` +$HOME/.browserstack/NOW/logs ``` + +#### Windows +``` +$HOME/.browserstack/NOW/logs +``` + + diff --git a/mac.sh b/mac.sh index 1df1e6a..6048835 100755 --- a/mac.sh +++ b/mac.sh @@ -624,7 +624,7 @@ is_domain_private() { domain=${CX_TEST_URL#*://} # remove protocol domain=${domain%%/*} # remove everything after first "/" log_msg_to "Website domain: $domain" - export NOW_WEB_DOMAIN="$domain" + export NOW_WEB_DOMAIN="$CX_TEST_URL" # Resolve domain using Cloudflare DNS IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1) @@ -650,14 +650,11 @@ setup_web_java() { TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" - + rm -rf $TARGET_DIR # === 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/browserstackCE/now-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 @@ -722,12 +719,9 @@ setup_web_python() { REPO="now-pytest-browserstack" TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" - if [ ! -d "$TARGET_DIR" ]; then git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$PRE_RUN_LOG_FILE" - else - log_msg_to "ℹ️ Repository already exists at: $TARGET_DIR (skipping clone)" - fi + cd "$TARGET_DIR" || return 1 @@ -808,12 +802,8 @@ setup_web_js() { 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 @@ -1052,12 +1042,8 @@ setup_mobile_python() { 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 @@ -1196,12 +1182,8 @@ setup_mobile_java() { 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" From 4444bb33a5665bb54261a8bb2884f1cf3f666e16 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Tue, 4 Nov 2025 01:25:39 +0530 Subject: [PATCH 3/8] nodejs tested --- mac.sh | 195 +++++++++++---------------------------------------------- 1 file changed, 36 insertions(+), 159 deletions(-) diff --git a/mac.sh b/mac.sh index 6048835..7021703 100755 --- a/mac.sh +++ b/mac.sh @@ -663,7 +663,7 @@ setup_web_java() { # === 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" .) + sed -i "s|https://www.bstackdemo.com|$CX_TEST_URL|g" $(grep -rl "https://www.bstackdemo.com" .) fi if is_domain_private; then @@ -719,6 +719,8 @@ setup_web_python() { REPO="now-pytest-browserstack" TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + rm -rf $TARGET_DIR + git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true log_msg_to "✅ Cloned repository: $REPO into $TARGET_DIR" "$PRE_RUN_LOG_FILE" @@ -774,8 +776,8 @@ EOF # 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 + # sed -i "s|https://bstackdemo.com/|$CX_TEST_URL|g" tests/bstack-sample-test.py || true + sed -i "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" @@ -792,21 +794,22 @@ EOF } -setup_web_js() { +setup_web_nodejs() { local local_flag=$1 local parallels=$2 - REPO="webdriverio-browserstack" + REPO="now-webdriverio-browserstack" TARGET_DIR="$WORKSPACE_DIR/$PROJECT_FOLDER/$REPO" + rm -rf $TARGET_DIR + mkdir -p "$WORKSPACE_DIR/$PROJECT_FOLDER" # === 1️⃣ Clone Repo === - log_msg_to "📦 Cloning repo $REPO (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 + log_msg_to "📦 Cloning repo $REPO into $TARGET_DIR" "$GLOBAL" "$WEB_LOG_FILE" + git clone https://github.com/browserstackCE/$REPO.git "$TARGET_DIR" >> "$WEB_LOG_FILE" 2>&1 || true cd "$TARGET_DIR" || return 1 - validate_prereqs || return 1 # === 2️⃣ Install Dependencies === log_msg_to "⚙️ Running 'npm install'" "$GLOBAL" "$WEB_LOG_FILE" @@ -818,148 +821,9 @@ setup_web_js() { 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 <browserstack-java-sdk<\/artifactId>/,/<\/dependency>/ s|.*|LATEST|' "$pom_file" + sed -i '/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 @@ -1202,7 +1079,7 @@ setup_mobile_java() { # 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" + sed -i '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 From 49099f5f157277dd6b15317b1104f799556090ee Mon Sep 17 00:00:00 2001 From: Rayan Bhat Date: Tue, 4 Nov 2025 18:42:09 +0530 Subject: [PATCH 4/8] nodejs mobile initial setup --- README.md | 6 +++--- mac.sh | 62 ++++++++++++++++++++++++------------------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 0a3ff03..63b17f5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This utility automates the entire first-mile experience — from credential inpu | 🔍 Pre-requisite Check | Validates if Java, Node, and Python are installed. Provides installation guidance if they are missing. | | 📊 Plan Variant Detection | Calls the `/plan` API to tailor the sample repo based on the customer’s subscription tier (e.g., Live, Pro). | | ✅ Console UX with Checkmarks | Mimics the BrowserStack Network Assessment Tool for a familiar experience. | -| 🪵 Logging for Debugging | Saves all raw logs under `~/.browserstack/bstack_onboarding.log` for support and debugging purposes. | +| 🪵 Logging for Debugging | Saves all raw logs under `~/.browserstack/NOW` for support and debugging purposes. | | 🖥️ (Planned) UI Framework Picker | Allows customers to select their preferred test framework via an interactive UI. | ## How to Use? @@ -34,9 +34,9 @@ You can either run the script directly from the web or clone the repository and **macOS / Linux** ```bash - bash mac.sh + bash mac.sh or ./mac.sh ``` - + Incase there are any permission related issues, please run: chmod +x ./mac.sh **Windows** ```powershell ./windows.ps1 diff --git a/mac.sh b/mac.sh index 7021703..86dd6a6 100755 --- a/mac.sh +++ b/mac.sh @@ -533,20 +533,21 @@ generate_web_caps_json() { generate_mobile_caps_json() { local max_total=$1 + local output_file="$WORKSPACE_DIR/$PROJECT_FOLDER/usage_file.json" + : > "$output_file" # Clear the output file + local count=0 - local 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") + local json="[" - if [ "$usage" -ge 5 ]; then + for template in "${MOBILE_ALL[@]}"; do + IFS="|" read -r platformName deviceName baseVersion <<< "$template" + if [ "$APP_PLATFORM" == "ios" ] && [ "$platformName" != "ios" ]; then + continue + elif [ "$APP_PLATFORM" == "android" ] || [ "$APP_PLATFORM" == "all" ] && [ "$platformName" != "android" ]; then continue fi - + json="${json}{ \"bstack:options\": { \"deviceName\": \"${deviceName}\", @@ -554,18 +555,14 @@ generate_mobile_caps_json() { } }," - echo "$deviceName" >> "$usage_file" count=$((count + 1)) if [ "$count" -ge "$max_total" ]; then break fi - done - + done # End of the loop json="${json%,}]" - echo "$json" - rm -f "$usage_file" + echo "$json" > "$output_file" } - # ===== Fetch plan details (writes to GLOBAL) ===== fetch_plan_details() { log_msg_to "ℹ️ Fetching BrowserStack Plan Details..." "$GLOBAL" @@ -829,7 +826,7 @@ setup_web_nodejs() { local_flag=true fi - # Log local flag status + Log local flag status if [ "$local_flag" = "true" ]; then log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$PRE_RUN_LOG_FILE" else @@ -1154,37 +1151,32 @@ EOF } -setup_mobile_js() { +setup_mobile_nodejs() { local local_flag=$1 local parallels=$2 local log_file=$3 - REPO="webdriverio-appium-app-browserstack" + cd $WORKSPACE_DIR/$PROJECT_FOLDER || return 1 + + REPO="now-webdriverio-appium-app-browserstack" if [ ! -d "$REPO" ]; then - git clone -b sdk https://github.com/browserstack/$REPO + git clone -b sdk https://github.com/BrowserStackCE/$REPO fi - cd "$REPO/android/" || return 1 + + cd "$REPO/test/" || 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 + generate_mobile_caps_json "$parallels" export BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME" export BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY" + export BSTACK_PARALLELS=$parallels + + npm run test > "$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 - 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 + # rm $WORKSPACE_DIR/$PROJECT_FOLDER/usage_file.json || true return 0 } @@ -1209,7 +1201,7 @@ setup_mobile() { 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" ;; - NodeJS) setup_mobile_js "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" ;; + NodeJS) setup_mobile_nodejs "$local_flag" "$parallels_per_platform" "$MOBILE_LOG_FILE" ;; *) log_msg_to "Unknown TECH_STACK: $TECH_STACK" "$MOBILE_LOG_FILE"; return 1 ;; esac From e104bcc12d76cfae304826e9cdf3426b7299fb8b Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Wed, 5 Nov 2025 01:53:30 +0530 Subject: [PATCH 5/8] proxy checks done --- mac.sh | 25 ++++++++-------------- proxy-check.sh | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 16 deletions(-) create mode 100755 proxy-check.sh diff --git a/mac.sh b/mac.sh index 86dd6a6..2adbe88 100755 --- a/mac.sh +++ b/mac.sh @@ -26,13 +26,6 @@ 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" @@ -40,10 +33,8 @@ WEB_PLATFORM_TEMPLATES=( "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" ) @@ -193,7 +184,6 @@ 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" @@ -826,7 +816,7 @@ setup_web_nodejs() { local_flag=true fi - Log local flag status + #log_msg_to local flag status if [ "$local_flag" = "true" ]; then log_msg_to "✅ BrowserStack Local is ENABLED for this run." "$PRE_RUN_LOG_FILE" else @@ -839,7 +829,7 @@ setup_web_nodejs() { export BROWSERSTACK_LOCAL=$local_flag # === 8️⃣ Run Tests === - log_msg_to "🚀 Running 'npm run test'" "$GLOBAL" "$WEB_LOG_FILE" + log_msg_to "🚀 Running 'npm run test'. This could take a few minutes. Follow the Automaton build here: https://automation.browserstack.com/" "$WEB_LOG_FILE" npm run test >> "$WEB_LOG_FILE" 2>&1 || true # === 9️⃣ Wrap Up === @@ -1026,13 +1016,13 @@ PYEOF # Log local flag status if [ "$local_flag" = "true" ]; then - log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." "$PRE_RUN_LOG_FILE" + log_msg_to "⚠️ BrowserStack Local is ENABLED for this run." else - log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." "$PRE_RUN_LOG_FILE" + log_msg_to "⚠️ BrowserStack Local is DISABLED for this run." fi # Run pytest with BrowserStack SDK from the chosen platform directory - log_msg_to "🚀 Running 'cd $run_dir && browserstack-sdk pytest -s bstack_sample.py'" "$PRE_RUN_LOG_FILE" + log_msg_to "🚀 Running 'cd $run_dir && browserstack-sdk pytest -s bstack_sample.py'" ( cd "$run_dir" && browserstack-sdk pytest -s bstack_sample.py >> "$log_file" 2>&1 || true ) @@ -1294,7 +1284,10 @@ 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" - +log_msg_to "Checking proxy in environment" "$GLOBAL" +chmod +x proxy-check.sh +./proxy-check.sh +log_msg_to "Starting setup run..." "$GLOBAL" run_setup log_msg_to "Setup run finished." "$GLOBAL" \ No newline at end of file diff --git a/proxy-check.sh b/proxy-check.sh new file mode 100755 index 0000000..4e6ba4b --- /dev/null +++ b/proxy-check.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env sh + +# URL to test +TEST_URL="https://www.browserstack.com/automate/browsers.json" + +# Detect proxy from env (case insensitive) +PROXY="${http_proxy:-${HTTP_PROXY:-${https_proxy:-${HTTPS_PROXY}}}}" + +# Reset output variables +export PROXY_HOST="" +export PROXY_PORT="" + +# Function: parse proxy url to host + port +parse_proxy() { + p="$1" + # strip protocol e.g. http://, https:// + p="${p#http://}" + p="${p#https://}" + # strip credentials if any user:pass@ + p="${p#*[@]}" + + # extract host and port + export PROXY_HOST="${p%%:*}" + export PROXY_PORT="${p##*:}" +} + +base64_encoded_creds=$(printf "%s" $BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY | base64 | tr -d '\n') + + +# If no proxy configured, exit early +if [ -z "$PROXY" ]; then + echo "No proxy found in environment. Clearing proxy host and port variables." + export PROXY_HOST="" + export PROXY_PORT="" + return 0 2>/dev/null || exit 0 +fi + +echo "Proxy detected: $PROXY" +parse_proxy "$PROXY" + +echo "Testing reachability via proxy..." + + +STATUS_CODE=$(curl -sS -o /dev/null -H "Authorization: Basic ${base64_encoded_creds}" -w "%{http_code}" --proxy "$PROXY" "$TEST_URL" 2>/dev/null) + +if [ "${STATUS_CODE#2}" != "$STATUS_CODE" ]; then + echo "✅ Reachable. HTTP $STATUS_CODE" + echo "Exporting PROXY_HOST=$PROXY_HOST" + echo "Exporting PROXY_PORT=$PROXY_PORT" + export PROXY_HOST + export PROXY_PORT +else + echo "❌ Not reachable (HTTP $STATUS_CODE). Clearing variables." + export PROXY_HOST="" + export PROXY_PORT="" +fi From 67ca2f8a83425109951e581354969d419862d844 Mon Sep 17 00:00:00 2001 From: Samiran Saha Date: Wed, 5 Nov 2025 02:00:04 +0530 Subject: [PATCH 6/8] proxy checks + folder structure --- README.md | 15 ++++++++++----- proxy-check.sh => mac/proxy-check.sh | 0 mac.sh => mac/run.sh | 0 windows.ps1 => win/run.ps1 | 0 4 files changed, 10 insertions(+), 5 deletions(-) rename proxy-check.sh => mac/proxy-check.sh (100%) rename mac.sh => mac/run.sh (100%) rename windows.ps1 => win/run.ps1 (100%) diff --git a/README.md b/README.md index 63b17f5..962cccb 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,17 @@ You can either run the script directly from the web or clone the repository and **macOS / Linux** ```bash - bash mac.sh or ./mac.sh + bash mac/run.sh or ./mac/run.sh ``` - Incase there are any permission related issues, please run: chmod +x ./mac.sh + If you encounter any permission issues, ensure the script is executable: + + ``` + chmod +x ./mac/run.sh + ``` + **Windows** ```powershell - ./windows.ps1 + ./win/run.ps1 ``` ### Remote Execution @@ -49,7 +54,7 @@ You can either run the script directly from the web or clone the repository and To run the onboarding utility on macOS or Linux without cloning, execute the following command in your terminal: ```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/http-heading/browserstack-now/main/mac_os.sh)" +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/http-heading/browserstack-now/main/mac/run.sh)" ``` #### Windows @@ -58,7 +63,7 @@ To run the onboarding utility on Windows without cloning, execute the following **Note:** You may need to set the execution policy to `RemoteSigned` or `Bypass` to run the script. ```powershell -iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/http-heading/browserstack-now/main/windows.ps1')) +iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/http-heading/browserstack-now/main/win/run.ps1')) ``` ## Identifying and sharing the log files diff --git a/proxy-check.sh b/mac/proxy-check.sh similarity index 100% rename from proxy-check.sh rename to mac/proxy-check.sh diff --git a/mac.sh b/mac/run.sh similarity index 100% rename from mac.sh rename to mac/run.sh diff --git a/windows.ps1 b/win/run.ps1 similarity index 100% rename from windows.ps1 rename to win/run.ps1 From a95155d722ec648f828169fad620786e60f11705 Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Mon, 3 Nov 2025 20:53:39 +0530 Subject: [PATCH 7/8] latest windows script --- win/run.ps1 | 1936 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 1190 insertions(+), 746 deletions(-) diff --git a/win/run.ps1 b/win/run.ps1 index 4492bda..fe369be 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -1,817 +1,1261 @@ -# BrowserStack Onboarding Script - Windows PowerShell version +#requires -version 5.0 +<# + BrowserStack Onboarding (PowerShell 5.0, GUI) + - Full parity port of macOS bash + - Uses WinForms for GUI prompts + - Logs to %USERPROFILE%\.browserstack\NOW\logs +#> -param( - [Alias("zlogs")][Switch]$ShowLogs = $false +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= @("") + +# ===== Example 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" ) -#================================================================================ -#region Custom UI Function -#================================================================================ - -function Show-CustomSelectionForm { - # Load required .NET assemblies for building a GUI - Add-Type -AssemblyName System.Windows.Forms - Add-Type -AssemblyName System.Drawing - - # --- Define the Refined Theme --- - $theme = @{ - BackColor = [System.Drawing.ColorTranslator]::FromHtml("#2D2D30") - ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#F1F1F1") - PrimaryAction = [System.Drawing.ColorTranslator]::FromHtml("#0070f0") - ButtonColor = [System.Drawing.ColorTranslator]::FromHtml("#555555") - Font = New-Object System.Drawing.Font("Segoe UI", 11) - HeaderFont = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold) - } +# 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", + "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" +) - # --- Create the Main Form --- - $form = New-Object System.Windows.Forms.Form - $form.Text = "BrowserStack Onboarding" - $form.Size = New-Object System.Drawing.Size(480, 480) - $form.BackColor = $theme.BackColor - $form.ForeColor = $theme.ForeColor - $form.Font = $theme.Font - $form.StartPosition = "CenterScreen" - $form.FormBorderStyle = "FixedDialog" - $form.MaximizeBox = $false - $form.MinimizeBox = $false - - # --- Create Credential Controls --- - $usernameLabel = New-Object System.Windows.Forms.Label - $usernameLabel.Text = "BrowserStack Username:" - $usernameLabel.Location = New-Object System.Drawing.Point(40, 40) - $usernameLabel.AutoSize = $true - $form.Controls.Add($usernameLabel) - - $usernameTextBox = New-Object System.Windows.Forms.TextBox - $usernameTextBox.Location = New-Object System.Drawing.Point(260, 37) - $usernameTextBox.Size = New-Object System.Drawing.Size(170, 20) - $form.Controls.Add($usernameTextBox) - - $accessKeyLabel = New-Object System.Windows.Forms.Label - $accessKeyLabel.Text = "BrowserStack Access Key:" - $accessKeyLabel.Location = New-Object System.Drawing.Point(40, 75) - $accessKeyLabel.AutoSize = $true - $form.Controls.Add($accessKeyLabel) - - $accessKeyTextBox = New-Object System.Windows.Forms.TextBox - $accessKeyTextBox.Location = New-Object System.Drawing.Point(260, 72) - $accessKeyTextBox.Size = New-Object System.Drawing.Size(170, 20) - $accessKeyTextBox.UseSystemPasswordChar = $true - $form.Controls.Add($accessKeyTextBox) - - - # --- Create Test Type Section --- - $testTypeHeader = New-Object System.Windows.Forms.Label - $testTypeHeader.Text = "Testing Type" - $testTypeHeader.Font = $theme.HeaderFont - $testTypeHeader.Location = New-Object System.Drawing.Point(36, 130) - $testTypeHeader.AutoSize = $true - $form.Controls.Add($testTypeHeader) - - # === FIX: Create an invisible Panel to group the radio buttons === - $testTypePanel = New-Object System.Windows.Forms.Panel - $testTypePanel.Location = New-Object System.Drawing.Point(38, 160) - $testTypePanel.Size = New-Object System.Drawing.Size(200, 100) - $form.Controls.Add($testTypePanel) - - $testOptions = @("Web Testing", "Mobile App Testing", "Both") - $yPos = 5 - foreach ($option in $testOptions) { - $rb = New-Object System.Windows.Forms.RadioButton - $rb.Text = $option - $rb.Location = New-Object System.Drawing.Point(2, $yPos) # Location is relative to the Panel - $rb.AutoSize = $true - $testTypePanel.Controls.Add($rb) # Add to the Panel, not the Form - $yPos += 30 - } +# MOBILE_ALL combines the tiers +$MOBILE_ALL = @() +$MOBILE_ALL += $MOBILE_TIER1 +$MOBILE_ALL += $MOBILE_TIER2 +$MOBILE_ALL += $MOBILE_TIER3 +$MOBILE_ALL += $MOBILE_TIER4 - # --- Create Tech Stack Section --- - $techStackHeader = New-Object System.Windows.Forms.Label - $techStackHeader.Text = "Technology Stack" - $techStackHeader.Font = $theme.HeaderFont - $techStackHeader.Location = New-Object System.Drawing.Point(36, 265) - $techStackHeader.AutoSize = $true - $form.Controls.Add($techStackHeader) - - # === FIX: Create another invisible Panel for the second group === - $techStackPanel = New-Object System.Windows.Forms.Panel - $techStackPanel.Location = New-Object System.Drawing.Point(38, 295) - $techStackPanel.Size = New-Object System.Drawing.Size(200, 100) - $form.Controls.Add($techStackPanel) - - $techOptions = @("Java", "Python", "JavaScript") - $yPos = 5 - foreach ($option in $techOptions) { - $rb = New-Object System.Windows.Forms.RadioButton - $rb.Text = $option - $rb.Location = New-Object System.Drawing.Point(2, $yPos) # Location is relative to the Panel - $rb.AutoSize = $true - $techStackPanel.Controls.Add($rb) # Add to the Panel, not the Form - $yPos += 30 - } +# ===== 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 + } +} - # --- Create Buttons --- - $continueButton = New-Object System.Windows.Forms.Button - $continueButton.Text = "Continue" - $continueButton.Location = New-Object System.Drawing.Point(240, 395) - $continueButton.Size = New-Object System.Drawing.Size(95, 35) - $continueButton.BackColor = $theme.PrimaryAction - $continueButton.ForeColor = [System.Drawing.Color]::White - $continueButton.FlatStyle = "Flat" - $continueButton.FlatAppearance.BorderSize = 0 - $form.Controls.Add($continueButton) - - $cancelButton = New-Object System.Windows.Forms.Button - $cancelButton.Text = "Cancel" - $cancelButton.Location = New-Object System.Drawing.Point(345, 395) - $cancelButton.Size = New-Object System.Drawing.Size(85, 35) - $cancelButton.BackColor = $theme.ButtonColor - $cancelButton.FlatStyle = "Flat" - $cancelButton.FlatAppearance.BorderSize = 0 - $form.Controls.Add($cancelButton) - - # --- Define Button Actions (Event Handlers) --- - $continueButton.Add_Click({ - # === FIX: Find checked buttons within their respective Panels === - $selectedTestType = $testTypePanel.Controls | Where-Object { $_.Checked } - $selectedTechStack = $techStackPanel.Controls | Where-Object { $_.Checked } - - if ([string]::IsNullOrWhiteSpace($usernameTextBox.Text) ` - -or [string]::IsNullOrWhiteSpace($accessKeyTextBox.Text) ` - -or -not $selectedTestType ` - -or -not $selectedTechStack) { - [System.Windows.Forms.MessageBox]::Show("Please fill in all fields to continue.", "Validation Error", "OK", "Error") - } else { - $form.Tag = [PSCustomObject]@{ - Username = $usernameTextBox.Text - AccessKey = $accessKeyTextBox.Text - TestType = $selectedTestType.Text - TechStack = $selectedTechStack.Text - } - $form.DialogResult = [System.Windows.Forms.DialogResult]::OK - $form.Close() - } - }) +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 + } +} - $cancelButton.Add_Click({ - $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel - $form.Close() - }) - - # --- Show the form and wait for the user --- - $result = $form.ShowDialog() +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) - if ($result -eq [System.Windows.Forms.DialogResult]::OK) { - return $form.Tag - } else { - return $null - } + $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) } -#endregion +# 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 ' ' -#================================================================================ -# Main Script Body -#================================================================================ + # .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 + } -# Step 1: Workspace Setup -$BSS_ROOT = Join-Path $HOME '.browserstack' -$BSS_SETUP_DIR = Join-Path $BSS_ROOT 'browserstackSampleSetup' -$LOG_FILE = Join-Path $BSS_SETUP_DIR 'bstackOnboardingLogs.log' -New-Item -Path $BSS_SETUP_DIR -ItemType Directory -Force | Out-Null -Set-Location -Path $BSS_SETUP_DIR -New-Item -Path $LOG_FILE -ItemType File -Force | Out-Null + $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 + } -function Write-Log($message) { - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - Add-Content -Path $LOG_FILE -Value "[${timestamp}] $message" + $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 } + } + 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." } -Write-Log "[Workspace initialized]" -# Step 2 & 3 Combined: Get all user input from the Custom UI -Write-Host "Please provide your details in the setup window..." -$selections = Show-CustomSelectionForm -if (-not $selections) { - Write-Host "❌ Operation canceled by user." - exit 1 +# 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" } -# Assign all selections from the UI to the script's variables -$BS_USERNAME = $selections.Username -$BS_ACCESS_KEY = $selections.AccessKey -$TEST_OPTION = $selections.TestType -$TECH_STACK = $selections.TechStack +# 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) +} + +# ===== 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,160) + $form.StartPosition = "CenterScreen" + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $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,50) + $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,80) + $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,160) + $form.StartPosition = "CenterScreen" + + $label = New-Object System.Windows.Forms.Label + $label.Text = $Prompt + $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,50) + $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,80) + $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 +} -Write-Host "✅ User credentials and options captured." -Write-Log "[User selected: $TEST_OPTION | $TECH_STACK]" +# === 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) -# Step 4: Validate Required Tools -Write-Host "ℹ️ Checking prerequisites for $TECH_STACK..." -Write-Host "📂 Current working directory: $(Get-Location)" -switch ($TECH_STACK) { + $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:" -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:" + 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") ` + -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" { - if (-not (Get-Command java -ErrorAction SilentlyContinue)) { - Write-Host "❌ Java is not installed or not in PATH.`n ❗ Please install Java and add it to PATH." - exit 1 - } - $javaVer = & java -version 2>&1 - Write-Host "✅ Java is installed. Version details:`n$javaVer" + 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" { - if (-not (Get-Command python -ErrorAction SilentlyContinue)) { - Write-Host "❌ Python is not installed or not in PATH.`n ❗ Please install Python 3 and ensure it's in PATH." - exit 1 + try { + Set-PythonCmd + $code = Invoke-Py -Arguments @("--version") -LogFile $null -WorkingDirectory (Get-Location).Path + if ($code -eq 0) { + Log-Line ("✅ Python detected: {0}" -f ( ($PY_CMD -join ' ') )) $GLOBAL_LOG + } else { + throw "Python present but failed to execute" } - $pyVer = & python --version - Write-Host "✅ python is installed: $pyVer" + } catch { + Log-Line "❌ Python exists but failed to run." $GLOBAL_LOG + throw + } } - "JavaScript" { - if (-not (Get-Command node -ErrorAction SilentlyContinue) -or -not (Get-Command npm -ErrorAction SilentlyContinue)) { - Write-Host "❌ Node.js or npm is not installed in PATH.`n ❗ Please install Node.js (which includes npm)." - exit 1 - } - $nodeVer = & node -v - $npmVer = & npm -v - Write-Host "✅ Node.js is installed: $nodeVer" - Write-Host "✅ npm is installed: $npmVer" + + "JS" { + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Log-Line "❌ Node.js not found." $GLOBAL_LOG; throw "Node not found" } + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { Log-Line "❌ npm not found." $GLOBAL_LOG; throw "npm not found" } + Log-Line "✅ Node.js: $(& node -v) ; npm: $(& npm -v)" $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" } -Write-Log "[Prerequisites validated for $TECH_STACK]" -# Step 5: Fetch Plan Details (BrowserStack API) -$authInfo = "${BS_USERNAME}:${BS_ACCESS_KEY}" -$authHeaderValue = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($authInfo)) -$headers = @{ Authorization = "Basic $authHeaderValue" } -$WEB_PLAN_FETCHED = $false -$MOBILE_PLAN_FETCHED = $false -$webUnauthorized = $false -$mobileUnauthorized = $false +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 + } +} -# Web Testing plan -try { - $respWeb = Invoke-WebRequest -Uri "https://api.browserstack.com/automate/plan.json" -Headers $headers -ErrorAction Stop - $HTTP_CODE_WEB = $respWeb.StatusCode - $RESPONSE_WEB_BODY = $respWeb.Content -} catch { - $HTTP_CODE_WEB = $_.Exception.Response.StatusCode.value__ - $reader = New-Object IO.StreamReader $_.Exception.Response.GetResponseStream() - $RESPONSE_WEB_BODY = $reader.ReadToEnd(); $reader.Close() -} -if ($HTTP_CODE_WEB -eq 200) { - $WEB_PLAN_FETCHED = $true - $planWebJson = $RESPONSE_WEB_BODY | ConvertFrom-Json - $TEAM_PARALLELS_MAX_ALLOWED_WEB = $planWebJson.parallel_sessions_max_allowed - Write-Host "✅ Web Testing plan fetched: Max Parallels = $TEAM_PARALLELS_MAX_ALLOWED_WEB" - Write-Log "[Web Plan] $RESPONSE_WEB_BODY" -} else { - Write-Host "❌ Web Testing plan fetch failed (HTTP $HTTP_CODE_WEB)" - Write-Log "[Web Plan Error] $RESPONSE_WEB_BODY" - if ($HTTP_CODE_WEB -eq 401) { - Write-Host "⚠️ Invalid credentials or no Web Testing access." - $webUnauthorized = $true +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 { + $path = Show-OpenFileDialog -Title "📱 Select your .apk or .ipa (Cancel = use default sample)" + 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 + $headers = @{ Authorization = (Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY) } + $form = @{ file = Get-Item -Path $path } + $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -Form $form + $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() } -# Mobile App Testing plan -try { - $respMob = Invoke-WebRequest -Uri "https://api-cloud.browserstack.com/app-automate/plan.json" -Headers $headers -ErrorAction Stop - $HTTP_CODE_MOBILE = $respMob.StatusCode - $RESPONSE_MOBILE_BODY = $respMob.Content -} catch { - $HTTP_CODE_MOBILE = $_.Exception.Response.StatusCode.value__ - $reader = New-Object IO.StreamReader $_.Exception.Response.GetResponseStream() - $RESPONSE_MOBILE_BODY = $reader.ReadToEnd(); $reader.Close() -} -if ($HTTP_CODE_MOBILE -eq 200) { - $MOBILE_PLAN_FETCHED = $true - $planMobJson = $RESPONSE_MOBILE_BODY | ConvertFrom-Json - $TEAM_PARALLELS_MAX_ALLOWED_MOBILE = $planMobJson.parallel_sessions_max_allowed - Write-Host "✅ Mobile Testing plan fetched: Max Parallels = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE" - Write-Log "[Mobile Plan] $RESPONSE_MOBILE_BODY" -} else { - Write-Host "❌ Mobile Testing plan fetch failed (HTTP $HTTP_CODE_MOBILE)" - Write-Log "[Mobile Plan Error] $RESPONSE_MOBILE_BODY" - if ($HTTP_CODE_MOBILE -eq 401) { - Write-Host "⚠️ Invalid credentials or no Mobile Testing access." - $mobileUnauthorized = $true +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() } -# Decide exit if no access based on user selection -if ($TEST_OPTION -eq "Web Testing" -and $webUnauthorized) { exit 1 } -if ($TEST_OPTION -eq "Mobile App Testing" -and $mobileUnauthorized) { exit 1 } -if ($TEST_OPTION -eq "Both" -and $webUnauthorized -and $mobileUnauthorized) { - Write-Host "❌ Both Web and Mobile testing are unavailable with current subscription. Exiting." - exit 1 + +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 ($items | ConvertTo-Json -Depth 5) } -Write-Log "[Plan details fetched]" -# Step 6: Prepare platform templates and YAML generation -$PARALLEL_PERCENTAGE = 0.75 -$WEB_PLATFORM_TEMPLATES = @( - "Windows|10|Chrome", - "Windows|10|Firefox", - "Windows|11|Edge", - "Windows|11|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" -) +# ===== 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 } -function Get-WebPlatformsYaml($maxParallels) { - $max = [int]([math]::Floor($maxParallels * $PARALLEL_PERCENTAGE)) - if ($max -lt 1) { $max = 1 } - $yamlLines = @(); $count = 0 - foreach ($template in $WEB_PLATFORM_TEMPLATES) { - if ($count -ge $max) { break } - $parts = $template -split '\|' - $os = $parts[0]; $osVer = $parts[1]; $browser = $parts[2] - foreach ($ver in @("latest", "latest-1", "latest-2")) { - $yamlLines += " - os: $os" - $yamlLines += " osVersion: $osVer" - $yamlLines += " browserName: $browser" - $yamlLines += " browserVersion: $ver" - $count++ - if ($count -ge $max) { break } - } + 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 } - return $yamlLines -join [Environment]::NewLine -} -function Get-MobilePlatformsYaml($maxParallels) { - $max = [int]([math]::Floor($maxParallels * $PARALLEL_PERCENTAGE)) - if ($max -lt 1) { $max = 1 } - $yamlLines = @(); $count = 0 - foreach ($template in $MOBILE_DEVICE_TEMPLATES) { - if ($count -ge $max) { break } - $parts = $template -split '\|' - $platform = $parts[0]; $device = $parts[1]; $version = $parts[2] - $yamlLines += " - platformName: $platform" - $yamlLines += " deviceName: $device" - $yamlLines += " platformVersion: '$version.0'" - $count++ - if ($count -ge $max) { break } + } + 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 } - return $yamlLines -join [Environment]::NewLine + } + + 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" + } } -# Step 7: Define test run functions and patterns -$WEB_SETUP_ERRORS = @("Error", "Exception", "Build failed", "Session not created", "Cannot start test") -$WEB_LOCAL_ERRORS = @("browserstack local failed", "fail local testing", "failed to connect tunnel") -$MOBILE_LOCAL_ERRORS = @("tunnel connection error") -$MOBILE_SETUP_ERRORS = @() +# ===== Setup: Web (Java) ===== +function Setup-Web-Java { + param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) -function Run-WebTests([bool]$useLocal) { - switch ($TECH_STACK) { - "Java" { $REPO="testng-browserstack"; $cloneUrl="https://github.com/browserstack/testng-browserstack.git" } - "Python" { $REPO="python-selenium-browserstack"; $cloneUrl="https://github.com/browserstack/python-selenium-browserstack.git" } - "JavaScript" { $REPO="webdriverio-browserstack"; $cloneUrl="https://github.com/browserstack/webdriverio-browserstack.git" } - } - if (-not (Test-Path $REPO)) { - if ($cloneBranch) { - & git clone $cloneUrl -b $cloneBranch - } else { - & git clone $cloneUrl - } + $REPO = "browserstack-examples-testng" + $TARGET = Join-Path $GLOBAL_DIR $REPO + + if (!(Test-Path $TARGET)) { + Log-Line "📦 Cloning repo $REPO into $TARGET" $GLOBAL_LOG + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG + } else { + Log-Line "📂 Repo $REPO already exists at $TARGET, skipping clone." $GLOBAL_LOG + } + + Push-Location $TARGET + try { + Validate-Tech-Stack + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + + $env:BROWSERSTACK_CONFIG_FILE = "src/test/resources/conf/capabilities/bstack-parallel.yml" + + # Replace base URL in TestBase.java (and ensure no BOM) + $repoRoot = $TARGET + $tb = Get-ChildItem -Path $repoRoot -Recurse -Filter TestBase.java -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + + if ($tb) { + $c = [System.IO.File]::ReadAllText($tb) + if ([string]::IsNullOrWhiteSpace($CX_TEST_URL)) { $CX_TEST_URL = "https://bstackdemo.com" } + $c = $c.Replace("https://bstackdemo.com", $CX_TEST_URL) + Set-ContentNoBom -Path $tb -Value $c + + # Hard strip BOM if any stray + $bytes = [System.IO.File]::ReadAllBytes($tb) + if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { + [System.IO.File]::WriteAllBytes($tb, $bytes[3..($bytes.Length-1)]) + } + Log-Line "🔧 Updated URL and ensured UTF-8 no BOM in: $tb" $GLOBAL_LOG + } else { + Log-Line "❌ Could not find TestBase.java under $repoRoot" $GLOBAL_LOG } - Set-Location -Path $REPO - # (Prerequisites check could be repeated here if needed) + Log-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $GLOBAL_LOG - $platformYaml = Get-WebPlatformsYaml $TEAM_PARALLELS_MAX_ALLOWED_WEB - if ($TECH_STACK -ne "JavaScript") { - # Determine framework name - if ($TECH_STACK -eq "Java") { - $framework = "testng" - } else { - $framework = "python" - } + $platforms = Generate-Web-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_WEB + $localFlag = if ($UseLocal) { "true" } else { "false" } - if ($framework -eq "testng") { - $yamlContent = @" -userName: $BS_USERNAME -accessKey: $BS_ACCESS_KEY -framework: $framework -browserstackLocal: $useLocal -buildName: browserstack-build-web -projectName: BrowserStack Web Sample +@" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: testng +browserstackLocal: $localFlag +buildName: browserstack-sample-java-web +projectName: NOW-Web-Test percy: true accessibility: true platforms: -$platformYaml -"@ - } else { - $yamlContent = @" -userName: $BS_USERNAME -accessKey: $BS_ACCESS_KEY -framework: $framework -browserstackLocal: $useLocal -buildName: browserstack-build-web -projectName: BrowserStack Web Sample -platforms: -$platformYaml -"@ - } - Set-Content -Path "browserstack.yml" -Value $yamlContent - } +$platforms +parallelsPerPlatform: $ParallelsPerPlatform +"@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE - $runLog = Join-Path $BSS_SETUP_DIR 'web_run_result.log' - if (Test-Path $runLog) { Remove-Item $runLog } - if ($TECH_STACK -eq "Java") { - & mvn test -P sample-test *> $runLog 2>&1 - } elseif ($TECH_STACK -eq "Python") { - & python -m venv env; . .\env\Scripts\Activate.ps1 - & python -m pip install -r requirements.txt >> $LOG_FILE 2>&1 - & browserstack-sdk python .\tests\test.py *> $runLog 2>&1 - } elseif ($TECH_STACK -eq "JavaScript") { - & npm install >> $LOG_FILE 2>&1 - $confFilePath = "conf/test.conf.js" - try { - $configJson = Get-Content $confFilePath -Raw | ConvertFrom-Json - } catch { - $configJson = @{} - } - $configJson.maxInstances = $TEAM_PARALLELS_MAX_ALLOWED_WEB - $capList = @(); $count = 0 - $maxCaps = [int]([math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) - if ($maxCaps -lt 1) { $maxCaps = 1 } - foreach ($template in $WEB_PLATFORM_TEMPLATES) { - if ($count -ge $maxCaps) { break } - $parts = $template -split '\|' - $os = $parts[0]; $osVer = $parts[1]; $browser = $parts[2] - foreach ($ver in @("latest", "latest-1", "latest-2")) { - if ($count -ge $maxCaps) { break } - $capList += @{ - browserName = $browser - browserVersion = $ver - "bstack:options" = @{ - os = $os - osVersion = $osVer - } - } - $count++ - if ($count -ge $maxCaps) { break } - } - } - $configJson.capabilities = $capList - ($configJson | ConvertTo-Json -Depth 6) | Set-Content $confFilePath - $env:BROWSERSTACK_USERNAME = $BS_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BS_ACCESS_KEY - $env:BROWSERSTACK_LOCAL = $useLocal.ToString().ToLower() - if ($useLocal) { - & npm run local *> $runLog 2>&1 - } else { - & npm run test *> $runLog 2>&1 - } - } - if (Test-Path $runLog) { Get-Content $runLog | Add-Content $LOG_FILE } - Set-Location -Path $BSS_SETUP_DIR + $mvn = Get-MavenCommand -RepoDir $TARGET + Log-Line "⚙️ Running '$mvn install -DskipTests'" $GLOBAL_LOG + Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + + Log-Line "🚀 Running '$mvn clean test -P bstack-parallel -Dtest=OrderTest'" $GLOBAL_LOG + Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("clean","test","-P","bstack-parallel","-Dtest=OrderTest") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + + } finally { + Pop-Location + } } -function Run-MobileTests([bool]$useLocal) { - switch ($TECH_STACK) { - "Java" { $REPO="testng-appium-app-browserstack"; $cloneUrl="https://github.com/browserstack/testng-appium-app-browserstack.git" } - "Python" { $REPO="python-appium-app-browserstack"; $cloneUrl="https://github.com/browserstack/python-appium-app-browserstack.git" } - "JavaScript" { $REPO="webdriverio-appium-app-browserstack"; $cloneUrl="https://github.com/browserstack/webdriverio-appium-app-browserstack.git"; $cloneBranch="sdk" } +# ===== Setup: Web (Python) ===== +function Setup-Web-Python { + param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) + + $REPO = "browserstack-examples-pytest" + $TARGET = Join-Path $GLOBAL_DIR $REPO + + if (!(Test-Path $TARGET)) { + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Branch "sdk" -Target $TARGET -LogFile $WEB_LOG + Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + } else { + Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + } + + Push-Location $TARGET + try { + Validate-Tech-Stack + + 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 } - if (-not (Test-Path $REPO)) { - if ($cloneBranch) { - & git clone $cloneUrl -b $cloneBranch - } else { - & git clone $cloneUrl - } + $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 + + $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 #TODO: Uncomment this when percy issue is fixed +accessibility: true +platforms: +$platforms +parallelsPerPlatform: $ParallelsPerPlatform +"@ | Set-Content "browserstack.yml" + + Log-Line "✅ Updated root-level browserstack.yml with platforms and credentials" $GLOBAL_LOG + + # Update demo URL in e2e if present (no BOM write) + $e2eRel = "src/test/suites/e2e.py" + $e2eFull = Join-Path $TARGET $e2eRel + if (Test-Path $e2eFull) { + $c = [System.IO.File]::ReadAllText($e2eFull) + $c = $c.Replace("https://bstackdemo.com/", $CX_TEST_URL) + Set-ContentNoBom -Path $e2eFull -Value $c + Log-Line "🔧 Updated URL in $e2eRel" $GLOBAL_LOG + } else { + Log-Line "ℹ️ Skipping URL update: $e2eRel not found in repo" $GLOBAL_LOG } - Set-Location -Path $REPO - - $platformYaml = Get-MobilePlatformsYaml $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - if ($TECH_STACK -eq "Java") { - $yamlPath = "android/testng-examples/browserstack.yml" - $yamlContent = @" -userName: $BS_USERNAME -accessKey: $BS_ACCESS_KEY -framework: testng -app: bs://sample.app + + $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" + # Run exactly: browserstack-sdk pytest -s src/test/suites/e2e.py (pytest must be on PATH) + $args = @('pytest','-s','src/test/suites/e2e.py') + Log-Line "⚠️ Running tests with local=$localFlag" $GLOBAL_LOG + [void](Invoke-External -Exe $sdk -Arguments $args -LogFile $LogFile -WorkingDirectory $TARGET) + + } finally { + Pop-Location + } +} + +# ===== Setup: Mobile (Python) ===== +function Setup-Mobile-Python { + param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) + + $REPO = "browserstack-examples-pytest-BDD-appium" + $TARGET = Join-Path $GLOBAL_DIR $REPO + + if (!(Test-Path $TARGET)) { + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + } else { + Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + } + + Push-Location $TARGET + try { + if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } + $venv = Join-Path $TARGET "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 + + $env:BROWSERSTACK_CONFIG_FILE = "browserstack.yml" + $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $localFlag = if ($UseLocal) { "true" } else { "false" } + +@" +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: -$platformYaml -browserstackLocal: $useLocal -buildName: browserstack-build-1 -projectName: BrowserStack Sample -"@ - Set-Content -Path $yamlPath -Value $yamlContent - Set-Location -Path "android/testng-examples" - & mvn test -P sample-test *> "$BSS_SETUP_DIR\mobile_run_result.log" 2>&1 +$platforms +"@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE + + Log-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $GLOBAL_LOG + Log-Line "🚀 Running 'browserstack-sdk pytest -s tests/test_wikipedia.py'" $GLOBAL_LOG + + $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" + # Run exactly: browserstack-sdk pytest -s tests/test_wikipedia.py (pytest must be on PATH) + [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/test_wikipedia.py') -LogFile $LogFile -WorkingDirectory $TARGET) + + } finally { + Pop-Location + } +} + +# ===== Setup: Mobile (Java) ===== +function Setup-Mobile-Java { + param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) + + $REPO = "browserstack-examples-appium-testng" + $TARGET = Join-Path $GLOBAL_DIR $REPO + + if (!(Test-Path $TARGET)) { + Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG + Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG + } else { + Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + } + + # Update pom.xml sdk version to LATEST (matches mac script) + $pom = Join-Path $TARGET "pom.xml" + if (Test-Path $pom) { + $pomContent = Get-Content $pom -Raw + $pomContent = $pomContent -replace '(?s)(browserstack-java-sdk.*?)(.*?)()', '$1LATEST$3' + $pomContent | Set-Content $pom + Log-Line "🔧 Updated browserstack-java-sdk version to LATEST in pom.xml" $GLOBAL_LOG + } + + Push-Location $TARGET + try { + Validate-Tech-Stack + + $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME + $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY + + # Update driver init to AndroidDriver (parity with bash) + $testBase = Get-ChildItem -Recurse -Filter "TestBase.java" | Select-Object -First 1 + if ($testBase) { + (Get-Content $testBase.FullName) -replace 'new AppiumDriver\(', 'new AndroidDriver(' | Set-Content $testBase.FullName + Log-Line "🔧 Updated driver initialization in $($testBase.FullName) to use AndroidDriver" $GLOBAL_LOG } - elseif ($TECH_STACK -eq "Python") { - & python -m venv env; . .\env\Scripts\Activate.ps1 - & python -m pip install -r requirements.txt >> $LOG_FILE 2>&1 - Set-Location -Path "android" - $yamlContent = @" -userName: $BS_USERNAME -accessKey: $BS_ACCESS_KEY -framework: python -app: bs://sample.app + + $env:BROWSERSTACK_CONFIG_FILE = "src/test/resources/conf/capabilities/browserstack-parallel.yml" + $platforms = Generate-Mobile-Platforms-Yaml -MaxTotalParallels $TEAM_PARALLELS_MAX_ALLOWED_MOBILE + $localFlag = if ($UseLocal) { "true" } else { "false" } + +@" +userName: $BROWSERSTACK_USERNAME +accessKey: $BROWSERSTACK_ACCESS_KEY +framework: testng +browserstackLocal: $localFlag +buildName: browserstack-build-mobile +projectName: NOW-Mobile-Test +parallelsPerPlatform: $ParallelsPerPlatform +accessibility: true +percy: true +app: $APP_URL platforms: -$platformYaml -browserstackLocal: $useLocal -buildName: browserstack-build-1 -projectName: BrowserStack Sample -"@ - Set-Content -Path "browserstack.yml" -Value $yamlContent - & browserstack-sdk python browserstack_sample.py *> "$BSS_SETUP_DIR\mobile_run_result.log" 2>&1 +$platforms +"@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE + + Log-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $GLOBAL_LOG + + $mvn = Get-MavenCommand -RepoDir $TARGET + Log-Line "⚙️ Running '$mvn install -DskipTests'" $GLOBAL_LOG + Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + + Log-Line "🚀 Running '$mvn clean test -P bstack-parallel -Dtest=OrderTest'" $GLOBAL_LOG + Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("clean","test","-P","bstack-parallel","-Dtest=OrderTest") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + + } finally { + Pop-Location + } +} + +# ===== Wrappers with retry ===== +function Setup-Web { + Log-Line "Starting Web setup for $TECH_STACK" $WEB_LOG + + $localFlag = $true + $attempt = 1 + $success = $false + + $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) + if ($totalParallels -lt 1) { $totalParallels = 1 } + $parallelsPerPlatform = $totalParallels + + while ($attempt -le 2) { + Log-Line "[Web Setup Attempt $attempt] browserstackLocal: $localFlag" $WEB_LOG + switch ($TECH_STACK) { + "Java" { Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG } + "Python" { Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG } + "JS" { Log-Line "JS path not enabled by current Tech Stack chooser; add if needed." $WEB_LOG } + default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $WEB_LOG; return } } - elseif ($TECH_STACK -eq "JavaScript") { - Push-Location -Path "android\examples\run-parallel-test" - & npm install >> $LOG_FILE 2>&1 - $confFile = "parallel.conf.js" - try { - $config = Get-Content $confFile -Raw | ConvertFrom-Json - } catch { - $config = @{} - } - $config.maxInstances = $TEAM_PARALLELS_MAX_ALLOWED_MOBILE - $capList = @(); $count = 0 - $maxCaps = [int]([math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_MOBILE * $PARALLEL_PERCENTAGE)) - if ($maxCaps -lt 1) { $maxCaps = 1 } - foreach ($template in $MOBILE_DEVICE_TEMPLATES) { - if ($count -ge $maxCaps) { break } - $parts = $template -split '\|' - $deviceName = $parts[1]; $baseVer = [int]$parts[2] - foreach ($delta in @(0, -1)) { - if ($count -ge $maxCaps) { break } - $version = $baseVer + $delta - $capList += @{ device = $deviceName; "os_version" = "$version.0" } - $count++ - if ($count -ge $maxCaps) { break } - } - } - $config.capabilities = $capList - ($config | ConvertTo-Json -Depth 5) | Set-Content $confFile - Pop-Location - Set-Location -Path "android" - $env:BROWSERSTACK_USERNAME = $BS_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BS_ACCESS_KEY - $env:BROWSERSTACK_LOCAL = $useLocal.ToString().ToLower() - if ($useLocal) { - & npm run local *> "$BSS_SETUP_DIR\mobile_run_result.log" 2>&1 - } else { - & npm run parallel *> "$BSS_SETUP_DIR\mobile_run_result.log" 2>&1 - } + + if (!(Test-Path $WEB_LOG)) { + $content = "" + } else { + $content = Get-Content $WEB_LOG -Raw } - if (Test-Path "$BSS_SETUP_DIR\mobile_run_result.log") { - Get-Content "$BSS_SETUP_DIR\mobile_run_result.log" | Add-Content $LOG_FILE + + $LOCAL_FAILURE = $false + $SETUP_FAILURE = $false + + foreach ($p in $WEB_LOCAL_ERRORS) { if ($p -and ($content -match $p)) { $LOCAL_FAILURE = $true; break } } + foreach ($p in $WEB_SETUP_ERRORS) { if ($p -and ($content -match $p)) { $SETUP_FAILURE = $true; break } } + + if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { $success = $true } + + if ($success) { + Log-Line "✅ Web setup succeeded" $WEB_LOG; break + } elseif ($LOCAL_FAILURE -and $attempt -eq 1) { + $localFlag = $false + $attempt++ + Log-Line "⚠️ Web test failed due to Local tunnel error. Retrying without browserstackLocal..." $WEB_LOG + } elseif ($SETUP_FAILURE) { + Log-Line "❌ Web test failed due to setup error. Check logs at: $WEB_LOG" $WEB_LOG + break + } else { + Log-Line "❌ Web setup ended without success; check $WEB_LOG for details" $WEB_LOG + break } - Set-Location -Path $BSS_SETUP_DIR + } } -# Step 8: Run the appropriate setups based on selection -if ($TEST_OPTION -eq "Web Testing" -and $WEB_PLAN_FETCHED) { - # Run Web tests (with retry logic) - $webSuccess = $false; $attempt = 1 - while ($attempt -le 2 -and -not $webSuccess) { - if ($ShowLogs) { - Write-Host "`n⏳ Running Web tests (Attempt $attempt, browserstackLocal=$($attempt -eq 1))..." - } else { - Write-Host "`n⏳ Please hold on while we prepare the next step in the background..." - } - $useLocalFlag = ($attempt -eq 1) - Write-Log "[Web Setup Attempt $attempt] browserstackLocal: $($useLocalFlag.ToString().ToLower())" - Run-WebTests -useLocal:$useLocalFlag - $logContent = Get-Content "$BSS_SETUP_DIR\web_run_result.log" -Raw - $localFailure = $WEB_LOCAL_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $setupFailure = $WEB_SETUP_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $hasSessionLink = ($logContent -match 'https://.+browserstack\.com.+') - if ($hasSessionLink) { - $webSuccess = $true; break - } elseif ($localFailure -and $attempt -eq 1) { - Write-Host "❌ Web test failed due to Local tunnel error. Retrying without Local..." - $attempt++; continue - } elseif ($setupFailure) { - Write-Host "❌ Web test failed due to setup error. Check logs for details." - break - } else { - break - } + +function Setup-Mobile { + Log-Line "Starting Mobile setup for $TECH_STACK" $MOBILE_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 2) { + 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 } + "JS" { Log-Line "JS path not enabled by current Tech Stack chooser; add if needed." $MOBILE_LOG } + default { Log-Line "Unknown TECH_STACK: $TECH_STACK" $MOBILE_LOG; return } } - if ($webSuccess) { - $buildUrl = Select-String -Path $LOG_FILE -Pattern 'https://[A-Za-z0-9./?=_-]*browserstack\.com[A-Za-z0-9./?=_-]*' | - Select-Object -Last 1 -ExpandProperty Line - Write-Host "✅ Web test run completed. View your tests here:`n👉 $buildUrl" + + if (!(Test-Path $MOBILE_LOG)) { + $content = "" } else { - $logPath = (Resolve-Path "$BSS_SETUP_DIR\web_run_result.log").Path - Write-Host "❌ Final Web setup failed.`n Check logs at: $logPath`n If the issue persists, contact support@browserstack.com" - } -} -elseif ($TEST_OPTION -eq "Mobile App Testing" -and $MOBILE_PLAN_FETCHED) { - # Run Mobile tests (with retry logic) - $mobileSuccess = $false; $attempt = 1 - while ($attempt -le 2 -and -not $mobileSuccess) { - if ($ShowLogs) { - Write-Host "`n⏳ Running Mobile tests (Attempt $attempt, browserstackLocal=$($attempt -eq 1))..." - } else { - Write-Host "`n⏳ Please hold on while we prepare the next step in the background..." - } - $useLocalFlag = ($attempt -eq 1) - Write-Log "[Mobile Setup Attempt $attempt] browserstackLocal: $($useLocalFlag.ToString().ToLower())" - Run-MobileTests -useLocal:$useLocalFlag - $logContent = Get-Content "$BSS_SETUP_DIR\mobile_run_result.log" -Raw - $localFailure = $MOBILE_LOCAL_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $setupFailure = $MOBILE_SETUP_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $hasSessionLink = ($logContent -match 'https://.+browserstack\.com.+') - if ($hasSessionLink) { - $mobileSuccess = $true; break - } elseif ($localFailure -and $attempt -eq 1) { - Write-Host "❌ Mobile test failed due to Local tunnel error. Retrying without Local..." - $attempt++; continue - } elseif ($setupFailure) { - Write-Host "❌ Mobile test failed due to setup error. Check logs for details." - break - } else { - break - } + $content = Get-Content $MOBILE_LOG -Raw } - if ($mobileSuccess) { - $buildUrl = Select-String -Path $LOG_FILE -Pattern 'https://[A-Za-z0-9./?=_-]*browserstack\.com[A-Za-z0-9./?=_-]*' | - Select-Object -Last 1 -ExpandProperty Line - Write-Host "✅ Mobile test run completed. View your tests here:`n👉 $buildUrl" + + $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 } } + + if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { $success = $true } + + if ($success) { + Log-Line "✅ Mobile setup succeeded" $MOBILE_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 + } elseif ($SETUP_FAILURE) { + Log-Line "❌ Mobile test failed due to setup error. Check logs at: $MOBILE_LOG" $MOBILE_LOG + break } else { - $logPath = (Resolve-Path "$BSS_SETUP_DIR\mobile_run_result.log").Path - Write-Host "❌ Final Mobile setup failed.`n Check logs at: $logPath`n If the issue persists, contact support@browserstack.com" + Log-Line "❌ Mobile setup ended without success; check $MOBILE_LOG for details" $MOBILE_LOG + break } + } } -elseif ($TEST_OPTION -eq "Both") { - $ranAny = $false - if ($WEB_PLAN_FETCHED) { - Write-Host "=== Executing Web Testing ===" - # Run Web tests (same logic as above) - $webSuccess = $false; $attempt = 1 - while ($attempt -le 2 -and -not $webSuccess) { - if ($ShowLogs) { - Write-Host "`n⏳ Running Web tests (Attempt $attempt, browserstackLocal=$($attempt -eq 1))..." - } else { - Write-Host "`n⏳ Please hold on while we prepare the next step in the background..." - } - $useLocalFlag = ($attempt -eq 1) - Write-Log "[Web Setup Attempt $attempt] browserstackLocal: $($useLocalFlag.ToString().ToLower())" - Run-WebTests -useLocal:$useLocalFlag - $logContent = Get-Content "$BSS_SETUP_DIR\web_run_result.log" -Raw - $localFailure = $WEB_LOCAL_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $setupFailure = $WEB_SETUP_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $hasSessionLink = ($logContent -match 'https://.+browserstack\.com.+') - if ($hasSessionLink) { - $webSuccess = $true; break - } elseif ($localFailure -and $attempt -eq 1) { - Write-Host "❌ Web test failed due to Local tunnel error. Retrying without Local..."; $attempt++; continue - } elseif ($setupFailure) { - Write-Host "❌ Web test failed due to setup error. Check logs." - break - } else { - break - } - } - if ($webSuccess) { - $buildUrl = Select-String -Path $LOG_FILE -Pattern 'https://[A-Za-z0-9./?=_-]*browserstack\.com[A-Za-z0-9./?=_-]*' | - Select-Object -Last 1 -ExpandProperty Line - Write-Host "✅ Web test run completed. See results:`n👉 $buildUrl" - } else { - $logPath = (Resolve-Path "$BSS_SETUP_DIR\web_run_result.log").Path - Write-Host "❌ Web tests failed. Check logs at $logPath" - } - $ranAny = $true - } else { - Write-Host "⚠️ Skipping Web setup (Automate plan not available)." + + +# ===== 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 + switch ($TEST_TYPE) { + "Web" { + if ($WEB_PLAN_FETCHED) { Setup-Web } else { Log-Line "⚠️ Skipping Web setup — Web plan not fetched" $GLOBAL_LOG } } - if ($MOBILE_PLAN_FETCHED) { - Write-Host "`n=== Executing Mobile App Testing ===" - $mobileSuccess = $false; $attempt = 1 - while ($attempt -le 2 -and -not $mobileSuccess) { - if ($ShowLogs) { - Write-Host "`n⏳ Running Mobile tests (Attempt $attempt, browserstackLocal=$($attempt -eq 1))..." - } else { - Write-Host "`n⏳ Please hold on while we prepare the next step in the background..." - } - $useLocalFlag = ($attempt -eq 1) - Write-Log "[Mobile Setup Attempt $attempt] browserstackLocal: $($useLocalFlag.ToString().ToLower())" - Run-MobileTests -useLocal:$useLocalFlag - $logContent = Get-Content "$BSS_SETUP_DIR\mobile_run_result.log" -Raw - $localFailure = $MOBILE_LOCAL_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $setupFailure = $MOBILE_SETUP_ERRORS | ForEach-Object { if ($logContent -match $_) { $_; break } } - $hasSessionLink = ($logContent -match 'https://.+browserstack\.com.+') - if ($hasSessionLink) { - $mobileSuccess = $true; break - } elseif ($localFailure -and $attempt -eq 1) { - Write-Host "❌ Mobile test failed due to Local tunnel error. Retrying without Local..."; $attempt++; continue - } elseif ($setupFailure) { - Write-Host "❌ Mobile test failed due to setup error. Check logs." - break - } else { - break - } - } - if ($mobileSuccess) { - $buildUrl = Select-String -Path $LOG_FILE -Pattern 'https://[A-Za-z0-9./?=_-]*browserstack\.com[A-Za-z0-9./?=_-]*' | - Select-Object -Last 1 -ExpandProperty Line - Write-Host "✅ Mobile test run completed. See results:`n👉 $buildUrl" - } else { - $logPath = (Resolve-Path "$BSS_SETUP_DIR\mobile_run_result.log").Path - Write-Host "❌ Mobile tests failed. Check logs at $logPath" - } - $ranAny = $true - } else { - Write-Host "⚠️ Skipping Mobile setup (App Automate plan not available)." + "App" { + if ($MOBILE_PLAN_FETCHED) { Setup-Mobile } else { Log-Line "⚠️ Skipping Mobile setup — Mobile plan not fetched" $GLOBAL_LOG } } - if (-not $ranAny) { - Write-Host "❌ Both Web and Mobile setups were skipped due to plan availability. Exiting." - exit 1 + "Both" { + $ranAny = $false + if ($WEB_PLAN_FETCHED) { Setup-Web; $ranAny = $true } else { Log-Line "⚠️ Skipping Web setup — Web plan not fetched" $GLOBAL_LOG } + if ($MOBILE_PLAN_FETCHED) { Setup-Mobile; $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" } } -} \ No newline at end of file + default { Log-Line "❌ Invalid TEST_TYPE: $TEST_TYPE" $GLOBAL_LOG; throw "Invalid TEST_TYPE" } + } +} + +# ===== Main ===== +try { + Ensure-Workspace + Ask-BrowserStack-Credentials + Ask-Test-Type + Ask-Tech-Stack + Validate-Tech-Stack + Fetch-Plan-Details + + 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 + Run-Setup + Log-Line "Setup run finished" $GLOBAL_LOG +} catch { + Log-Line "❌ Fatal: $($_.Exception.Message)" $GLOBAL_LOG + throw +} From c8ad7587295c6bc912c8fb2167283f01a507cd5b Mon Sep 17 00:00:00 2001 From: himanshu-02 Date: Thu, 6 Nov 2025 00:13:09 +0530 Subject: [PATCH 8/8] optimisations for windows --- win/run.ps1 | 833 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 673 insertions(+), 160 deletions(-) diff --git a/win/run.ps1 b/win/run.ps1 index fe369be..6ead69c 100644 --- a/win/run.ps1 +++ b/win/run.ps1 @@ -64,10 +64,10 @@ $WEB_PLATFORM_TEMPLATES = @( "Windows|11|Edge", "Windows|11|Chrome", "Windows|8|Chrome", - "OS X|Monterey|Safari", + #"OS X|Monterey|Safari", "OS X|Monterey|Chrome", "OS X|Ventura|Chrome", - "OS X|Big Sur|Safari", + #"OS X|Big Sur|Safari", "OS X|Catalina|Firefox" ) @@ -194,7 +194,7 @@ $MOBILE_TIER4 = @( "android|Google Pixel 8 Pro|14", "android|Google Pixel 7 Pro|13", "android|Google Pixel 5|11", - "OnePlus 13R|15", + "android|OnePlus 13R|15", "android|OnePlus 12R|14", "android|OnePlus 11R|13", "android|OnePlus 9|11", @@ -324,15 +324,47 @@ function Invoke-External { $p = New-Object System.Diagnostics.Process $p.StartInfo = $psi - [void]$p.Start() - $stdout = $p.StandardOutput.ReadToEnd() - $stderr = $p.StandardError.ReadToEnd() - $p.WaitForExit() - + + # Stream output to log file in real-time if LogFile is specified if ($LogFile) { - if ($stdout) { Add-Content -Path $LogFile -Value $stdout } - if ($stderr) { Add-Content -Path $LogFile -Value $stderr } + # 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 } @@ -391,6 +423,73 @@ function Invoke-Py { 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( @@ -400,24 +499,25 @@ function Show-InputBox { ) $form = New-Object System.Windows.Forms.Form $form.Text = $Title - $form.Size = New-Object System.Drawing.Size(500,160) + $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,50) + $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,80) + $okButton.Location = New-Object System.Drawing.Point(380,($textBox.Bottom + 20)) $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() }) $form.Controls.Add($okButton) @@ -433,24 +533,25 @@ function Show-PasswordBox { ) $form = New-Object System.Windows.Forms.Form $form.Text = $Title - $form.Size = New-Object System.Drawing.Size(500,160) + $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,50) + $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,80) + $okButton.Location = New-Object System.Drawing.Point(380,($textBox.Bottom + 20)) $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() }) $form.Controls.Add($okButton) @@ -597,12 +698,12 @@ function Show-OpenFileDialog { # ===== Baseline interactions ===== function Ask-BrowserStack-Credentials { - $script:BROWSERSTACK_USERNAME = Show-InputBox -Title "BrowserStack Setup" -Prompt "Enter your BrowserStack Username:" -DefaultText "" + $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:" + $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" @@ -631,7 +732,7 @@ function Ask-Test-Type { function Ask-Tech-Stack { $choice = Show-ClickChoice -Title "Tech Stack" ` -Prompt "Select your installed language / framework:" ` - -Choices @("Java","Python") ` + -Choices @("Java","Python","NodeJS") ` -DefaultChoice "Java" if ([string]::IsNullOrWhiteSpace($choice)) { throw "No tech stack selected" } $script:TECH_STACK = $choice @@ -642,10 +743,12 @@ 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 @@ -655,24 +758,47 @@ function Validate-Tech-Stack { ($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 ("✅ Python detected: {0}" -f ( ($PY_CMD -join ' ') )) $GLOBAL_LOG + Log-Line ("✅ Python3 is installed: {0}" -f ( ($PY_CMD -join ' ') )) $GLOBAL_LOG } else { throw "Python present but failed to execute" } } catch { - Log-Line "❌ Python exists but failed to run." $GLOBAL_LOG + Log-Line "❌ Python3 exists but failed to run." $GLOBAL_LOG throw } } - "JS" { - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Log-Line "❌ Node.js not found." $GLOBAL_LOG; throw "Node not found" } - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { Log-Line "❌ npm not found." $GLOBAL_LOG; throw "npm not found" } - Log-Line "✅ Node.js: $(& node -v) ; npm: $(& npm -v)" $GLOBAL_LOG + "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" } } @@ -703,13 +829,28 @@ function Get-BasicAuthHeader { } function Ask-And-Upload-App { - $path = Show-OpenFileDialog -Title "📱 Select your .apk or .ipa (Cancel = use default sample)" + # 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" } @@ -718,9 +859,27 @@ function Ask-And-Upload-App { } Log-Line "⬆️ Uploading $path to BrowserStack..." $GLOBAL_LOG - $headers = @{ Authorization = (Get-BasicAuthHeader -User $BROWSERSTACK_USERNAME -Key $BROWSERSTACK_ACCESS_KEY) } - $form = @{ file = Get-Item -Path $path } - $resp = Invoke-RestMethod -Method Post -Uri "https://api-cloud.browserstack.com/app-automate/upload" -Headers $headers -Form $form + + # 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 @@ -782,10 +941,51 @@ function Generate-Mobile-Platforms-Yaml { 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) { @@ -805,7 +1005,23 @@ function Generate-Web-Caps-Json { } if ($count -ge $max) { break } } - return ($items | ConvertTo-Json -Depth 5) + + # 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 ===== @@ -847,74 +1063,74 @@ function Fetch-Plan-Details { function Setup-Web-Java { param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - $REPO = "browserstack-examples-testng" + $REPO = "now-testng-browserstack" $TARGET = Join-Path $GLOBAL_DIR $REPO - if (!(Test-Path $TARGET)) { - Log-Line "📦 Cloning repo $REPO into $TARGET" $GLOBAL_LOG - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $WEB_LOG - } else { - Log-Line "📂 Repo $REPO already exists at $TARGET, skipping clone." $GLOBAL_LOG + 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 { - Validate-Tech-Stack - - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME - $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY - - $env:BROWSERSTACK_CONFIG_FILE = "src/test/resources/conf/capabilities/bstack-parallel.yml" - - # Replace base URL in TestBase.java (and ensure no BOM) - $repoRoot = $TARGET - $tb = Get-ChildItem -Path $repoRoot -Recurse -Filter TestBase.java -ErrorAction SilentlyContinue | - Select-Object -First 1 -ExpandProperty FullName + # Update Base URL + $files = Get-ChildItem -Path $TARGET -Recurse -Filter *.* -File | Where-Object { $_.Extension -match '\.(java|xml|properties)$' } + foreach ($file in $files) { + $content = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue + if ($content -and $content -match "https://www\.bstackdemo\.com") { + $content = $content -replace "https://www\.bstackdemo\.com", $CX_TEST_URL + Set-ContentNoBom -Path $file.FullName -Value $content + Log-Line "🌐 Updated base URL in $($file.Name)" $GLOBAL_LOG + } + } - if ($tb) { - $c = [System.IO.File]::ReadAllText($tb) - if ([string]::IsNullOrWhiteSpace($CX_TEST_URL)) { $CX_TEST_URL = "https://bstackdemo.com" } - $c = $c.Replace("https://bstackdemo.com", $CX_TEST_URL) - Set-ContentNoBom -Path $tb -Value $c + # Check if domain is private + if (Test-DomainPrivate) { + $UseLocal = $true + } - # Hard strip BOM if any stray - $bytes = [System.IO.File]::ReadAllBytes($tb) - if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { - [System.IO.File]::WriteAllBytes($tb, $bytes[3..($bytes.Length-1)]) - } - Log-Line "🔧 Updated URL and ensured UTF-8 no BOM in: $tb" $GLOBAL_LOG + # Log local flag status + if ($UseLocal) { + Log-Line "✅ BrowserStack Local is ENABLED for this run." $GLOBAL_LOG } else { - Log-Line "❌ Could not find TestBase.java under $repoRoot" $GLOBAL_LOG + Log-Line "✅ BrowserStack Local is DISABLED for this run." $GLOBAL_LOG } - Log-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $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: browserstack-sample-java-web +buildName: now-testng-java-web projectName: NOW-Web-Test percy: true accessibility: true platforms: $platforms parallelsPerPlatform: $ParallelsPerPlatform -"@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE +"@ + + 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 install -DskipTests'" $GLOBAL_LOG - Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + Log-Line "⚙️ Running '$mvn compile'" $GLOBAL_LOG + [void](Invoke-External -Exe $mvn -Arguments @("compile") -LogFile $LogFile -WorkingDirectory $TARGET) - Log-Line "🚀 Running '$mvn clean test -P bstack-parallel -Dtest=OrderTest'" $GLOBAL_LOG - Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("clean","test","-P","bstack-parallel","-Dtest=OrderTest") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + 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) } } @@ -922,20 +1138,19 @@ parallelsPerPlatform: $ParallelsPerPlatform function Setup-Web-Python { param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - $REPO = "browserstack-examples-pytest" + $REPO = "now-pytest-browserstack" $TARGET = Join-Path $GLOBAL_DIR $REPO - if (!(Test-Path $TARGET)) { - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Branch "sdk" -Target $TARGET -LogFile $WEB_LOG - Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - } else { - Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + 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 { - Validate-Tech-Stack - if (-not $PY_CMD -or $PY_CMD.Count -eq 0) { Set-PythonCmd } $venv = Join-Path $TARGET "venv" if (!(Test-Path $venv)) { @@ -950,6 +1165,18 @@ function Setup-Web-Python { $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" } @@ -961,7 +1188,7 @@ framework: pytest browserstackLocal: $localFlag buildName: browserstack-sample-python-web projectName: NOW-Web-Test -# percy: true #TODO: Uncomment this when percy issue is fixed +percy: true accessibility: true platforms: $platforms @@ -970,26 +1197,78 @@ parallelsPerPlatform: $ParallelsPerPlatform Log-Line "✅ Updated root-level browserstack.yml with platforms and credentials" $GLOBAL_LOG - # Update demo URL in e2e if present (no BOM write) - $e2eRel = "src/test/suites/e2e.py" - $e2eFull = Join-Path $TARGET $e2eRel - if (Test-Path $e2eFull) { - $c = [System.IO.File]::ReadAllText($e2eFull) - $c = $c.Replace("https://bstackdemo.com/", $CX_TEST_URL) - Set-ContentNoBom -Path $e2eFull -Value $c - Log-Line "🔧 Updated URL in $e2eRel" $GLOBAL_LOG - } else { - Log-Line "ℹ️ Skipping URL update: $e2eRel not found in repo" $GLOBAL_LOG + # Update base URL in test file + $testFile = "tests\bstack-sample-test.py" + $testFileFull = Join-Path $TARGET $testFile + if (Test-Path $testFileFull) { + $c = [System.IO.File]::ReadAllText($testFileFull) + $c = $c.Replace("https://bstackdemo.com", $CX_TEST_URL) + Set-ContentNoBom -Path $testFileFull -Value $c + Log-Line "🌐 Updated base URL in tests/bstack-sample-test.py to: $CX_TEST_URL" $GLOBAL_LOG } $sdk = Join-Path $venv "Scripts\browserstack-sdk.exe" - # Run exactly: browserstack-sdk pytest -s src/test/suites/e2e.py (pytest must be on PATH) - $args = @('pytest','-s','src/test/suites/e2e.py') - Log-Line "⚠️ Running tests with local=$localFlag" $GLOBAL_LOG - [void](Invoke-External -Exe $sdk -Arguments $args -LogFile $LogFile -WorkingDirectory $TARGET) + 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 + $env:BROWSERSTACK_LOCAL = if ($UseLocal) { "true" } else { "false" } + + 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) } } @@ -997,21 +1276,24 @@ parallelsPerPlatform: $ParallelsPerPlatform function Setup-Mobile-Python { param([bool]$UseLocal, [int]$ParallelsPerPlatform, [string]$LogFile) - $REPO = "browserstack-examples-pytest-BDD-appium" + $REPO = "pytest-appium-app-browserstack" $TARGET = Join-Path $GLOBAL_DIR $REPO - if (!(Test-Path $TARGET)) { - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - } else { - Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + 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" - [void](Invoke-Py -Arguments @("-m","venv",$venv) -LogFile $LogFile -WorkingDirectory $TARGET) + 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 @@ -1020,10 +1302,13 @@ function Setup-Mobile-Python { $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" } + # 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 @@ -1033,20 +1318,103 @@ 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: -$platforms -"@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE +$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-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $GLOBAL_LOG - Log-Line "🚀 Running 'browserstack-sdk pytest -s tests/test_wikipedia.py'" $GLOBAL_LOG + # 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" - # Run exactly: browserstack-sdk pytest -s tests/test_wikipedia.py (pytest must be on PATH) - [void](Invoke-External -Exe $sdk -Arguments @('pytest','-s','tests/test_wikipedia.py') -LogFile $LogFile -WorkingDirectory $TARGET) + 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) } } @@ -1057,13 +1425,14 @@ function Setup-Mobile-Java { $REPO = "browserstack-examples-appium-testng" $TARGET = Join-Path $GLOBAL_DIR $REPO - if (!(Test-Path $TARGET)) { - Invoke-GitClone -Url "https://github.com/BrowserStackCE/$REPO.git" -Target $TARGET -LogFile $MOBILE_LOG - Log-Line "✅ Cloned repository: $REPO into $TARGET" $GLOBAL_LOG - } else { - Log-Line "ℹ️ Repository already exists at: $TARGET (skipping clone)" $GLOBAL_LOG + 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 + # Update pom.xml sdk version to LATEST (matches mac script) $pom = Join-Path $TARGET "pom.xml" if (Test-Path $pom) { @@ -1075,15 +1444,13 @@ function Setup-Mobile-Java { Push-Location $TARGET try { - Validate-Tech-Stack - $env:BROWSERSTACK_USERNAME = $BROWSERSTACK_USERNAME $env:BROWSERSTACK_ACCESS_KEY = $BROWSERSTACK_ACCESS_KEY # Update driver init to AndroidDriver (parity with bash) - $testBase = Get-ChildItem -Recurse -Filter "TestBase.java" | Select-Object -First 1 + $testBase = Get-ChildItem -Path "src" -Recurse -Filter "TestBase.java" -ErrorAction SilentlyContinue | Select-Object -First 1 if ($testBase) { - (Get-Content $testBase.FullName) -replace 'new AppiumDriver\(', 'new AndroidDriver(' | Set-Content $testBase.FullName + (Get-Content $testBase.FullName -Raw) -replace 'new AppiumDriver\(', 'new AndroidDriver(' | Set-Content $testBase.FullName Log-Line "🔧 Updated driver initialization in $($testBase.FullName) to use AndroidDriver" $GLOBAL_LOG } @@ -1106,66 +1473,137 @@ platforms: $platforms "@ | Set-Content $env:BROWSERSTACK_CONFIG_FILE - Log-Line ("⚠️ BrowserStack Local is {0} for this run." -f ($(if($UseLocal){"ENABLED"}else{"DISABLED"}))) $GLOBAL_LOG + Log-Line "✅ Updated $env:BROWSERSTACK_CONFIG_FILE with platforms and credentials" $GLOBAL_LOG + + # 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 $TARGET Log-Line "⚙️ Running '$mvn install -DskipTests'" $GLOBAL_LOG - Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + [void](Invoke-External -Exe $mvn -Arguments @("install","-DskipTests") -LogFile $LogFile -WorkingDirectory $TARGET) Log-Line "🚀 Running '$mvn clean test -P bstack-parallel -Dtest=OrderTest'" $GLOBAL_LOG - Push-Location $TARGET; try { [void](Invoke-External -Exe $mvn -Arguments @("clean","test","-P","bstack-parallel","-Dtest=OrderTest") -LogFile $LogFile -WorkingDirectory $TARGET) } finally { Pop-Location } + [void](Invoke-External -Exe $mvn -Arguments @("clean","test","-P","bstack-parallel","-Dtest=OrderTest") -LogFile $LogFile -WorkingDirectory $TARGET) } 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" -Branch "sdk" -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 = $true + $localFlag = $false $attempt = 1 - $success = $false + $success = $true $totalParallels = [int]([Math]::Floor($TEAM_PARALLELS_MAX_ALLOWED_WEB * $PARALLEL_PERCENTAGE)) if ($totalParallels -lt 1) { $totalParallels = 1 } $parallelsPerPlatform = $totalParallels - while ($attempt -le 2) { - Log-Line "[Web Setup Attempt $attempt] browserstackLocal: $localFlag" $WEB_LOG + while ($attempt -le 1) { + Log-Line "[Web Setup]" $WEB_LOG switch ($TECH_STACK) { - "Java" { Setup-Web-Java -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG } - "Python" { Setup-Web-Python -UseLocal:$localFlag -ParallelsPerPlatform $parallelsPerPlatform -LogFile $WEB_LOG } - "JS" { Log-Line "JS path not enabled by current Tech Stack chooser; add if needed." $WEB_LOG } + "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 (!(Test-Path $WEB_LOG)) { - $content = "" - } else { - $content = Get-Content $WEB_LOG -Raw - } - - $LOCAL_FAILURE = $false - $SETUP_FAILURE = $false - - foreach ($p in $WEB_LOCAL_ERRORS) { if ($p -and ($content -match $p)) { $LOCAL_FAILURE = $true; break } } - foreach ($p in $WEB_SETUP_ERRORS) { if ($p -and ($content -match $p)) { $SETUP_FAILURE = $true; break } } - - if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { $success = $true } - if ($success) { - Log-Line "✅ Web setup succeeded" $WEB_LOG; break - } elseif ($LOCAL_FAILURE -and $attempt -eq 1) { - $localFlag = $false - $attempt++ - Log-Line "⚠️ Web test failed due to Local tunnel error. Retrying without browserstackLocal..." $WEB_LOG - } elseif ($SETUP_FAILURE) { - Log-Line "❌ Web test failed due to setup error. Check logs at: $WEB_LOG" $WEB_LOG + 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 } } @@ -1174,6 +1612,9 @@ function Setup-Web { 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 @@ -1183,15 +1624,18 @@ function Setup-Mobile { if ($totalParallels -lt 1) { $totalParallels = 1 } $parallelsPerPlatform = $totalParallels - while ($attempt -le 2) { + 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 } - "JS" { Log-Line "JS path not enabled by current Tech Stack chooser; add if needed." $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 { @@ -1204,19 +1648,30 @@ function Setup-Mobile { 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 } } - if ($content -match 'https://[a-zA-Z0-9./?=_-]*browserstack\.com') { $success = $true } + # 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; break + 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 } } @@ -1226,21 +1681,71 @@ function Setup-Mobile { # ===== 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 } else { Log-Line "⚠️ Skipping Web setup — Web plan not fetched" $GLOBAL_LOG } + 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 } else { Log-Line "⚠️ Skipping Mobile setup — Mobile plan not fetched" $GLOBAL_LOG } + 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; $ranAny = $true } else { Log-Line "⚠️ Skipping Web setup — Web plan not fetched" $GLOBAL_LOG } - if ($MOBILE_PLAN_FETCHED) { Setup-Mobile; $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" } + 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" } - 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://automate.browserstack.com/ (Web) | https://app-automate.browserstack.com/ (Mobile)" $GLOBAL_LOG + Log-Line "========================================" $GLOBAL_LOG } # ===== Main ===== @@ -1254,8 +1759,16 @@ try { 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 Run-Setup - Log-Line "Setup run finished" $GLOBAL_LOG } catch { - Log-Line "❌ Fatal: $($_.Exception.Message)" $GLOBAL_LOG + Log-Line " " $GLOBAL_LOG + Log-Line "========================================" $GLOBAL_LOG + Log-Line "❌ EXECUTION FAILED" $GLOBAL_LOG + Log-Line "========================================" $GLOBAL_LOG + Log-Line "Error: $($_.Exception.Message)" $GLOBAL_LOG + Log-Line "Check logs for details:" $GLOBAL_LOG + Log-Line " Global: $GLOBAL_LOG" $GLOBAL_LOG + Log-Line " Web: $WEB_LOG" $GLOBAL_LOG + Log-Line " Mobile: $MOBILE_LOG" $GLOBAL_LOG + Log-Line "========================================" $GLOBAL_LOG throw -} +} \ No newline at end of file