mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-28 01:12:42 +00:00
* core: test that history posted before channel signing reaches later joiners as signature missing, and channel moderator delete for all Confirms the case the owner's new alert describes: a subscriber who joins after the owner turned on "Sign messages" sees the earlier posts as "signature missing" and later ones as signed, while earlier subscribers keep them unmarked. Also covers a non-owner channel moderator deleting a contributor's signed post for everyone. Run: 001-channel-signing-moderators, slice 1 * ios: keep "Chat with admins" off when saving group preferences toGroupPreferences left out the support preference, so the saved profile had none and core filled in its default, on: every channel preference save from iOS silently turned on chats with admins, which have no end-to-end encryption in channels. Android and desktop already pass it. Run: 001-channel-signing-moderators, slice 2 * ui: let channel owners turn on "Sign messages", warning that earlier messages will show signature missing The channel preference was hidden while signing was recipient-only. Owners now see the switch; turning it on asks for confirmation, because subscribers who join later receive the earlier posts unsigned through history and see them as "signature missing". The "Signature missing" explanation now says the message may predate signing or have been forwarded by a relay without its signature, instead of only implying tampering. Run: 001-channel-signing-moderators, slice 3 * ui: channel owners can make subscribers moderators, and moderators see the subscriber list Core already lets a channel owner make a subscriber a moderator; the role picker offered only subscriber and contributor. It now offers moderator too (not admin, by decision). Moderators see the full member list the owner sees, so they can reach the subscribers they may block (subscribers can't post, so there was no other way to reach them), and member info is headed Moderator or Contributor instead of Subscriber for those roles. Run: 001-channel-signing-moderators, slice 4 * ci: long-press and right-click for app tests, safer ui arguments, desktop Kotlin 4 GB, desktop session limits from claude-scope Message menus open only on long-press (iOS) or right-click (desktop), so Claude sessions couldn't reach Moderate, Info or Delete. Ported from f/directory-search unchanged, with its hardening: the guards refuse unquoted glob, brace and tilde expansion, and desktop ui checks numbers and sends one key per call so a key argument can't run an xdotool command. The desktop app job's Kotlin compiler gets 4 GB, as f/directory-search needed after merging master. Desktop sessions take max-turns and max-budget-usd from claude-scope like iOS (60 and 3 when unset, as before), and the Claude job may run 120 minutes. Run: 001-channel-signing-moderators, slice 5 * ci: Claude test plans for channel message signing and channel moderators This branch's iOS device test plan and desktop prompt replace master's smoke tests: the owner creates a channel on the preset relays, turns on Sign messages through its alert, and two more profiles in the same app join by the copied link; the owner makes one a moderator, who then sees the subscriber list, moderates a contributor's message and blocks the contributor. Signatures are checked as text in message Info. One layout, no test backend. Run: 001-channel-signing-moderators, slice 6 * fixup! ui: let channel owners turn on "Sign messages", warning that earlier messages will show signature missing * update plans * fixup! ui: let channel owners turn on "Sign messages", warning that earlier messages will show signature missing * fixup! ci: Claude test plans for channel message signing and channel moderators * fixup! ui: let channel owners turn on "Sign messages", warning that earlier messages will show signature missing * fixup! ci: Claude test plans for channel message signing and channel moderators * fixup! ui: let channel owners turn on "Sign messages", warning that earlier messages will show signature missing * fixup! ci: Claude test plans for channel message signing and channel moderators --------- Co-authored-by: spacedandy-agents-simplex <342056592+spacedandy-agents-simplex@users.noreply.github.com>
246 lines
8.7 KiB
Bash
Executable File
246 lines
8.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Drives the app in the simulator SIM_UDID with AXe, for the scripted cases and for an agent on the runner.
|
|
# Screenshots and accessibility trees go to UI_OUT.
|
|
#
|
|
# ui tap <label> [secs] tap the element with this exact accessibility label, waiting up to secs (10) for it
|
|
# ui tap-value <text> [secs] tap the element with this exact value: text fields have no label, only their placeholder or text
|
|
# ui tap-xy <x> <y> tap this point (points, as in the tree)
|
|
# ui long-press <x> <y> touch this point for 1.5 s, e.g. to open a context menu
|
|
# ui swipe <x1> <y1> <x2> <y2> swipe from one point to the other, e.g. to scroll a list
|
|
# ui type <text> type into the focused field (US keyboard characters only)
|
|
# ui clipboard <text> put the text on the clipboard, to paste it with the text field's Paste menu
|
|
# ui key <return|delete> [count] press a keyboard key: return submits the field (the Search key), delete erases a character
|
|
# ui wait <text> [secs] wait up to secs (30) until a label or value contains the text (case-sensitive)
|
|
# ui find <text> print the elements whose label or value contains the text (any case)
|
|
# ui shot <name> save UI_OUT/NN-<name>.png, a smaller NN-<name>-view.jpg and NN-<name>.json, and print the tree
|
|
# ui tree print the tree, one element per line: type, label, value, top-left corner and size in points,
|
|
# and "disabled" for a disabled element
|
|
# ui point <x> <y> print the element at this point, also when it belongs to a system alert that the tree misses
|
|
# ui layout <reachable|top> restart the app with the one-hand UI (reachable toolbar) or the top bar
|
|
# ui relaunch restart the app
|
|
# ui text-size <category> set Dynamic Type: medium (the default), large, extra-large, extra-extra-large,
|
|
# extra-extra-extra-large, accessibility-medium ... accessibility-extra-extra-extra-large
|
|
# ui directory <stop|start> stop or start the test directory (searches fail while it is stopped)
|
|
# ui listings the groups and channels listed in the test directory
|
|
# ui requests how many directory searches the app sent and how many were answered (needs APP_LOG)
|
|
|
|
set -euo pipefail
|
|
|
|
# nothing this helper runs needs the token of the Claude session that calls it
|
|
unset CLAUDE_CODE_OAUTH_TOKEN
|
|
|
|
: "${SIM_UDID:?SIM_UDID must be the simulator UDID}"
|
|
|
|
bundle_id=chat.simplex.app
|
|
app_group=group.chat.simplex.app
|
|
# the toolbar's new chat button: shown with and without conversations, unlike the search field
|
|
toolbar_button=Compose
|
|
|
|
# Elements below the screen, such as the keyboard's keys, are counted rather than listed; the screen is the first
|
|
# element's frame, or the given height.
|
|
compact_tree() {
|
|
jq -r --argjson screen "${1:-null}" '($screen // .[0].frame.height // 100000) as $h
|
|
| [.. | objects | select(.AXLabel or .AXValue)]
|
|
| (.[] | select((.frame.y // 0) < $h)
|
|
| "\(.type) \(.AXLabel | tojson) value=\(.AXValue | tojson) at \(.frame.x // 0 | round),\(.frame.y // 0 | round) size \(.frame.width // 0 | round)x\(.frame.height // 0 | round)\(if .enabled == false then " disabled" else "" end)"),
|
|
(map(select((.frame.y // 0) >= $h)) | length | select(. > 0) | "(\(.) elements below the screen not listed, such as keyboard keys)")'
|
|
}
|
|
|
|
# describe-ui failed once while a system alert was appearing, and read the tree a few seconds later (run 38895987656)
|
|
describe() {
|
|
local attempt
|
|
for attempt in 1 2 3 4; do
|
|
if axe describe-ui --udid "$SIM_UDID" "$@"; then
|
|
return
|
|
fi
|
|
sleep 2
|
|
done
|
|
axe describe-ui --udid "$SIM_UDID" "$@"
|
|
}
|
|
|
|
tree() {
|
|
describe | compact_tree
|
|
}
|
|
|
|
point() {
|
|
number "$1"
|
|
number "$2"
|
|
describe --point "$1,$2" | compact_tree 100000
|
|
}
|
|
|
|
wait_for() {
|
|
local text=$1 secs=${2:-30}
|
|
number "$secs"
|
|
local deadline=$((SECONDS + secs)) t
|
|
until t=$(tree 2> /dev/null) && grep -qF -- "$text" <<< "$t"; do
|
|
if [ "$SECONDS" -ge "$deadline" ]; then
|
|
echo "error: \"$text\" not shown after $secs s"
|
|
return 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
}
|
|
|
|
find_text() {
|
|
local t
|
|
t=$(tree)
|
|
if ! grep -iF -- "$1" <<< "$t"; then
|
|
echo "no element contains \"$1\""
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
shot() {
|
|
: "${UI_OUT:?UI_OUT must be the output directory}"
|
|
if ! [[ "$1" =~ ^[A-Za-z0-9_-]+$ ]]; then
|
|
echo "error: a shot name has only letters, digits, - and _"
|
|
return 1
|
|
fi
|
|
mkdir -p "$UI_OUT"
|
|
local n f
|
|
n=$(find "$UI_OUT" -maxdepth 1 -name '*.png' | wc -l)
|
|
f=$(printf '%s/%02d-%s' "$UI_OUT" $((n + 1)) "$1")
|
|
xcrun simctl io "$SIM_UDID" screenshot "$f.png" > /dev/null
|
|
sips -Z 1000 -s format jpeg -s formatOptions 70 "$f.png" --out "$f-view.jpg" > /dev/null
|
|
describe > "$f.json"
|
|
echo "::group::accessibility tree: $1"
|
|
echo "saved $f.png, and $f-view.jpg to look at"
|
|
compact_tree < "$f.json"
|
|
echo "::endgroup::"
|
|
}
|
|
|
|
number() {
|
|
if ! [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
|
echo "error: \"$1\" is not a number"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
key() {
|
|
local code count=${2:-1} i
|
|
case "$1" in
|
|
return) code=40 ;;
|
|
delete) code=42 ;;
|
|
*)
|
|
echo "error: key is return or delete"
|
|
return 1
|
|
;;
|
|
esac
|
|
number "$count"
|
|
for ((i = 0; i < count; i++)); do
|
|
axe key "$code" --udid "$SIM_UDID"
|
|
done
|
|
sleep 1
|
|
}
|
|
|
|
launch() {
|
|
xcrun simctl terminate "$SIM_UDID" "$bundle_id" 2> /dev/null || true
|
|
"$@"
|
|
xcrun simctl launch "$SIM_UDID" "$bundle_id" > /dev/null
|
|
wait_for "$toolbar_button" 90
|
|
}
|
|
|
|
toolbar_y() {
|
|
tree | grep -F "Button \"$toolbar_button\" " | sed -E 's/.* at [0-9-]+,([0-9-]+) .*/\1/' | head -n 1
|
|
}
|
|
|
|
# The preference is set in the app group's defaults while the app is not running; Settings shows the same switch.
|
|
layout() {
|
|
local one_hand
|
|
case "$1" in
|
|
reachable) one_hand=true ;;
|
|
top) one_hand=false ;;
|
|
*)
|
|
echo "error: layout is reachable or top"
|
|
return 1
|
|
;;
|
|
esac
|
|
local prefs
|
|
prefs=$(xcrun simctl get_app_container "$SIM_UDID" "$bundle_id" "$app_group")/Library/Preferences/$app_group
|
|
launch xcrun simctl spawn "$SIM_UDID" defaults write "$prefs" oneHandUI -bool "$one_hand"
|
|
local y
|
|
y=$(toolbar_y)
|
|
if { [ "$one_hand" = true ] && [ "$y" -lt 437 ]; } || { [ "$one_hand" = false ] && [ "$y" -ge 437 ]; }; then
|
|
echo "error: the toolbar is at y=$y, not where the $1 layout puts it"
|
|
return 1
|
|
fi
|
|
echo "layout $1: the toolbar is at y=$y"
|
|
}
|
|
|
|
text_size() {
|
|
case "$1" in
|
|
medium | large | extra-large | extra-extra-large | extra-extra-extra-large | accessibility-medium | accessibility-large | accessibility-extra-large | accessibility-extra-extra-large | accessibility-extra-extra-extra-large) ;;
|
|
*)
|
|
echo "error: unknown text size \"$1\""
|
|
return 1
|
|
;;
|
|
esac
|
|
xcrun simctl ui "$SIM_UDID" content_size "$1"
|
|
sleep 2
|
|
}
|
|
|
|
backend() {
|
|
: "${BACKEND_DIR:?BACKEND_DIR must be the work dir of the test backend}"
|
|
"$(dirname "$0")/backend.sh" "$@" "$BACKEND_DIR"
|
|
}
|
|
|
|
directory() {
|
|
case "$1" in
|
|
stop) backend directory-stop ;;
|
|
start) backend directory-start ;;
|
|
*)
|
|
echo "error: directory stop or directory start"
|
|
return 1
|
|
;;
|
|
esac
|
|
echo "test directory: $1"
|
|
}
|
|
|
|
requests() {
|
|
: "${APP_LOG:?APP_LOG must be the app log}"
|
|
local sent answered results
|
|
sent=$(grep -c 'chatSendCmd apiSendServiceRequest$' "$APP_LOG" || true)
|
|
results=$(grep -c 'chatSendCmd apiSendServiceRequest: ' "$APP_LOG" || true)
|
|
answered=$(grep -c 'chatSendCmd apiSendServiceRequest: serviceResponse' "$APP_LOG" || true)
|
|
echo "directory searches sent: $sent, answered: $answered, failed: $((results - answered)), waiting: $((sent - results))"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
tap) axe tap --label "$2" --wait-timeout "${3:-10}" --post-delay 2 --udid "$SIM_UDID" ;;
|
|
tap-value) axe tap --value "$2" --wait-timeout "${3:-10}" --post-delay 2 --udid "$SIM_UDID" ;;
|
|
tap-xy)
|
|
number "$2"
|
|
number "$3"
|
|
axe tap -x "$2" -y "$3" --post-delay 2 --udid "$SIM_UDID"
|
|
;;
|
|
long-press)
|
|
number "$2"
|
|
number "$3"
|
|
axe touch -x "$2" -y "$3" --down --up --delay 1.5 --udid "$SIM_UDID"
|
|
sleep 2
|
|
;;
|
|
swipe)
|
|
for n in "$2" "$3" "$4" "$5"; do number "$n"; done
|
|
axe swipe --start-x "$2" --start-y "$3" --end-x "$4" --end-y "$5" --duration 0.5 --post-delay 1 --udid "$SIM_UDID"
|
|
;;
|
|
type) axe type "$2" --udid "$SIM_UDID" ;;
|
|
clipboard) printf '%s' "$2" | xcrun simctl pbcopy "$SIM_UDID" ;;
|
|
key) key "$2" "${3:-1}" ;;
|
|
wait) wait_for "$2" "${3:-30}" ;;
|
|
find) find_text "$2" ;;
|
|
shot) shot "$2" ;;
|
|
tree) tree ;;
|
|
point) point "$2" "$3" ;;
|
|
layout) layout "$2" ;;
|
|
relaunch) launch true ;;
|
|
text-size) text_size "$2" ;;
|
|
directory) directory "$2" ;;
|
|
listings) backend listings ;;
|
|
requests) requests ;;
|
|
*)
|
|
sed -n '3,27s/^# \{0,1\}//p' "$0"
|
|
exit 1
|
|
;;
|
|
esac
|