test(desktop): core e2e for the active gateway's default in the condensed fleet menu

This commit is contained in:
Brooklyn Nicholson
2026-10-03 14:25:55 -05:00
committed by brooklyn!
parent ad798ee921
commit f341e5697c
2 changed files with 140 additions and 0 deletions

View File

@@ -64,6 +64,10 @@ A small, deterministic Electron suite that guards three issue classes end to end
client's picture folder from the backend with a private mount namespace
(unprivileged user namespaces; without them the test is annotated
`fidelity` because a same-host path would resolve on the backend).
`fleet-condensed-default.spec.ts`: local primary + a remote connection with
enough profiles to condense the sidebar rail; the profile menu lists the
ACTIVE gateway's default (checked, home glyph) on both sides of a switch
(#106017, #131632).
- **Packaged build** — `packaged-smoke.spec.ts`: asarUnpack contract of the
`electron-builder --dir` output (#121097) and the packaged binary booting to
a first chat (≤60 s to interactive, main-process log tail on failure). It

View File

@@ -0,0 +1,136 @@
/**
* Every gateway's default profile has a door in the condensed profile menu
* (#106017, #131632). A second registered gateway puts the sidebar rail in fleet mode,
* which drops the default ↔ all home pill; past the condensed threshold the
* menu is then the only way to reach a default by pointer. It listed every
* AT-REST gateway's default (the remote's, or "This device") but never the
* ACTIVE gateway's own, so on the usual setup the local default vanished
* while the remote one stayed.
*
* Real app, real local backend, real second `hermes serve` registered as a
* remote connection; checked from both sides of a switch.
*/
import * as fs from 'node:fs'
import * as path from 'node:path'
import { expect, type Page, test } from '@playwright/test'
import { coreAppEnv, createCoreSandbox, launchCoreApp, waitForInteractive, writeProviderHome } from './harness'
import { startScriptedProvider } from './provider'
import { startRemoteBackend } from './remote-helpers'
const REMOTE_ID = 'linuxhost'
const REMOTE_LABEL = 'Linux host'
// 13 local profiles + the remote's default square already crosses the
// condensed threshold (13) before the roster enumerates the remote's others.
const LOCAL_NAMED = Array.from({ length: 12 }, (_, i) => `local${i + 1}`)
const REMOTE_NAMED = ['inbox', 'research']
function seedProfiles(hermesHome: string, names: string[]): void {
for (const name of names) {
fs.mkdirSync(path.join(hermesHome, 'profiles', name), { recursive: true })
fs.writeFileSync(path.join(hermesHome, 'profiles', name, 'config.yaml'), '')
}
}
function writeConnectionsRegistry(userDataDir: string, url: string, token: string): void {
fs.writeFileSync(
path.join(userDataDir, 'connections.json'),
JSON.stringify({
version: 2,
primary: 'local',
launchMode: 'primary',
lastUsed: 'local',
connections: [
{ id: 'local', kind: 'local', label: 'This device' },
{
id: REMOTE_ID,
kind: 'remote',
label: REMOTE_LABEL,
url,
authMode: 'token',
token: { encoding: 'plain', value: token }
}
]
}),
{ encoding: 'utf8', mode: 0o600 }
)
}
const trigger = (page: Page) => page.locator('[data-slot="profile-rail"] [data-slot="profile-dropdown"]')
async function openMenu(page: Page) {
await expect(trigger(page), 'the fleet rail condensed to its menu').toBeVisible()
await trigger(page).click()
const menu = page.getByRole('menu')
await expect(menu).toBeVisible()
return menu
}
/** The active gateway's own default: a checked radio row with the home glyph. */
async function expectActiveDefault(page: Page, where: string) {
const menu = await openMenu(page)
const home = menu.getByRole('menuitemradio', { name: 'default', exact: true })
await expect(home, `${where}: the active gateway's default has a row`).toBeVisible()
await expect(home).toHaveAttribute('aria-checked', 'true')
await expect(home.locator('.codicon-home')).toHaveCount(1)
return menu
}
test("the condensed fleet menu lists the active gateway's default on both sides of a switch (#131632)", async () => {
test.setTimeout(240_000)
const provider = await startScriptedProvider()
const clientBox = createCoreSandbox('fleet-default-client')
const remoteBox = createCoreSandbox('fleet-default-remote')
writeProviderHome(clientBox.hermesHome, provider.url)
writeProviderHome(remoteBox.hermesHome, provider.url)
seedProfiles(clientBox.hermesHome, LOCAL_NAMED)
seedProfiles(remoteBox.hermesHome, REMOTE_NAMED)
const remote = await startRemoteBackend(remoteBox)
writeConnectionsRegistry(clientBox.userDataDir, remote.url, remote.token)
const { app, page } = await launchCoreApp(coreAppEnv(clientBox))
try {
await waitForInteractive(app, page)
await test.step('on This device: its default is listed beside the remote one', async () => {
await expect(trigger(page)).toContainText('default')
const menu = await expectActiveDefault(page, 'This device active')
await expect(menu.getByRole('menuitemradio', { name: LOCAL_NAMED[0], exact: true })).toBeVisible()
const remoteDefault = menu.getByRole('menuitem', { name: `default · ${REMOTE_LABEL}` })
await expect(remoteDefault, 'the at-rest remote lists its default').toBeVisible({ timeout: 90_000 })
await remoteDefault.click()
})
await test.step(`on ${REMOTE_LABEL}: its default is listed beside This device`, async () => {
await expect
.poll(
async () => {
const menu = await openMenu(page)
const atRestLocal = await menu
.locator('[data-slot="profile-dropdown-gateway"][data-connection-id="local"]')
.count()
await page.keyboard.press('Escape')
return atRestLocal
},
{ message: `the window re-homed onto ${REMOTE_LABEL}`, timeout: 120_000, intervals: [1_000, 2_000] }
)
.toBe(1)
const menu = await expectActiveDefault(page, `${REMOTE_LABEL} active`)
await expect(menu.getByRole('menuitemradio', { name: REMOTE_NAMED[0], exact: true })).toBeVisible()
await expect(menu.getByRole('menuitem', { name: /^This device/ }), 'This device at rest').toBeVisible()
})
} finally {
await app.close().catch(() => undefined)
await remote.kill()
await provider.close()
clientBox.cleanup()
remoteBox.cleanup()
}
})