#!/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 <&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" <