ci: search the test directory from the chat list in the smoke test

The app is built with DIRECTORY_SERVICE_LINK set to the test directory's
short address (sed in the workflow). The smoke test types "ci seed", taps
"Search in Directory", accepts the first-search consent alert with "Search",
and expects the Directory results with #ci_group (Join group) and
#ci_channel (Join channel), retrying once if the search fails.

The steps now use scripts/ci/ios-sim/ui, which wraps AXe (tap by label or
value, type, wait, screenshot, compact accessibility tree) for scripts and
for an agent on the runner. Later steps get SIM_UDID and UI_OUT.

This run tests: the onboarding and typing steps on the new helper, the
consent alert, and the directory results in the app.
This commit is contained in:
spacedandy-agents-simplex committed 2026-10-14 12:20:10 +00:00
1 parent e348855a20
commit 2ebad19b21
3 files changed
+145 -79

No files matched your search

+7
View File
@@ -204,6 +204,13 @@ jobs:
timeout-minutes: 30
run: scripts/ci/ios-sim/backend.sh start ~/Downloads/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
- name: Point the app at the test directory
run: |
link=$(cat "$RUNNER_TEMP/backend/directory-link")
file=apps/ios/Shared/Model/DirectorySearch.swift
sed -i '' "s|^let DIRECTORY_SERVICE_LINK = \".*\"$|let DIRECTORY_SERVICE_LINK = \"$link\"|" "$file"
grep -F "let DIRECTORY_SERVICE_LINK = \"$link\"" "$file"
- name: Build app
run: |
xcodebuild build -quiet \
+76 -79
View File
@@ -1,70 +1,45 @@
#!/bin/bash
# Installs the app in a new iPhone simulator and drives it with AXe: onboarding, chat list, typing in the search field.
# Installs the app in a new iPhone simulator and runs the scripted UI cases with the ui helper:
# onboarding to the chat list, typing in the search field, and searching the test directory.
# Usage: scripts/ci/ios-sim/smoke-test.sh <path to SimpleX.app> <output dir>
# Each step saves a screenshot and an accessibility tree to the output dir and prints the tree to the log.
# Later steps of the job get SIM_UDID and UI_OUT, with the simulator still running.
set -euo pipefail
app=$1
out=$2
export UI_OUT=$2
bundle_id=chat.simplex.app
search_text="ci smoke"
mkdir -p "$out"
search_text="ci seed"
ui=$(dirname "$0")/ui
mkdir -p "$UI_OUT"
axe --version
runtime=com.apple.CoreSimulator.SimRuntime.iOS-$(xcrun --sdk iphonesimulator --show-sdk-version | tr . -)
udid=$(xcrun simctl create ci-iphone com.apple.CoreSimulator.SimDeviceType.iPhone-17 "$runtime")
echo "simulator $udid, runtime $runtime"
xcrun simctl boot "$udid"
xcrun simctl bootstatus "$udid" -b
open -a Simulator --args -CurrentDeviceUDID "$udid"
SIM_UDID=$(xcrun simctl create ci-iphone com.apple.CoreSimulator.SimDeviceType.iPhone-17 "$runtime")
export SIM_UDID
echo "simulator $SIM_UDID, runtime $runtime"
if [ -n "${GITHUB_ENV:-}" ]; then
echo "SIM_UDID=$SIM_UDID" >> "$GITHUB_ENV"
echo "UI_OUT=$UI_OUT" >> "$GITHUB_ENV"
fi
xcrun simctl boot "$SIM_UDID"
xcrun simctl bootstatus "$SIM_UDID" -b
open -a Simulator --args -CurrentDeviceUDID "$SIM_UDID"
xcrun simctl spawn "$udid" log stream --level debug --style compact --predicate 'process == "SimpleX"' > "$out/app.log" 2>&1 &
xcrun simctl spawn "$SIM_UDID" log stream --level debug --style compact --predicate 'process == "SimpleX"' > "$UI_OUT/app.log" 2>&1 &
log_pid=$!
step=0
snap() {
step=$((step + 1))
local f
f=$(printf '%s/%02d-%s' "$out" "$step" "$1")
xcrun simctl io "$udid" screenshot "$f.png" > /dev/null
axe describe-ui --udid "$udid" > "$f.json"
echo "::group::accessibility tree: $1"
cat "$f.json"
echo "::endgroup::"
}
wait_for() {
local deadline=$((SECONDS + $2))
local tree="$out/wait.json"
until axe describe-ui --udid "$udid" > "$tree" && grep -qF "$1" "$tree"; do
if [ "$SECONDS" -ge "$deadline" ]; then
echo "error: \"$1\" not shown after $2 s"
return 1
fi
sleep 2
done
}
tap_label() {
axe tap --label "$1" --wait-timeout "$2" --post-delay 2 --udid "$udid"
}
# SwiftUI text fields have no accessibility label; an empty field exposes its placeholder as the value
tap_field() {
axe tap --value "$1" --wait-timeout "$2" --post-delay 2 --udid "$udid"
}
finish() {
local status=$?
snap final || true
"$ui" shot final || true
kill "$log_pid" || true
echo "::group::core start in the app log"
grep -E 'chatMigrateInit|startChat' "$out/app.log" || true
grep -E 'chatMigrateInit|startChat' "$UI_OUT/app.log" || true
echo "::endgroup::"
echo "::group::app log, last 300 lines"
tail -n 300 "$out/app.log"
tail -n 300 "$UI_OUT/app.log"
echo "::endgroup::"
find ~/Library/Logs/DiagnosticReports -name 'SimpleX*' -newer "$app" 2>/dev/null | while read -r report; do
echo "::group::crash report $report"
@@ -75,46 +50,68 @@ finish() {
}
trap finish EXIT
xcrun simctl install "$udid" "$app"
xcrun simctl launch "$udid" "$bundle_id"
expect_in_tree() {
local tree
tree=$("$ui" tree)
if ! grep -q "$@" <<< "$tree"; then
echo "error: ${*: -1} is not in the accessibility tree"
exit 1
fi
}
wait_for "Get started" 180
snap first-screen
tap_label "Get started" 10
xcrun simctl install "$SIM_UDID" "$app"
xcrun simctl launch "$SIM_UDID" "$bundle_id"
wait_for "Create profile" 30
tap_field "Enter profile name..." 10
axe type 'citester' --udid "$udid"
snap profile-name
tap_label "Create profile" 10
# onboarding to the chat list
"$ui" wait "Get started" 180
"$ui" shot first-screen
"$ui" tap "Get started"
wait_for "Continue" 60
snap your-network
tap_label "Continue" 10
"$ui" wait "Create profile" 30
"$ui" tap-value "Enter profile name..."
"$ui" type citester
"$ui" shot profile-name
"$ui" tap "Create profile"
wait_for "Accept" 30
snap network-commitments
tap_label "Accept" 10
"$ui" wait "Continue" 60
"$ui" shot your-network
"$ui" tap "Continue"
"$ui" wait "Accept" 30
"$ui" shot network-commitments
"$ui" tap "Accept"
sleep 5
snap after-onboarding
tap_label "Allow" 20 || echo "no notifications alert to allow"
"$ui" shot after-onboarding
"$ui" tap "Allow" 20 || echo "no notifications alert to allow"
wait_for "Search or paste SimpleX link" 60
snap chat-list
tap_field "Search or paste SimpleX link" 10
axe type "$search_text" --udid "$udid"
"$ui" wait "Search or paste SimpleX link" 60
"$ui" shot chat-list
# typed text that is not a SimpleX name offers the directory
"$ui" tap-value "Search or paste SimpleX link"
"$ui" type "$search_text"
sleep 2
snap search-typed
"$ui" shot search-typed
# the keyboard capitalizes the first letter
tree=$(ls "$out"/*-search-typed.json)
if ! grep -qiF "$search_text" "$tree"; then
echo "error: \"$search_text\" is not in the accessibility tree after typing"
exit 1
expect_in_tree -iF "\"$search_text\""
expect_in_tree -F '"Search in Directory"'
# the directory finds the seeded group and channel, after the first-search consent
"$ui" tap "Search in Directory"
"$ui" wait "Search in Directory?" 10
"$ui" shot search-consent
"$ui" tap "Search"
if ! "$ui" wait "Join channel" 60; then
"$ui" shot search-failed
"$ui" tap "Search failed, tap to retry"
"$ui" wait "Join channel" 60
fi
if ! grep -qF "Search in Directory" "$tree"; then
echo "error: no \"Search in Directory\" row after typing"
exit 1
fi
echo "smoke test passed: the chat list search field shows \"$search_text\""
"$ui" shot directory-results
# the header alone is matched exactly: "Search in Directory" stays shown above the field
expect_in_tree -F '"Directory"'
expect_in_tree -F '#ci_group'
expect_in_tree -F 'Join group'
expect_in_tree -F '#ci_channel'
expect_in_tree -F 'Join channel'
echo "smoke test passed: the directory search for \"$search_text\" shows #ci_group (Join group) and #ci_channel (Join channel)"
+62
View File
@@ -0,0 +1,62 @@
#!/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 type <text> type into the focused field (US keyboard characters only)
# ui wait <text> [secs] wait up to secs (30) until a label or value contains the text (case-sensitive)
# ui shot <name> save UI_OUT/NN-<name>.png and .json, and print the tree
# ui tree print the tree, one element per line: type, label, value, top-left corner and size in points
set -euo pipefail
: "${SIM_UDID:?SIM_UDID must be the simulator UDID}"
compact_tree() {
jq -r '.. | objects | select(.AXLabel or .AXValue)
| "\(.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)"'
}
tree() {
axe describe-ui --udid "$SIM_UDID" | compact_tree
}
wait_for() {
local text=$1 secs=${2:-30}
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
}
shot() {
: "${UI_OUT:?UI_OUT must be the output directory}"
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
axe describe-ui --udid "$SIM_UDID" > "$f.json"
echo "::group::accessibility tree: $1"
compact_tree < "$f.json"
echo "::endgroup::"
}
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" ;;
type) axe type "$2" --udid "$SIM_UDID" ;;
wait) wait_for "$2" "${3:-30}" ;;
shot) shot "$2" ;;
tree) tree ;;
*)
sed -n '3,12s/^# \{0,1\}//p' "$0"
exit 1
;;
esac