Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 203 additions & 159 deletions .github/workflows/test-scripts.yml

Large diffs are not rendered by default.

160 changes: 136 additions & 24 deletions mac/common-utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,33 @@ handle_app_upload() {
log_msg_to "Exported APP_PLATFORM=$APP_PLATFORM"
else
local choice
choice=$(osascript -e '
display dialog "How would you like to select your app?" ¬
with title "BrowserStack App Upload" ¬
with icon note ¬
buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬
default button "Upload my App (.apk/.ipa)"
' 2>/dev/null)

if [[ "$choice" == *"Use Sample App"* ]]; then
if [[ "$NOW_OS" == "macos" ]]; then
choice=$(osascript -e '
display dialog "How would you like to select your app?" ¬
with title "BrowserStack App Upload" ¬
with icon note ¬
buttons {"Use Sample App", "Upload my App (.apk/.ipa)", "Cancel"} ¬
default button "Upload my App (.apk/.ipa)"
' 2>/dev/null)
else
echo "How would you like to select your app?"
select opt in "Use Sample App" "Upload my App (.apk/.ipa)" "Cancel"; do
case $opt in
"Use Sample App") choice="Use Sample App"; break ;;
"Upload my App (.apk/.ipa)") choice="Upload my App"; break ;;
"Cancel") choice="Cancel";
log_error "App upload cancelled by user."; exit 1;;
*)
log_error "App upload cancelled by user."; exit 1;;

esac
done
fi

if [[ "$choice" == "" ]]; then
log_error "App upload cancelled by user."
exit 1
elif [[ "$choice" == *"Use Sample App"* ]]; then
upload_sample_app
app_platform="android"
export APP_PLATFORM="$app_platform"
Expand All @@ -110,7 +128,7 @@ handle_app_upload() {
}

upload_sample_app() {
log_msg_to "⬆️ Uploading sample app to BrowserStack..."
log_info "Uploading sample app to BrowserStack"
local upload_response
upload_response=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
Expand All @@ -135,16 +153,26 @@ upload_custom_app() {
local file_path

# Convert to POSIX path
file_path=$(osascript -e \
'POSIX path of (choose file with prompt "Select your .apk or .ipa file:" of type {"apk", "ipa"})' \
2>/dev/null)
# Convert to POSIX path
if [[ "$NOW_OS" == "macos" ]]; then
file_path=$(osascript -e \
'POSIX path of (choose file with prompt "Select your .apk or .ipa file:" of type {"apk", "ipa"})' \
2>/dev/null)
else
echo "Please enter the full path to your .apk or .ipa file:"
read -r file_path
# Remove quotes if user added them
file_path="${file_path%\"}"
file_path="${file_path#\"}"
fi

# Trim whitespace
file_path="${file_path%"${file_path##*[![:space:]]}"}"

if [ -z "$file_path" ]; then
log_msg_to "❌ No file selected"
return 1
log_error "App upload cancelled by user. No .apk /.ipa file path provided."
exit 1
fi

log_info "Selected file: $file_path"
Expand All @@ -154,11 +182,11 @@ upload_custom_app() {
elif [[ "$file_path" == *.apk ]]; then
app_platform="android"
else
log_msg_to "❌ Invalid file type. Must be .apk or .ipa"
return 1
log_error "❌ Invalid file type. Must be .apk or .ipa"
exit 1
fi

log_msg_to "⬆️ Uploading app to BrowserStack..."
log_info "Uploading app to BrowserStack"
local upload_response
upload_response=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
Expand All @@ -167,8 +195,8 @@ upload_custom_app() {
local app_url
app_url=$(echo "$upload_response" | grep -o '"app_url":"[^"]*' | cut -d'"' -f4)
if [ -z "$app_url" ]; then
log_msg_to "❌ Failed to upload app"
return 1
log_error "❌ Failed to upload app: $upload_response"
exit 1
fi

export BROWSERSTACK_APP=$app_url
Expand Down Expand Up @@ -210,7 +238,7 @@ fetch_plan_details() {
local web_unauthorized=false
local mobile_unauthorized=false

if [[ "$test_type" == "web" || "$test_type" == "both" ]]; then
if [[ "$test_type" == "web" ]]; then
RESPONSE_WEB=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api.browserstack.com/automate/plan.json)
HTTP_CODE_WEB=$(echo "$RESPONSE_WEB" | tail -n1)
RESPONSE_WEB_BODY=$(echo "$RESPONSE_WEB" | sed '$d')
Expand All @@ -225,7 +253,7 @@ fetch_plan_details() {
fi
fi

if [[ "$test_type" == "app" || "$test_type" == "both" ]]; then
if [[ "$test_type" == "app" ]]; then
RESPONSE_MOBILE=$(curl -s -w "\n%{http_code}" -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" https://api-cloud.browserstack.com/app-automate/plan.json)
HTTP_CODE_MOBILE=$(echo "$RESPONSE_MOBILE" | tail -n1)
RESPONSE_MOBILE_BODY=$(echo "$RESPONSE_MOBILE" | sed '$d')
Expand All @@ -243,11 +271,21 @@ fetch_plan_details() {
log_info "Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)"

if [[ "$test_type" == "web" && "$web_unauthorized" == true ]] || \
[[ "$test_type" == "app" && "$mobile_unauthorized" == true ]] || \
[[ "$test_type" == "both" && "$web_unauthorized" == true && "$mobile_unauthorized" == true ]]; then
[[ "$test_type" == "app" && "$mobile_unauthorized" == true ]]; then
log_msg_to "❌ Unauthorized to fetch required plan(s). Exiting."
exit 1
fi

if [[ "$RUN_MODE" == *"--silent"* ]]; then
if [[ "$test_type" == "web" ]]; then
TEAM_PARALLELS_MAX_ALLOWED_WEB=5
export TEAM_PARALLELS_MAX_ALLOWED_WEB=5
else
TEAM_PARALLELS_MAX_ALLOWED_MOBILE=5
export TEAM_PARALLELS_MAX_ALLOWED_MOBILE=5
fi
log_info "Resetting Plan summary: Web $WEB_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_WEB max), Mobile $MOBILE_PLAN_FETCHED ($TEAM_PARALLELS_MAX_ALLOWED_MOBILE max)"
fi
}

# Function to check if IP is private
Expand All @@ -263,14 +301,15 @@ is_private_ip() {
}

is_domain_private() {
local is_cx_domain_private=100
domain=${CX_TEST_URL#*://} # remove protocol
domain=${domain%%/*} # remove everything after first "/"
log_msg_to "Website domain: $domain"
export NOW_WEB_DOMAIN="$CX_TEST_URL"
export CX_TEST_URL="$CX_TEST_URL"

# Resolve domain using Cloudflare DNS
IP_ADDRESS=$(dig +short "$domain" @1.1.1.1 | head -n1)
IP_ADDRESS=$(resolve_ip "$domain")

# Determine if domain is private
if is_private_ip "$IP_ADDRESS"; then
Expand All @@ -284,6 +323,31 @@ is_domain_private() {
return $is_cx_domain_private
}

resolve_ip() {
local domain=$1
local ip=""

# Try dig first (standard on macOS/Linux, optional on Windows)
if command -v dig >/dev/null 2>&1; then
ip=$(dig +short "$domain" @1.1.1.1 | head -n1)
fi

# Try Python if dig failed or missing
if [ -z "$ip" ] && command -v python3 >/dev/null 2>&1; then
ip=$(python3 -c "import socket; print(socket.gethostbyname('$domain'))" 2>/dev/null)
fi

# Try nslookup as last resort (parsing is fragile)
if [ -z "$ip" ] && command -v nslookup >/dev/null 2>&1; then
# Windows/Generic nslookup parsing
# Look for "Address:" or "Addresses:" after "Name:"
# This is a best-effort attempt
ip=$(nslookup "$domain" 2>/dev/null | grep -A 10 "Name:" | grep "Address" | tail -n1 | awk '{print $NF}')
fi

echo "$ip"
}


identify_run_status_java() {
local log_file=$1
Expand Down Expand Up @@ -402,3 +466,51 @@ detect_os() {
export NOW_OS=$response
}

print_env_vars() {
local test_type=$1
local tech_stack=$2
log_section "✅ Environment Variables"
log_info "BrowserStack Username: $BROWSERSTACK_USERNAME"
log_info "BrowserStack Project Name: $BROWSERSTACK_PROJECT_NAME"
log_info "BrowserStack Build: $BROWSERSTACK_BUILD_NAME"

log_info "BrowserStack Custom Local Flag: $BROWSERSTACK_LOCAL_CUSTOM"
log_info "BrowserStack Local Flag: $BROWSERSTACK_LOCAL"
log_info "Parallels per platform: $BSTACK_PARALLELS"

if [ "$tech_stack" = "nodejs" ]; then
log_info "Capabilities JSON: \n$BSTACK_CAPS_JSON"
else
log_info "Platforms: \n$BSTACK_PLATFORMS"
fi

if [ "$test_type" = "app" ]; then
log_info "Native App Endpoint: $BROWSERSTACK_APP"
else
log_info "Web Application Endpoint: $CX_TEST_URL"
fi
}

clean_env_vars() {
log_section "✅ Clean Environment Variables"

# list of variables to unset
vars=(
BSTACK_CAPS_JSON
BSTACK_PLATFORMS
BROWSERSTACK_PROJECT_NAME
BROWSERSTACK_BUILD_NAME
BROWSERSTACK_LOCAL_CUSTOM
BROWSERSTACK_LOCAL
)

# unset each variable safely
for var in "${vars[@]}"; do
unset "$var"
done

log_info "Cleared environment variables."

log_info "Terminating any running BrowserStack Local instances."
pgrep '[B]rowserStack' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 || true
}
16 changes: 11 additions & 5 deletions mac/device-machine-allocation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ WEB_ALL=(
"OS X|Chrome"
"OS X|Safari"
"OS X|Chrome"
"OS X|Safari"
"OS X|Firefox"
"OS X|Safari"
# Tier 1
"ios|iPhone 1[234567]*"
"android|Samsung Galaxy S*"
Expand Down Expand Up @@ -126,16 +124,24 @@ pick_terminal_devices() {
else
bVersionLiteral=""
fi
bVersion="latest$bVersionLiteral"

bVersion="latest$bVersionLiteral"

if [[ "$suffixEntry" == "Safari" ]]; then
bVersion="latest"
else
bVersion=$(( hardcodedBVersion-i ))
fi

if [[ "$platformsListContentFormat" == "yaml" ]]; then
if [[ "$prefixEntry" == "android" || "$prefixEntry" == "ios" ]]; then
yaml+=" - platformName: $prefixEntry
deviceName: $suffixEntry
"
else
yaml+=" - osVersion: $prefixEntry
yaml+=" - os: $prefixEntry
browserName: $suffixEntry
browserVersion: $(( hardcodedBVersion-i ))
browserVersion: $bVersion
"
fi

Expand Down
Loading