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

291 lines
11 KiB
Bash
Executable File

#!/bin/bash
# ╔═══════════════════════════════════════════════════════════════════╗
# ║ state-helpers.sh — State Persistence Inspection & Mutation ║
# ╠═══════════════════════════════════════════════════════════════════╣
# ║ Source this file in state persistence test scripts: ║
# ║ source "$(dirname "$0")/../lib/state-helpers.sh" ║
# ║ ║
# ║ Provides: ║
# ║ • State debug hook installation ║
# ║ • Item state queries (any property at any index) ║
# ║ • CDP-first state mutation with tap fallback ║
# ║ • Feed index tracking and scroll-to-index ║
# ║ • Assertion helpers for property values ║
# ╚═══════════════════════════════════════════════════════════════════╝
set -euo pipefail
# ── Source shared helpers (chains to test-helpers + cdp-helpers) ─────
STATE_SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SCROLL_SKILL_DIR="$(cd "$STATE_SKILL_DIR/../qa-scroll" && pwd)"
source "$SCROLL_SKILL_DIR/lib/scroll-helpers.sh"
# ── Install State Debug Hook ────────────────────────────────────────
# Sets up a global accessor for reading feed item properties.
install_state_debug_hook() {
cdp_eval_safe "
globalThis.__qaItemState = function(targetIndex) {
try {
var feed = globalThis.${GLOBAL_FEED_VAR};
if (!feed) return JSON.stringify({error: '${GLOBAL_FEED_VAR} not available', method: 'none'});
var idx = (targetIndex !== undefined && targetIndex !== null) ? targetIndex : feed.currentIndex;
// Method 1: getItem() directly
if (typeof feed.getItem === 'function') {
var item = feed.getItem(idx);
if (item && typeof item === 'object') {
return JSON.stringify({
ok: true,
method: 'getItem',
index: idx,
${STATE_PROPERTY}: !!item.${STATE_PROPERTY},
${STATE_COUNTER_PROPERTY}: item.${STATE_COUNTER_PROPERTY} || 0,
id: item.id || item._id || 'unknown',
title: (item.description || item.title || item.name || '').substring(0, 50)
});
}
}
// Method 2: getData() array
if (typeof feed.getData === 'function') {
var data = feed.getData();
if (data && idx < data.length && data[idx]) {
var it = data[idx];
return JSON.stringify({
ok: true,
method: 'getData',
index: idx,
${STATE_PROPERTY}: !!it.${STATE_PROPERTY},
${STATE_COUNTER_PROPERTY}: it.${STATE_COUNTER_PROPERTY} || 0,
id: it.id || it._id || 'unknown',
title: (it.description || it.title || it.name || '').substring(0, 50)
});
}
}
return JSON.stringify({
error: 'feed methods not available',
method: 'none',
hasGetItem: typeof feed.getItem === 'function',
hasGetData: typeof feed.getData === 'function',
dataLength: feed.dataLength,
currentIndex: feed.currentIndex
});
} catch(e) {
return JSON.stringify({error: e.message, method: 'exception'});
}
};
var feed = globalThis.${GLOBAL_FEED_VAR};
return JSON.stringify({
ok: true,
hookInstalled: 'itemState',
hasGetItem: !!(feed && typeof feed.getItem === 'function'),
hasGetData: !!(feed && typeof feed.getData === 'function'),
dataLength: feed ? feed.dataLength : 0
});
"
}
# ── Query Item State ─────────────────────────────────────────────────
# Returns JSON: {ok, STATE_PROPERTY, STATE_COUNTER_PROPERTY, id, ...}
query_item_state() {
local target_index="${1:-}"
local index_arg=""
[ -n "$target_index" ] && index_arg="$target_index"
local attempt=0
local max_attempts=3
local result=""
while [ $attempt -lt $max_attempts ]; do
result=$(cdp_eval "
(function() {
try {
if (globalThis.__qaItemState) {
return globalThis.__qaItemState($index_arg);
}
return JSON.stringify({error: 'state hook not installed'});
} catch(e) {
return JSON.stringify({error: e.message});
}
})();
" 2>/dev/null || echo '{"error":"cdp failed"}')
local has_ok
has_ok=$(echo "$result" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{const o=JSON.parse(d);console.log(o.ok?'yes':'no')}catch(e){console.log('no')}});
" 2>/dev/null || echo "no")
if [ "$has_ok" = "yes" ]; then
echo "$result"
return 0
fi
attempt=$((attempt + 1))
[ $attempt -lt $max_attempts ] && sleep 1
done
echo "$result"
}
# ── Feed Index ───────────────────────────────────────────────────────
get_current_feed_index() {
local result
result=$(cdp_eval "
(function() {
if (globalThis.${GLOBAL_FEED_VAR}) {
return '' + (globalThis.${GLOBAL_FEED_VAR}.currentIndex || 0);
}
return '-1';
})();
" 2>/dev/null || echo "-1")
echo "$result"
}
scroll_to_index() {
local target_index="$1"
step "Scrolling to feed index $target_index"
local result
result=$(cdp_eval "
(function() {
if (globalThis.${GLOBAL_FEED_VAR} && globalThis.${GLOBAL_FEED_VAR}.scrollToIndex) {
var before = globalThis.${GLOBAL_FEED_VAR}.currentIndex;
globalThis.${GLOBAL_FEED_VAR}.scrollToIndex($target_index);
return JSON.stringify({ok:true, before:before, after:$target_index});
}
return JSON.stringify({error:'scrollToIndex not available'});
})();
" 2>/dev/null || echo '{"error":"cdp failed"}')
log_info "Scroll to index result: $result"
sleep 3
}
scroll_back_to_start() {
local current_index
current_index=$(get_current_feed_index)
if [ "$current_index" = "-1" ] || [ "$current_index" = "0" ]; then
log_info "Already at start or unknown index"
return 0
fi
# Try scrollToIndex first
local result
result=$(cdp_eval "
(function() {
if (globalThis.${GLOBAL_FEED_VAR} && globalThis.${GLOBAL_FEED_VAR}.scrollToIndex) {
globalThis.${GLOBAL_FEED_VAR}.scrollToIndex(0);
return 'ok';
}
return 'no_hook';
})();
" 2>/dev/null || echo "error")
if [ "$result" = "ok" ]; then
log_info "Scrolled to index 0 via scrollToIndex"
sleep 3
return 0
fi
# Fallback: swipe down N times
log_info "Using swipe-down fallback ($current_index times)"
local i=0
while [ $i -lt "$current_index" ]; do
swipe $SWIPE_END_X $SWIPE_END_Y $SWIPE_START_X $SWIPE_START_Y
sleep 2
i=$((i + 1))
done
}
# ── State Mutation ───────────────────────────────────────────────────
# Toggle a boolean property on the current feed item via CDP.
toggle_item_property_cdp() {
local current_index
current_index=$(get_current_feed_index)
local result
result=$(cdp_eval_safe "
var feed = globalThis.${GLOBAL_FEED_VAR};
if (!feed || typeof feed.getItem !== 'function') {
return JSON.stringify({error: 'feed not available'});
}
var item = feed.getItem($current_index);
if (!item) {
return JSON.stringify({error: 'no item at index $current_index'});
}
try {
var oldValue = !!item.${STATE_PROPERTY};
item.${STATE_PROPERTY} = !oldValue;
if (item.${STATE_COUNTER_PROPERTY} !== undefined) {
item.${STATE_COUNTER_PROPERTY} = oldValue
? (item.${STATE_COUNTER_PROPERTY} - 1)
: (item.${STATE_COUNTER_PROPERTY} + 1);
}
return JSON.stringify({ok: true, was: oldValue, now: !oldValue, id: item.id});
} catch(e) {
return JSON.stringify({error: e.message});
}
" 2>/dev/null || echo '{"error":"cdp failed"}')
echo "$result"
}
# ── Assertion Helpers ────────────────────────────────────────────────
# Assert that item at index has the property set to true
assert_item_property_true() {
local target_index="${1:-}"
local state
state=$(query_item_state "$target_index")
local value
value=$(echo "$state" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{const o=JSON.parse(d);console.log(o.${STATE_PROPERTY}?'true':'false')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
if [ "$value" = "true" ]; then
log_pass "Item ${STATE_PROPERTY} is true"
return 0
elif [ "$value" = "unknown" ]; then
log_warn "Could not determine ${STATE_PROPERTY} state"
return 2
else
log_fail "Item ${STATE_PROPERTY} is false (expected true)"
return 1
fi
}
# Assert that item at index has the property set to false
assert_item_property_false() {
local target_index="${1:-}"
local state
state=$(query_item_state "$target_index")
local value
value=$(echo "$state" | node -e "
let d='';process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{try{const o=JSON.parse(d);console.log(o.${STATE_PROPERTY}?'true':'false')}catch(e){console.log('unknown')}});
" 2>/dev/null || echo "unknown")
if [ "$value" = "false" ]; then
log_pass "Item ${STATE_PROPERTY} is false (as expected)"
return 0
elif [ "$value" = "unknown" ]; then
log_warn "Could not determine ${STATE_PROPERTY} state"
return 2
else
log_fail "Item ${STATE_PROPERTY} is true (expected false)"
return 1
fi
}
echo "State persistence helpers loaded."