mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
* nix: add `cage` to devShell
* test(desktop): add pre-filled sessions support
Exports createSandbox, writeMockProviderConfig, writeEnvFile,
buildAppEnv, findElectron, and launchDesktop from fixtures.ts so
specs can compose their own seeded-backend fixtures without duplicating
the sandbox/config/launch logic.
* test(desktop): auto-fail e2e tests on error banner
Adds a shared test fixture (e2e/test.ts) that wraps @playwright/test's
page with an error-banner guard. When any [role="alert"] element
(error notification toast) appears in the DOM during a test, the test
fails with the error message text.
The guard uses:
- A MutationObserver (injected via addInitScript) that watches for
[role="alert"] elements appearing at any point during the test
- A final DOM scan in afterEach for alerts still visible at teardown
- Deduplication so the same error text only fires once
All existing e2e specs updated to import { test, expect } from './test'
instead of '@playwright/test'. No per-spec setup needed — the guard is
auto-installed on every page via the extended fixture.
This catches issues like the "resume failed" error banner that can
appear during session loading — previously the test would pass while
an error toast was silently visible on screen.
* fix(state): parse tool_calls JSON string before re-serializing
_insert_message_rows and append_message both do json.dumps(tool_calls)
to serialize the field for SQLite storage. But when tool_calls arrives
as a JSON string (from import_sessions / export_session, which store it
as TEXT), json.dumps double-encodes it — wrapping the already-serialized
string in quotes and escaping the inner quotes.
When _rows_to_conversation later does json.loads(row['tool_calls']),
the double-encoded string parses back to a plain string (not a list).
_history_to_messages then iterates this string character-by-character,
calling tc.get('function', {}) on each char — 'str' object has no
attribute 'get'.
This was a pre-existing bug (on main), but only triggered by the
import_sessions path (the live agent always passes tool_calls as a
Python list). The e2e error-banner guard caught it via the 'Resume
failed' notification toast.
Fix: in both append_message and _insert_message_rows, parse tool_calls
with json.loads first if it's a string, then re-serialize.
* fix(desktop): exempt boot-failure from error guard
- boot-failure: add allowErrorBanners() beforeEach — these tests
deliberately trigger boot errors, so error toasts are expected
- test.ts: export allowErrorBanners() opt-out + reset flag in afterEach
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
/**
|
|
* E2E onboarding tests — verify the provider picker appears when no
|
|
* inference provider is configured.
|
|
*
|
|
* Launches the app with an empty config.yaml (no providers). The renderer
|
|
* should detect the unconfigured state and show the DesktopOnboardingOverlay
|
|
* with provider options / API key form.
|
|
*
|
|
* Prerequisite: `npm run build` must have been run so dist/ exists.
|
|
*/
|
|
|
|
import { expect, test } from './test'
|
|
|
|
import {
|
|
type NoProviderFixture,
|
|
setupNoProvider,
|
|
waitForOnboarding,
|
|
} from './fixtures'
|
|
import { expectVisualSnapshot } from './visual-snapshot'
|
|
|
|
let fixture: NoProviderFixture | null = null
|
|
|
|
test.afterAll(async () => {
|
|
await fixture?.cleanup()
|
|
fixture = null
|
|
})
|
|
|
|
test.describe('onboarding with no provider configured', () => {
|
|
test('onboarding overlay appears on first boot', async () => {
|
|
fixture = await setupNoProvider()
|
|
|
|
// The app should boot (hermes serve starts fine even without a provider),
|
|
// but the renderer should show the onboarding overlay because no
|
|
// provider is configured.
|
|
await waitForOnboarding(fixture.page, 90_000)
|
|
})
|
|
|
|
test('onboarding shows provider options or API key form', async () => {
|
|
if (!fixture) {
|
|
test.skip(true, 'Previous test failed — no app running')
|
|
|
|
return
|
|
}
|
|
|
|
const page = fixture.page
|
|
|
|
// The onboarding overlay should contain provider-related text.
|
|
// It might show OAuth providers, an API key form, or a "choose later"
|
|
// link. Verify at least one of these is visible.
|
|
const rootText = await page.evaluate(() => {
|
|
const root = document.getElementById('root')
|
|
|
|
return root?.textContent ?? ''
|
|
})
|
|
|
|
const hasProviderText =
|
|
rootText.includes('provider') ||
|
|
rootText.includes('Provider') ||
|
|
rootText.includes('API key') ||
|
|
rootText.includes('Sign in') ||
|
|
rootText.includes('OpenRouter') ||
|
|
rootText.includes('OpenAI')
|
|
|
|
expect(hasProviderText).toBe(true)
|
|
})
|
|
|
|
test('screenshot of onboarding overlay', async () => {
|
|
if (!fixture) {
|
|
test.skip(true, 'Previous test failed — no app running')
|
|
|
|
return
|
|
}
|
|
|
|
await expectVisualSnapshot(fixture.page, { name: 'onboarding-overlay', app: fixture.app })
|
|
})
|
|
})
|