Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi
This commit is contained in:
290
skills/qa-automation/qa-state-persistence/lib/state-helpers.sh
Executable file
290
skills/qa-automation/qa-state-persistence/lib/state-helpers.sh
Executable file
@@ -0,0 +1,290 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user