Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi

This commit is contained in:
Kunthawat Greethong
2026-05-25 16:38:02 +07:00
commit 69f7d8bdda
1689 changed files with 342427 additions and 0 deletions

321
skills/qa-automation/qa.config.sh Executable file
View File

@@ -0,0 +1,321 @@
#!/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