Files
2026-05-25 16:41:08 +07:00

300 lines
14 KiB
Bash
Executable File

#!/bin/bash
# ╔═══════════════════════════════════════════════════════════════════╗
# ║ Example State Persistence Test ║
# ╠═══════════════════════════════════════════════════════════════════╣
# ║ Tests: mutate state → scroll away → scroll back → verify state ║
# ║ ║
# ║ CUSTOMIZE: Set STATE_PROPERTY and SCREEN_EXPLORE in qa.config.sh ║
# ║ ║
# ║ Usage: bash .pi/skills/qa-automation/qa-state-persistence/flows/example-state-test.sh
# ╚═══════════════════════════════════════════════════════════════════╝
set -euo pipefail
# ── Source Libraries ─────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/../lib/state-helpers.sh"
# ── Test Setup ───────────────────────────────────────────────────────
TEST_NAME="state-persistence"
setup_test "$TEST_NAME"
run_setup_guard || {
echo "FATAL: Setup guard failed."
teardown_test
exit 1
}
declare -a TEST_RESULTS=()
add_test_result() {
local name="$1"
local status="$2"
local error="${3:-}"
local screenshots="${4:-}"
TEST_RESULTS+=("$(cat <<RESULT
{
"name": "$name",
"suite": "State Persistence",
"status": "$status",
"error": "$error",
"screenshots": [$screenshots]
}
RESULT
)")
}
# ════════════════════════════════════════════════════════════════════
# Step 0 — Install Hooks
# ════════════════════════════════════════════════════════════════════
step "Install debug hooks"
assert_app_foreground || add_test_result "App Check" "failed" "Not foreground"
hook_result=$(install_debug_hook 2>&1 || echo '{"error":"failed"}')
log_info "Video hook: $hook_result"
take_screenshot "00-initial"
# ════════════════════════════════════════════════════════════════════
# Step 1 — Navigate to Feed
# CUSTOMIZE: Change nav_explore to your feed navigation
# ════════════════════════════════════════════════════════════════════
step "Navigate to feed screen"
nav_home 2>/dev/null || true
sleep 1
nav_explore
sleep 4
overlay=$(check_error_overlay)
[ "$overlay" = "visible" ] && dismiss_error_overlay && sleep 1
# Reset to index 0
current_idx=$(get_current_feed_index)
if [ "$current_idx" != "0" ] && [ "$current_idx" != "-1" ]; then
scroll_to_index 0
sleep 3
fi
# Install state hook after feed is mounted
state_hook_result=$(install_state_debug_hook 2>&1 || echo '{"error":"failed"}')
log_info "State hook: $state_hook_result"
take_screenshot "01-feed"
add_test_result "Navigate to Feed" "passed" "" "\"01-feed.png\""
# ════════════════════════════════════════════════════════════════════
# Step 2 — Record Item Identity
# ════════════════════════════════════════════════════════════════════
step "Record first item identity"
first_item=$(query_item_state 0 2>&1 || echo '{"error":"query failed"}')
log_info "First item: $first_item"
item_id=$(echo "$first_item" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).id||'unknown')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
has_ok=$(echo "$first_item" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).ok?'yes':'no')}catch(e){console.log('no')}});
" 2>/dev/null || echo "no")
if [ "$has_ok" = "yes" ]; then
log_pass "Item captured: $item_id"
add_test_result "Record Identity" "passed" ""
else
# Retry with re-installed hook
install_state_debug_hook >/dev/null 2>&1 || true
sleep 2
first_item=$(query_item_state 0 2>&1 || echo '{"error":"retry failed"}')
has_ok=$(echo "$first_item" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).ok?'yes':'no')}catch(e){console.log('no')}});
" 2>/dev/null || echo "no")
if [ "$has_ok" = "yes" ]; then
add_test_result "Record Identity" "passed" "Retry succeeded"
else
add_test_result "Record Identity" "skipped" "Could not read data"
fi
fi
# ════════════════════════════════════════════════════════════════════
# Step 3 — Verify Initial State
# ════════════════════════════════════════════════════════════════════
step "Verify initial state (${STATE_PROPERTY} should be false)"
initial_value=$(echo "$first_item" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).${STATE_PROPERTY}?'true':'false')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
take_screenshot "02-initial-state"
if [ "$initial_value" = "true" ]; then
log_warn "State is already true — toggling off for clean baseline"
toggle_item_property_cdp >/dev/null 2>&1
sleep 2
add_test_result "Initial State" "passed" "Reset to false"
elif [ "$initial_value" = "false" ]; then
log_pass "Clean baseline (${STATE_PROPERTY}=false)"
add_test_result "Initial State" "passed" "" "\"02-initial-state.png\""
else
add_test_result "Initial State" "skipped" "Unknown" "\"02-initial-state.png\""
fi
# ════════════════════════════════════════════════════════════════════
# Step 4 — Mutate State
# ════════════════════════════════════════════════════════════════════
step "Mutate state (set ${STATE_PROPERTY}=true)"
toggle_result=$(toggle_item_property_cdp 2>&1 || echo '{"error":"failed"}')
log_info "Toggle result: $toggle_result"
take_screenshot "03-after-mutate"
after_value=$(query_item_state 0 2>&1 || echo '{"error":"failed"}')
after_prop=$(echo "$after_value" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).${STATE_PROPERTY}?'true':'false')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
if [ "$after_prop" = "true" ]; then
log_pass "State mutated successfully"
add_test_result "Mutate State" "passed" "" "\"03-after-mutate.png\""
else
log_warn "Could not verify mutation: $after_prop"
add_test_result "Mutate State" "skipped" "Verification failed" "\"03-after-mutate.png\""
fi
# ════════════════════════════════════════════════════════════════════
# Step 5 — Scroll Away
# ════════════════════════════════════════════════════════════════════
for i in $(seq 1 $STATE_SCROLL_COUNT); do
step "Scroll to item #$i"
scroll_to_next_video
sleep 2
take_screenshot "04-scroll-${i}"
add_test_result "Scroll to Item $i" "passed" "" "\"04-scroll-${i}.png\""
done
# Verify we scrolled away
away_index=$(get_current_feed_index)
log_info "Current index: $away_index"
take_screenshot "05-scrolled-away"
add_test_result "Scrolled Away" "passed" "Index: $away_index" "\"05-scrolled-away.png\""
# ════════════════════════════════════════════════════════════════════
# Step 6 — Scroll Back
# ════════════════════════════════════════════════════════════════════
step "Scroll back to first item"
scroll_to_index 0
sleep 3
back_index=$(get_current_feed_index)
take_screenshot "06-scrolled-back"
if [ "$back_index" = "0" ]; then
log_pass "Back at first item"
add_test_result "Scroll Back" "passed" "" "\"06-scrolled-back.png\""
else
scroll_back_to_start
sleep 2
add_test_result "Scroll Back" "passed" "Used fallback" "\"06-scrolled-back.png\""
fi
# ════════════════════════════════════════════════════════════════════
# Step 7 — KEY ASSERTION: State Persisted
# ════════════════════════════════════════════════════════════════════
step "★ KEY ASSERTION: Verify ${STATE_PROPERTY} persisted"
# Re-install hook (fiber tree may have shifted)
install_state_debug_hook >/dev/null 2>&1 || true
sleep 2
persist_state=$(query_item_state 0 2>&1 || echo '{"error":"failed"}')
persist_value=$(echo "$persist_state" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{console.log(JSON.parse(d).${STATE_PROPERTY}?'true':'false')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
take_screenshot "07-persisted"
if [ "$persist_value" = "true" ]; then
log_pass "★ STATE PERSISTED! ${STATE_PROPERTY} is still true after scrolling away and back"
add_test_result "State Persisted" "passed" "" "\"07-persisted.png\""
elif [ "$persist_value" = "unknown" ]; then
log_warn "Could not verify — CDP state read failed"
add_test_result "State Persisted" "skipped" "CDP failed" "\"07-persisted.png\""
else
log_fail "★ STATE LOST! ${STATE_PROPERTY} is false after scrolling back"
add_test_result "State Persisted" "failed" "${STATE_PROPERTY}=false after scroll" "\"07-persisted.png\""
fi
# ════════════════════════════════════════════════════════════════════
# Step 8 — Cleanup
# ════════════════════════════════════════════════════════════════════
step "Cleanup: restore original state"
if [ "$persist_value" = "true" ]; then
toggle_item_property_cdp >/dev/null 2>&1 || true
sleep 1
log_pass "State restored"
add_test_result "Cleanup" "passed" ""
else
log_info "No cleanup needed"
add_test_result "Cleanup" "passed" "Not needed"
fi
take_screenshot "08-final"
# ════════════════════════════════════════════════════════════════════
# Generate Report
# ════════════════════════════════════════════════════════════════════
step "Generating report"
SKIP_COUNT=0
for r in "${TEST_RESULTS[@]}"; do
echo "$r" | grep -q '"status": "skipped"' && SKIP_COUNT=$((SKIP_COUNT + 1))
done
TESTS_JSON=""
for r in "${TEST_RESULTS[@]}"; do
[ -n "$TESTS_JSON" ] && TESTS_JSON+=","
TESTS_JSON+="$r"
done
REPORT_FILE="$TEST_OUTPUT_DIR/${TEST_NAME}-report.json"
end_time=$(date +%s)
duration=$((end_time - TEST_START_TIME))
cat > "$REPORT_FILE" <<REPORT
{
"title": "State Persistence QA",
"generatedAt": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
"suites": [{
"name": "State Persistence (${STATE_PROPERTY})",
"type": "e2e",
"passed": $PASS_COUNT,
"failed": $FAIL_COUNT,
"skipped": $SKIP_COUNT,
"duration": $((duration * 1000)),
"tests": [$TESTS_JSON],
"screenshotDir": "$SCREENSHOT_DIR/$TEST_NAME"
}],
"totalPassed": $PASS_COUNT,
"totalFailed": $FAIL_COUNT,
"totalSkipped": $SKIP_COUNT,
"totalDuration": $((duration * 1000))
}
REPORT
log_info "Report: $REPORT_FILE"
teardown_test
echo ""
echo "State persistence test completed!"
echo " Screenshots: $SCREENSHOT_DIR/$TEST_NAME/"
echo " Report: $REPORT_FILE"
echo ""