feat(install-e2e): cover non-app update methods after a dmg install
A dmg user can also update from the terminal (hermes update) or by re-running the install one-liner; the dmg arm only routed the two app-button methods, leaving three declared cells as permanent TODOs. Port the POSIX driver's method blocks into the macos driver's update phase (hermes-update with the --yes probe, installer re-run with per-ref flag probing, the +desktop built-app assert) and open the workflow gate. The dmg bootstrap driver also learns to recover instead of waiting out its bound when a stage fails: the bootstrap parks on an error screen with a Retry button (seen live: HTTP 429 downloading install.sh under full-matrix runner load), so the driver watches the bootstrap log for real failure shapes, requires two consecutive error probes before retargeting the click at Retry's measured position, unlatches when the log goes healthy, and gives up with the true cause after three retries. Driver rule learned three times in this suite (lsof +D, git show and find piped to grep -q): under set -euo pipefail, never feed grep -q from a pipe... grep exits at first match, the producer takes SIGPIPE, and a TRUE condition reads as failure. Capture to a variable or test paths directly. Verified end to end: run 33506177130, macos slice 13/13 green.
This commit is contained in:
2
.github/workflows/install-e2e-macos-run.yml
vendored
2
.github/workflows/install-e2e-macos-run.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
name: Hermes-Setup.dmg
|
||||
if: >-
|
||||
inputs.install-method == 'desktop-installer@latest' && inputs.tag-has-desktop
|
||||
&& contains(fromJSON('["open-app-update", "hermes-desktop-app-update"]'), inputs.update-method)
|
||||
&& contains(fromJSON('["open-app-update", "hermes-desktop-app-update", "hermes-update", "installer-script", "installer-script+desktop"]'), inputs.update-method)
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: ${{ inputs.timeout-minutes }}
|
||||
|
||||
|
||||
@@ -76,8 +76,16 @@ click_install() {
|
||||
# shellcheck disable=SC2086
|
||||
set -- $geo
|
||||
local x=$1 y=$2 wd=$3 ht=$4
|
||||
# Button center sits at ~65% of window height (measured from the
|
||||
# installer's first-run screen at its fixed 880x620 window size).
|
||||
if [ -n "$LAST_ERR" ]; then
|
||||
# Error screen: Retry install sits left of center at ~59% height
|
||||
# (measured: button x 359-492, y 402-441 in the 880x620 window).
|
||||
local cx=$((x + wd * 48 / 100))
|
||||
local cy=$((y + ht * 59 / 100))
|
||||
cliclick "c:${cx},${cy}" 2>&1 || true
|
||||
echo "clicked retry ${cx},${cy} (window ${x},${y} ${wd}x${ht})"
|
||||
return 0
|
||||
fi
|
||||
# First-run screen: button center sits at ~65% of window height.
|
||||
local cx=$((x + wd / 2))
|
||||
local cy=$((y + ht * 65 / 100))
|
||||
cliclick "c:${cx},${cy}" 2>&1 || true
|
||||
@@ -101,6 +109,26 @@ installed_app() {
|
||||
install_complete() {
|
||||
[ -d "$INSTALL_DIR/.git" ] && [ -x "$HERMES_BIN" ] && installed_app
|
||||
}
|
||||
# The bootstrap parks on an error screen instead of exiting when a stage
|
||||
# fails (e.g. a transient 429 downloading install.sh), with a Retry button
|
||||
# in the same button zone the install click hits. Its log names the cause;
|
||||
# watch for new failure lines, let the regular click drive the retry, and
|
||||
# give up after a few so a persistent failure reports the real error
|
||||
# instead of burning the whole install timeout.
|
||||
BOOTSTRAP_LOG="$HOME/.hermes/logs/bootstrap-installer.log"
|
||||
bootstrap_error() {
|
||||
[ -f "$BOOTSTRAP_LOG" ] || return 0
|
||||
# Match REAL failure shapes only: the structured stage log's state=Failed,
|
||||
# or the error screen's own message. Healthy INFO lines carry the literal
|
||||
# field error=None, so a bare error/failed substring match false-positives
|
||||
# on every stage transition.
|
||||
tail -5 "$BOOTSTRAP_LOG" 2>/dev/null \
|
||||
| grep -E 'state=Failed|install script failed|didn.t finish|ERROR ' | tail -1 || true
|
||||
}
|
||||
RETRIES=0
|
||||
MAX_RETRIES=3
|
||||
LAST_ERR=""
|
||||
PENDING_ERR_COUNT=0
|
||||
DEADLINE=$((SECONDS + INSTALL_TIMEOUT_SECS))
|
||||
FIRST_SHOT=0
|
||||
CLICKS=0
|
||||
@@ -119,6 +147,28 @@ while :; do
|
||||
log "Hermes-Setup exited (pid $SETUP_PID) before the install landed"
|
||||
exit 1
|
||||
fi
|
||||
err="$(bootstrap_error)"
|
||||
if [ -n "$err" ]; then
|
||||
PENDING_ERR_COUNT=$((PENDING_ERR_COUNT + 1))
|
||||
else
|
||||
PENDING_ERR_COUNT=0
|
||||
fi
|
||||
# Two consecutive error probes before switching click targets: a single
|
||||
# log-parse glitch must never redirect the clicker.
|
||||
if [ "$PENDING_ERR_COUNT" -ge 2 ] && [ "$err" != "$LAST_ERR" ]; then
|
||||
LAST_ERR="$err"
|
||||
RETRIES=$((RETRIES + 1))
|
||||
log "bootstrap error (retry $RETRIES/$MAX_RETRIES): $err"
|
||||
shot "ERROR-bootstrap-attempt-$RETRIES"
|
||||
if [ "$RETRIES" -gt "$MAX_RETRIES" ]; then
|
||||
log "bootstrap failing persistently; giving up"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ -z "$err" ] && [ -n "$LAST_ERR" ]; then
|
||||
log "bootstrap error cleared; resuming install-button targeting"
|
||||
LAST_ERR=""
|
||||
fi
|
||||
if [ "$FIRST_SHOT" -eq 0 ] && [ "$SECONDS" -gt 10 ]; then
|
||||
shot "00-setup-window"
|
||||
FIRST_SHOT=1
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
# hermes-desktop-app-update capture `hermes desktop`'s spawn,
|
||||
# launch the spec under Playwright,
|
||||
# click Update now
|
||||
# hermes-update CLI update from the installed venv
|
||||
# installer-script[+desktop] re-run the current install one-liner
|
||||
#
|
||||
# Usage:
|
||||
# tests/install/macos-desktop-e2e.sh --phase stage|install|update|all
|
||||
@@ -61,8 +63,8 @@ while [ "$#" -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
case "$UPDATE_METHOD" in
|
||||
open-app-update|hermes-desktop-app-update) ;;
|
||||
*) echo "error: --update-method must be open-app-update or hermes-desktop-app-update, got '$UPDATE_METHOD'" >&2; exit 1 ;;
|
||||
open-app-update|hermes-desktop-app-update|hermes-update|installer-script|installer-script+desktop) ;;
|
||||
*) echo "error: unsupported --update-method '$UPDATE_METHOD'" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$(uname -s)" = "Darwin" ] || { echo "error: this driver runs on macOS only" >&2; exit 1; }
|
||||
|
||||
@@ -266,6 +268,39 @@ ensure_playwright() {
|
||||
printf '%s' "$pw_dir"
|
||||
}
|
||||
|
||||
installer_supports() {
|
||||
# $1: ref; $2: flag. Installer flags must match the installer being run,
|
||||
# not this checkout's: older releases reject options added later.
|
||||
# Capture before grepping: a `git show | grep -q` pipe takes SIGPIPE
|
||||
# under pipefail when grep exits at first match, so a supported flag
|
||||
# would read as unsupported.
|
||||
local text
|
||||
text="$(git -C "$REPO_ROOT" show "$1:scripts/install.sh")"
|
||||
grep -qF -- "$2" <<< "$text"
|
||||
}
|
||||
|
||||
run_installer() {
|
||||
# $1: ref whose scripts/install.sh to run; $2: log name; $3: "desktop" to
|
||||
# opt the desktop stage in (--include-desktop). Mirrors the POSIX driver.
|
||||
local script="$WORK_ROOT/install-$2.sh"
|
||||
git -C "$REPO_ROOT" show "$1:scripts/install.sh" > "$script"
|
||||
chmod +x "$script"
|
||||
local flags=(--skip-setup)
|
||||
if installer_supports "$1" "--skip-browser"; then
|
||||
flags+=(--skip-browser)
|
||||
fi
|
||||
if [ "${3:-}" = "desktop" ]; then
|
||||
installer_supports "$1" "--include-desktop" \
|
||||
|| fail "ref $1 does not support --include-desktop; this leg cannot mean what it claims"
|
||||
flags+=(--include-desktop)
|
||||
fi
|
||||
# </dev/null: EOF makes every prompt take its default.
|
||||
local rc=0
|
||||
bash "$script" "${flags[@]}" < /dev/null 2>&1 | ts_prefix > "$LOG_DIR/install-$2.log" || rc=$?
|
||||
log_group "installer ($2) transcript" "$LOG_DIR/install-$2.log"
|
||||
[ "$rc" -eq 0 ] || fail "installer ($2) exited $rc; transcript above"
|
||||
}
|
||||
|
||||
run_playwright_update() {
|
||||
# $1: spec file to launch from.
|
||||
local spec="$1"
|
||||
@@ -300,6 +335,35 @@ phase_update() {
|
||||
mock_start "$WORK_ROOT"
|
||||
trap mock_stop EXIT
|
||||
case "$UPDATE_METHOD" in
|
||||
hermes-update)
|
||||
# The CLI route a dmg user takes from a terminal. `--yes` reaches the
|
||||
# update subcommand only in later releases; ask the installed hermes.
|
||||
local hermes="$INSTALL_DIR/venv/bin/hermes"
|
||||
local update_cmd=("$hermes" update)
|
||||
if "$hermes" update --help 2>&1 | grep -qF -- --yes; then
|
||||
update_cmd=("$hermes" update --yes)
|
||||
fi
|
||||
local rc=0
|
||||
(cd "$INSTALL_DIR" && "${update_cmd[@]}" < /dev/null 2>&1 | ts_prefix > "$LOG_DIR/update.log") || rc=$?
|
||||
log_group "hermes update transcript" "$LOG_DIR/update.log"
|
||||
[ "$rc" -eq 0 ] || fail "hermes update exited $rc; transcript above"
|
||||
;;
|
||||
installer-script)
|
||||
# A dmg user re-running today's install one-liner.
|
||||
run_installer "$HEAD_SHA" head
|
||||
;;
|
||||
installer-script+desktop)
|
||||
run_installer "$HEAD_SHA" head desktop
|
||||
# The desktop stage is this leg's claim: the rebuilt app must exist.
|
||||
head_app=""
|
||||
for cand in \
|
||||
"$INSTALL_DIR/apps/desktop/release/mac-arm64/Hermes.app" \
|
||||
"$INSTALL_DIR/apps/desktop/release/mac/Hermes.app"; do
|
||||
[ -d "$cand" ] && { head_app="$cand"; break; }
|
||||
done
|
||||
[ -n "$head_app" ] || fail "no built Hermes.app under the checkout after the +desktop update"
|
||||
ok "rebuilt app present: $head_app"
|
||||
;;
|
||||
open-app-update)
|
||||
# The installed app IS the user surface here (double-click the .app);
|
||||
# hand-build the spec Playwright launches from. Env: the redirect set,
|
||||
|
||||
Reference in New Issue
Block a user