fix(install-e2e): boot the app-update legs against a REAL configured provider
The app-update legs died on the onboarding overlay - a fullscreen div
that intercepts every click (the Settings click timed out under it).
The old plan seeded a fake provider key, which lies: the overlay
vanishes but the app is broken. Instead the driver now runs the
desktop E2E suite's own mock inference server
(tests-js/scripts/mock-server.ts, zero deps, bare-node type
stripping >=22.18) and configures it into HERMES_HOME byte-for-byte
like the dev:mock flow: config.yaml provider + MOCK_API_KEY env. The
app boots genuinely configured - no overlay, real chat surface.
e2e-assets/mock-provider.{sh,mjs} own start/stop (pid + url files;
the wrapper lives until SIGTERM - gating on stdin-close made the
server die instantly, a background process's stdin is already EOF)
and the config write. Wired into the posix script driver's
hermes-desktop-app-update arm and the macos driver's update phase
(both app-update methods). The Playwright flow keeps its defense-in-
depth: the real escape hatch ('I'll choose a provider later') and the
verified 'Open settings' selector.
Probed locally: models + streamed/non-streamed completions answer,
server stops cleanly on kill.
This commit is contained in:
@@ -120,15 +120,24 @@ async function main() {
|
||||
}
|
||||
const deadline = Date.now() + Number(values['timeout-ms']);
|
||||
|
||||
// Dismiss the onboarding overlay when present (fresh HERMES_HOME).
|
||||
const skip = window.getByRole('button', { name: /skip|get started|continue/i }).first();
|
||||
if (await skip.isVisible({ timeout: 5_000 }).catch(() => false)) {
|
||||
await skip.click().catch(() => {});
|
||||
// Dismiss the onboarding overlay when present. Two layers of defense:
|
||||
// the drivers seed a provider key so the app's runtime check reports
|
||||
// configured=true and the overlay never mounts; if it shows anyway
|
||||
// (fresh HERMES_HOME, slow readiness check), click the real escape
|
||||
// hatch - "I'll choose a provider later" (i18n en: chooseLater). The
|
||||
// overlay is a fullscreen div that intercepts ALL clicks, so this must
|
||||
// resolve before any Settings navigation.
|
||||
const later = window
|
||||
.getByRole('button', { name: /choose a provider later|skip/i })
|
||||
.first();
|
||||
if (await later.isVisible({ timeout: 10_000 }).catch(() => false)) {
|
||||
await later.click().catch(() => {});
|
||||
await later.waitFor({ state: 'hidden', timeout: 15_000 }).catch(() => {});
|
||||
}
|
||||
|
||||
// Settings -> About -> Update now. Selectors favor accessible names over
|
||||
// DOM structure so renderer refactors don't break the leg.
|
||||
await window.getByRole('button', { name: /settings/i }).first().click();
|
||||
// Settings -> About -> Update now. The settings trigger is an icon
|
||||
// button whose accessible name is "Open settings".
|
||||
await window.getByRole('button', { name: /open settings|settings/i }).first().click();
|
||||
await window.getByRole('tab', { name: /about/i }).or(
|
||||
window.getByRole('button', { name: /about/i })).first().click();
|
||||
const updateNow = window.getByRole('button', { name: /update now/i }).first();
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Driver-side wrapper around tests-js/scripts/mock-server.ts (the desktop
|
||||
// E2E suite's OpenAI-compatible mock): starts the server as a LIBRARY
|
||||
// (importing, not executing, so the dev-launcher block never runs) and
|
||||
// publishes its URL to a file for the shell driver to consume.
|
||||
//
|
||||
// Usage: node mock-provider.mjs <url-file>
|
||||
// Writes "<url-file>" with the base URL (http://127.0.0.1:<port>) once
|
||||
// the server is listening, then stays alive until stdin closes.
|
||||
|
||||
// @ts-check
|
||||
import fs from 'node:fs';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const mockUrl = fileURLToPath(new URL('../../../tests-js/scripts/mock-server.ts', import.meta.url));
|
||||
|
||||
const { startMockServer } = await import(mockUrl);
|
||||
const mock = await startMockServer();
|
||||
|
||||
const urlFile = process.argv[2];
|
||||
if (!urlFile) {
|
||||
console.error('usage: node mock-provider.mjs <url-file>');
|
||||
process.exit(1);
|
||||
}
|
||||
fs.writeFileSync(urlFile, mock.url);
|
||||
console.log(`[mock-provider] listening at ${mock.url}`);
|
||||
|
||||
// Live until killed. NOT gated on stdin closing: a backgrounded process's
|
||||
// stdin is already at EOF, so an end-event exit would fire immediately
|
||||
// and the server would die right after writing its URL.
|
||||
await new Promise(() => {});
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start/stop the mock inference server and point the app at it as a REAL
|
||||
# configured provider.
|
||||
#
|
||||
# Why: the desktop app boots to the onboarding overlay when no provider is
|
||||
# configured - a fullscreen div that intercepts every click, which killed
|
||||
# the app-update legs. Seeding a fake key makes the overlay vanish but
|
||||
# leaves a lying app; this makes the app GENUINELY configured (config.yaml
|
||||
# provider + key env, exactly what tests-js/scripts/mock-server.ts's
|
||||
# dev:mock flow writes) with a real, chat-capable backend.
|
||||
#
|
||||
# Usage (sourced from a driver):
|
||||
# mock_start <workroot> start, write config into $HERMES_HOME
|
||||
# mock_stop kill the background server
|
||||
#
|
||||
# Requires: ASSETS (e2e-assets dir), LOG_DIR, HERMES_HOME, ok/fail helpers.
|
||||
|
||||
MOCK_PIDFILE=""
|
||||
MOCK_URLFILE=""
|
||||
|
||||
mock_start() {
|
||||
local workroot="${1:?mock_start needs a workroot}"
|
||||
MOCK_PIDFILE="$workroot/mock.pid"
|
||||
MOCK_URLFILE="$workroot/mock.url"
|
||||
rm -f "$MOCK_PIDFILE" "$MOCK_URLFILE"
|
||||
|
||||
# Bare `node file.ts` type-stripping works on node >=22.18 (the images
|
||||
# ship 22.22+; the installed managed node is >=26) - same contract as
|
||||
# the repo's own `dev:mock` script.
|
||||
node "$ASSETS/mock-provider.mjs" "$MOCK_URLFILE" > "$LOG_DIR/mock.log" 2>&1 &
|
||||
echo $! > "$MOCK_PIDFILE"
|
||||
|
||||
local _i
|
||||
for _i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
[ -s "$MOCK_URLFILE" ] && break
|
||||
sleep 0.2
|
||||
done
|
||||
if [ ! -s "$MOCK_URLFILE" ]; then
|
||||
log_group "mock server transcript" "$LOG_DIR/mock.log"
|
||||
fail "mock inference server did not come up; transcript above"
|
||||
fi
|
||||
local url
|
||||
url="$(cat "$MOCK_URLFILE")"
|
||||
ok "mock inference server: $url"
|
||||
|
||||
# The provider config, byte-compatible with writeMockConfig() in
|
||||
# tests-js/scripts/mock-server.ts.
|
||||
cat > "$HERMES_HOME/config.yaml" <<EOF
|
||||
model:
|
||||
default: mock-model
|
||||
provider: mock
|
||||
providers:
|
||||
mock:
|
||||
api: $url/v1
|
||||
name: Mock
|
||||
api_mode: chat_completions
|
||||
key_env: MOCK_API_KEY
|
||||
models:
|
||||
mock-model: {}
|
||||
context_length: 4096
|
||||
EOF
|
||||
printf 'MOCK_API_KEY=e2e-mock-key\n' >> "$HERMES_HOME/.env"
|
||||
ok "provider 'mock' configured in $HERMES_HOME (api $url/v1)"
|
||||
}
|
||||
|
||||
mock_stop() {
|
||||
if [ -n "$MOCK_PIDFILE" ] && [ -f "$MOCK_PIDFILE" ]; then
|
||||
kill "$(cat "$MOCK_PIDFILE")" 2>/dev/null || true
|
||||
rm -f "$MOCK_PIDFILE"
|
||||
fi
|
||||
}
|
||||
@@ -357,6 +357,15 @@ case "$UPDATE_METHOD" in
|
||||
ASSETS="$REPO_ROOT/tests/install/e2e-assets"
|
||||
SPEC="$WORK_ROOT/launch-spec.json"
|
||||
|
||||
# A REAL configured provider: the mock inference server (the desktop E2E
|
||||
# suite's own) is configured into HERMES_HOME exactly like the dev:mock
|
||||
# flow does. The app then boots genuinely configured - no onboarding
|
||||
# overlay (a fullscreen div that intercepts every click) - and the chat
|
||||
# surface is real too.
|
||||
source "$ASSETS/mock-provider.sh"
|
||||
mock_start "$WORK_ROOT"
|
||||
trap mock_stop EXIT
|
||||
|
||||
step "capturing the hermes desktop launch spec (build runs for real)"
|
||||
rc=0
|
||||
(cd "$INSTALL_DIR" && \
|
||||
|
||||
@@ -270,6 +270,13 @@ phase_update() {
|
||||
ok "serve.git main = $HEAD_SHA"
|
||||
|
||||
step "updating via $UPDATE_METHOD"
|
||||
# The app must boot configured or the onboarding overlay (a fullscreen
|
||||
# div) eats every click: configure the mock inference server exactly like
|
||||
# the dev:mock flow does, so the app is genuinely configured.
|
||||
# shellcheck source=../install/e2e-assets/mock-provider.sh
|
||||
source "$ASSETS/mock-provider.sh"
|
||||
mock_start "$WORK_ROOT"
|
||||
trap mock_stop EXIT
|
||||
case "$UPDATE_METHOD" in
|
||||
open-app-update)
|
||||
# The installed app IS the user surface here (double-click the .app);
|
||||
|
||||
Reference in New Issue
Block a user