Files
pi-skill/skills/qa-automation/qa.config.sh
2026-05-25 16:41:08 +07:00

322 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ╔═══════════════════════════════════════════════════════════════════╗
# ║ qa.config.sh — Central Configuration for QA Automation Skills ║
# ╠═══════════════════════════════════════════════════════════════════╣
# ║ Source this file at the top of every QA script: ║
# ║ source "$(dirname "$0")/../qa.config.sh" ║
# ║ ║
# ║ Override any variable by exporting it before sourcing: ║
# ║ export APP_BUNDLE_ID="com.myapp.dev" ║
# ║ source qa.config.sh ║
# ║ ║
# ║ Or create a local override file: ║
# ║ qa.config.local.sh (gitignored, sourced automatically) ║
# ╚═══════════════════════════════════════════════════════════════════╝
set -euo pipefail
# ── Resolve paths ────────────────────────────────────────────────────
QA_AUTOMATION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── App Configuration ────────────────────────────────────────────────
# Bundle/package identifier for your app (dev build)
export APP_BUNDLE_ID="${APP_BUNDLE_ID:-com.example.app.dev}"
# Bundle/package identifier for production builds
export APP_BUNDLE_ID_PROD="${APP_BUNDLE_ID_PROD:-com.example.app}"
# Project root directory (where package.json lives)
export PROJECT_DIR="${PROJECT_DIR:-$(pwd)}"
# ── iOS Simulator ────────────────────────────────────────────────────
# Simulator UDID — set to "auto" to detect the first booted simulator
export SIMULATOR_UDID="${SIMULATOR_UDID:-auto}"
# Simulator device name (used when creating/finding simulators)
export SIMULATOR_DEVICE_NAME="${SIMULATOR_DEVICE_NAME:-iPhone 16 Pro}"
# ── Android Emulator ─────────────────────────────────────────────────
# Android AVD name
export ANDROID_AVD="${ANDROID_AVD:-Pixel_8}"
# Android serial (usually emulator-5554)
export ANDROID_SERIAL="${ANDROID_SERIAL:-emulator-5554}"
# Android SDK paths
export ANDROID_SDK="${ANDROID_SDK:-$HOME/Library/Android/sdk}"
export ADB_PATH="${ADB_PATH:-$ANDROID_SDK/platform-tools/adb}"
export EMULATOR_PATH="${EMULATOR_PATH:-$ANDROID_SDK/emulator/emulator}"
# ── Dev Server / Metro ───────────────────────────────────────────────
# Port for the dev server (Metro bundler, Vite, webpack, etc.)
export METRO_PORT="${METRO_PORT:-8081}"
# URL for the dev server
export METRO_URL="${METRO_URL:-http://localhost:${METRO_PORT}}"
# Command to start the dev server (run from PROJECT_DIR)
export DEV_SERVER_CMD="${DEV_SERVER_CMD:-npx expo start --port $METRO_PORT}"
# Health check endpoint (returns 200 when server is ready)
export DEV_SERVER_HEALTH="${DEV_SERVER_HEALTH:-${METRO_URL}/status}"
# ── CDP (Chrome DevTools Protocol) ───────────────────────────────────
# CDP discovery URL (Metro exposes debug targets here)
export CDP_DISCOVERY_URL="${CDP_DISCOVERY_URL:-${METRO_URL}/json}"
# CDP WebSocket URL — set to "auto" for auto-discovery via /json endpoint
export CDP_WS_URL="${CDP_WS_URL:-auto}"
# CDP device ID — set to "auto" to pick from /json response
export CDP_DEVICE_ID="${CDP_DEVICE_ID:-auto}"
# ── Navigation ───────────────────────────────────────────────────────
# React Navigation module ID cache file
export NAV_MODULE_CACHE="${NAV_MODULE_CACHE:-/tmp/qa-nav-module-id}"
# Video/media player module cache file
export VIDEO_MODULE_CACHE="${VIDEO_MODULE_CACHE:-/tmp/qa-video-module-id}"
# Module scan range for auto-discovery (start, end)
export MODULE_SCAN_START="${MODULE_SCAN_START:-0}"
export MODULE_SCAN_END="${MODULE_SCAN_END:-5000}"
# ── Screen Names (configure per app) ────────────────────────────────
# These are used by CDP navigation helpers. Set them to your app's
# React Navigation screen names. Leave empty to skip.
export SCREEN_HOME="${SCREEN_HOME:-HomeScreen}"
export SCREEN_EXPLORE="${SCREEN_EXPLORE:-ExploreScreen}"
export SCREEN_SEARCH="${SCREEN_SEARCH:-SearchScreen}"
export SCREEN_PROFILE="${SCREEN_PROFILE:-ProfileScreen}"
export SCREEN_SETTINGS="${SCREEN_SETTINGS:-SettingsScreen}"
# Tab navigator name (for BottomTab-style navigation)
export TAB_NAVIGATOR_NAME="${TAB_NAVIGATOR_NAME:-BottomTab}"
# ── Coordinate System ────────────────────────────────────────────────
# Override these for your specific device's logical point dimensions.
# Default: iPhone 16 Pro logical resolution (402 × 874 points)
export SCREEN_WIDTH="${SCREEN_WIDTH:-402}"
export SCREEN_HEIGHT="${SCREEN_HEIGHT:-874}"
export SCREEN_SCALE="${SCREEN_SCALE:-3}"
# Tab bar Y position (bottom of screen, above safe area)
export TAB_BAR_Y="${TAB_BAR_Y:-855}"
# Tab positions (x coordinates, left to right)
# Set these to match your app's bottom tab layout
export TAB_1_X="${TAB_1_X:-60}"
export TAB_2_X="${TAB_2_X:-170}"
export TAB_3_X="${TAB_3_X:-290}"
export TAB_4_X="${TAB_4_X:-400}"
export TAB_5_X="${TAB_5_X:-520}"
# Common UI element coordinates
export BACK_BUTTON_X="${BACK_BUTTON_X:-30}"
export BACK_BUTTON_Y="${BACK_BUTTON_Y:-60}"
export SETTINGS_BUTTON_X="${SETTINGS_BUTTON_X:-510}"
export SETTINGS_BUTTON_Y="${SETTINGS_BUTTON_Y:-140}"
# ── Video/Media Player ──────────────────────────────────────────────
# Class name to look for when installing debug hooks
# For expo-video: "VideoPlayer" (looks for m.default.VideoPlayer or m.VideoPlayer)
export VIDEO_PLAYER_CLASS="${VIDEO_PLAYER_CLASS:-VideoPlayer}"
# Global variable name for tracking video player instances
export GLOBAL_PLAYERS_VAR="${GLOBAL_PLAYERS_VAR:-__qaVideoPlayers}"
# Global variable name for feed state debug hook
export GLOBAL_FEED_VAR="${GLOBAL_FEED_VAR:-__qaFeedState}"
# Maximum tracked player instances (prevents memory leaks)
export MAX_TRACKED_PLAYERS="${MAX_TRACKED_PLAYERS:-20}"
# ── Feed/List State (for state persistence tests) ───────────────────
# Property name to test for state persistence (e.g., "isLiked", "isBookmarked", "isInCart")
export STATE_PROPERTY="${STATE_PROPERTY:-isLiked}"
# Counter property that accompanies the state (e.g., "likesCount", "saveCount")
export STATE_COUNTER_PROPERTY="${STATE_COUNTER_PROPERTY:-likesCount}"
# Number of items to scroll through before checking persistence
export STATE_SCROLL_COUNT="${STATE_SCROLL_COUNT:-5}"
# ── Output & Reporting ──────────────────────────────────────────────
# Base directory for test output
export TEST_OUTPUT_DIR="${TEST_OUTPUT_DIR:-/tmp/qa-tests}"
# Screenshot subdirectory
export SCREENSHOT_DIR="${SCREENSHOT_DIR:-$TEST_OUTPUT_DIR/screenshots}"
# Test results log
export RESULTS_FILE="${RESULTS_FILE:-$TEST_OUTPUT_DIR/results.log}"
# ── Timeouts ─────────────────────────────────────────────────────────
# Dev server startup timeout (seconds)
export DEV_SERVER_TIMEOUT="${DEV_SERVER_TIMEOUT:-60}"
# CDP target discovery timeout (seconds)
export CDP_TIMEOUT="${CDP_TIMEOUT:-30}"
# App settle time after launch (seconds)
export APP_SETTLE_TIME="${APP_SETTLE_TIME:-6}"
# agent-device command timeout (seconds)
export DEVICE_CMD_TIMEOUT="${DEVICE_CMD_TIMEOUT:-5}"
# ── agent-device Sessions ───────────────────────────────────────────
# iOS session name
export IOS_SESSION="${IOS_SESSION:-default}"
# Android session name
export ANDROID_SESSION="${ANDROID_SESSION:-android}"
# Active session (set at runtime)
export ACTIVE_SESSION="${ACTIVE_SESSION:-$IOS_SESSION}"
# ── Web Testing (agent-browser) ─────────────────────────────────────
# Base URL for web testing
export WEB_BASE_URL="${WEB_BASE_URL:-http://localhost:3000}"
# Browser session name
export WEB_SESSION="${WEB_SESSION:-qa}"
# Default viewport for web tests
export WEB_VIEWPORT_WIDTH="${WEB_VIEWPORT_WIDTH:-1280}"
export WEB_VIEWPORT_HEIGHT="${WEB_VIEWPORT_HEIGHT:-720}"
# ── Auto-Detection Functions ─────────────────────────────────────────
# Auto-detect simulator UDID (finds first booted iOS simulator)
qa_detect_simulator_udid() {
if [ "$SIMULATOR_UDID" != "auto" ]; then
echo "$SIMULATOR_UDID"
return 0
fi
local udid
udid=$(xcrun simctl list devices 2>/dev/null | grep "Booted" | head -1 | grep -oE '[A-F0-9-]{36}' || echo "")
if [ -n "$udid" ]; then
export SIMULATOR_UDID="$udid"
echo "$udid"
return 0
fi
echo ""
return 1
}
# Auto-detect CDP WebSocket URL from Metro /json endpoint
qa_detect_cdp_ws_url() {
if [ "$CDP_WS_URL" != "auto" ]; then
echo "$CDP_WS_URL"
return 0
fi
local json
json=$(curl -s "$CDP_DISCOVERY_URL" 2>/dev/null || echo "[]")
local ws_url
ws_url=$(echo "$json" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{
try {
const targets=JSON.parse(d);
// Prefer 'Bridgeless' target (Hermes), fall back to first
const hermes=targets.find(t=>t.description && t.description.includes('Bridgeless'));
console.log(hermes ? hermes.webSocketDebuggerUrl : (targets[0]?.webSocketDebuggerUrl || ''));
} catch(e) { console.log(''); }
});
" 2>/dev/null || echo "")
if [ -n "$ws_url" ]; then
export CDP_WS_URL="$ws_url"
echo "$ws_url"
return 0
fi
echo ""
return 1
}
# Auto-detect and cache the React Navigation module ID
qa_detect_nav_module() {
# Check cache first
if [ -f "$NAV_MODULE_CACHE" ]; then
local cached
cached=$(cat "$NAV_MODULE_CACHE" 2>/dev/null || echo "")
if [ -n "$cached" ]; then
echo "$cached"
return 0
fi
fi
local ws_url
ws_url=$(qa_detect_cdp_ws_url)
if [ -z "$ws_url" ]; then
echo ""
return 1
fi
local module_id
module_id=$(cd "$PROJECT_DIR" && node -e "
const WebSocket=require('ws');
const ws=new WebSocket('$ws_url');
ws.on('open',()=>{
const expr=\`
(function(){
var origHandler=globalThis.ErrorUtils?ErrorUtils.getGlobalHandler():null;
var origCE=console.error;
if(globalThis.ErrorUtils)ErrorUtils.setGlobalHandler(function(){});
console.error=function(){};
try{
for(var j=${MODULE_SCAN_START};j<${MODULE_SCAN_END};j++){
try{
var m=__r(j);
if(m&&m.navigationRef&&m.navigationRef.current){
return JSON.stringify({ok:true,moduleId:j});
}
}catch(e){}
}
return JSON.stringify({error:'not found'});
}finally{
if(globalThis.ErrorUtils&&origHandler)ErrorUtils.setGlobalHandler(origHandler);
console.error=origCE;
}
})();
\`;
ws.send(JSON.stringify({id:1,method:'Runtime.evaluate',params:{expression:expr,returnByValue:true}}));
});
ws.on('message',d=>{
const m=JSON.parse(d);
if(m.id===1){console.log(m.result?.result?.value||'');ws.close();process.exit(0);}
});
ws.on('error',()=>{console.log('');process.exit(1)});
setTimeout(()=>{console.log('');process.exit(1)},10000);
" 2>/dev/null || echo "")
local id
id=$(echo "$module_id" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{const o=JSON.parse(d);console.log(o.moduleId||'')}catch(e){console.log('')}});
" 2>/dev/null || echo "")
if [ -n "$id" ]; then
echo "$id" > "$NAV_MODULE_CACHE"
echo "$id"
return 0
fi
echo ""
return 1
}
# ── Source local overrides (if present) ──────────────────────────────
if [ -f "$QA_AUTOMATION_DIR/qa.config.local.sh" ]; then
source "$QA_AUTOMATION_DIR/qa.config.local.sh"
fi
# ── Ensure output directories exist ─────────────────────────────────
mkdir -p "$TEST_OUTPUT_DIR" "$SCREENSHOT_DIR" 2>/dev/null || true