mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
184 lines
7.3 KiB
TypeScript
184 lines
7.3 KiB
TypeScript
import { expect, type Page, test } from '@playwright/test';
|
|
|
|
import {
|
|
bootAuthenticatedPage,
|
|
callCoreRpc,
|
|
dismissWalkthroughIfPresent,
|
|
waitForAppReady,
|
|
} from '../helpers/core-rpc';
|
|
|
|
async function openSettings(page: Page, userId: string, hash: string): Promise<void> {
|
|
await bootAuthenticatedPage(page, userId, hash);
|
|
await waitForAppReady(page);
|
|
await dismissWalkthroughIfPresent(page);
|
|
}
|
|
|
|
async function themeState(
|
|
page: Page
|
|
): Promise<{ mode?: string; tabBarLabels?: string; agentMessageViewMode?: string }> {
|
|
return page.evaluate(() => {
|
|
const store = (
|
|
window as unknown as {
|
|
__OPENHUMAN_STORE__?: {
|
|
getState?: () => {
|
|
theme?: { mode?: string; tabBarLabels?: string; agentMessageViewMode?: string };
|
|
};
|
|
};
|
|
}
|
|
).__OPENHUMAN_STORE__;
|
|
return store?.getState?.().theme ?? {};
|
|
});
|
|
}
|
|
|
|
async function persistedThemeState(
|
|
page: Page
|
|
): Promise<{ mode?: string; tabBarLabels?: string; agentMessageViewMode?: string }> {
|
|
return page.evaluate(() => {
|
|
const raw = localStorage.getItem('persist:theme');
|
|
if (!raw) return {};
|
|
try {
|
|
const parsed = JSON.parse(raw) as Record<string, string>;
|
|
return {
|
|
mode: parsed.mode ? JSON.parse(parsed.mode) : undefined,
|
|
tabBarLabels: parsed.tabBarLabels ? JSON.parse(parsed.tabBarLabels) : undefined,
|
|
agentMessageViewMode: parsed.agentMessageViewMode
|
|
? JSON.parse(parsed.agentMessageViewMode)
|
|
: undefined,
|
|
};
|
|
} catch {
|
|
return {};
|
|
}
|
|
});
|
|
}
|
|
|
|
function unwrap<T>(value: T | { result: T }): T {
|
|
if (value && typeof value === 'object' && 'result' in value) {
|
|
return (value as { result: T }).result;
|
|
}
|
|
return value as T;
|
|
}
|
|
|
|
test.describe('Settings leaf workflows', () => {
|
|
test('appearance theme, tab bar, and chat rendering preferences persist in app state', async ({
|
|
page,
|
|
}) => {
|
|
await openSettings(page, 'pw-settings-appearance', '/settings/appearance');
|
|
|
|
// Panel title dropped in the PanelPage migration; the theme radios confirm
|
|
// the Appearance panel mounted.
|
|
await expect(page.getByRole('radio', { name: /Dark/ })).toBeVisible();
|
|
await page.getByRole('radio', { name: /Dark/ }).click();
|
|
const labelSwitch = page.getByRole('switch', { name: /Always show labels/ });
|
|
if ((await labelSwitch.getAttribute('aria-checked')) !== 'true') {
|
|
await labelSwitch.click();
|
|
}
|
|
const assistantTextSwitch = page.getByRole('switch', { name: /Plain assistant responses/ });
|
|
if ((await assistantTextSwitch.getAttribute('aria-checked')) !== 'true') {
|
|
await assistantTextSwitch.click();
|
|
}
|
|
|
|
await expect
|
|
.poll(() => themeState(page))
|
|
.toMatchObject({ mode: 'dark', tabBarLabels: 'always', agentMessageViewMode: 'text' });
|
|
await expect
|
|
.poll(() => persistedThemeState(page))
|
|
.toMatchObject({ mode: 'dark', tabBarLabels: 'always', agentMessageViewMode: 'text' });
|
|
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
await expect
|
|
.poll(() => themeState(page))
|
|
.toMatchObject({ mode: 'dark', tabBarLabels: 'always', agentMessageViewMode: 'text' });
|
|
});
|
|
|
|
test('embeddings custom endpoint setup writes provider, model, and dimensions', async ({
|
|
page,
|
|
}) => {
|
|
await openSettings(page, 'pw-settings-embeddings', '/settings/embeddings');
|
|
|
|
// Panel title dropped in the PanelPage migration; the provider radios confirm
|
|
// the Embeddings panel mounted.
|
|
await expect(page.getByRole('radio', { name: /Custom/i })).toBeVisible();
|
|
await page.getByRole('radio', { name: /Custom/i }).click();
|
|
|
|
await expect(page.getByRole('heading', { name: /Set up/i })).toBeVisible();
|
|
await page
|
|
.getByPlaceholder('https://your-endpoint.com/v1')
|
|
.fill('http://127.0.0.1:18473/openai/v1');
|
|
// Use a `text-embedding-3-*` model so the save-time verification probe sends
|
|
// `dimensions: 64` — the mock backend (scripts/mock-api) echoes that length,
|
|
// so the live test embed verifies and the config can be persisted.
|
|
await page.getByPlaceholder('text-embedding-3-small').fill('text-embedding-3-small');
|
|
await page.getByPlaceholder('1024').fill('64');
|
|
await page.getByRole('button', { name: 'Save & switch' }).click();
|
|
|
|
const wipe = page.getByRole('button', { name: 'Wipe & apply' });
|
|
await expect(wipe).toBeVisible({ timeout: 15_000 });
|
|
await wipe.click();
|
|
|
|
await expect(page.getByText('Saved.')).toBeVisible({ timeout: 15_000 });
|
|
await expect
|
|
.poll(async () => {
|
|
const raw = await callCoreRpc<any>('openhuman.embeddings_get_settings', {});
|
|
const settings =
|
|
unwrap<{ provider?: string; model?: string; dimensions?: number }>(raw) ?? {};
|
|
return {
|
|
provider: settings.provider,
|
|
model: settings.model,
|
|
dimensions: settings.dimensions,
|
|
};
|
|
})
|
|
.toEqual({
|
|
provider: 'custom:http://127.0.0.1:18473/openai/v1',
|
|
model: 'text-embedding-3-small',
|
|
dimensions: 64,
|
|
});
|
|
});
|
|
|
|
test('agents/new creates a custom agent that appears in the registry', async ({ page }) => {
|
|
const agentId = `pw-researcher-${Date.now()}`;
|
|
await openSettings(page, 'pw-settings-agent-new', '/settings/agents/new');
|
|
|
|
// Page title dropped in the PanelPage migration; the Name field confirms the
|
|
// agent editor mounted.
|
|
await expect(page.getByRole('textbox', { name: 'Name' })).toBeVisible();
|
|
await page.getByRole('textbox', { name: 'Name' }).fill('Playwright Researcher');
|
|
await page.getByRole('textbox', { name: 'ID', exact: true }).fill(agentId);
|
|
await page.getByLabel('Description').fill('Validates settings agent authoring in E2E.');
|
|
await page.getByLabel('Model (optional)').selectOption('hint:reasoning');
|
|
await page
|
|
.getByLabel('System prompt (optional)')
|
|
.fill('Prefer concise citations and explain uncertainty.');
|
|
await page.getByRole('button', { name: 'Add tools' }).click();
|
|
await page.getByRole('button', { name: /Allow all tools/ }).click();
|
|
await page.getByRole('button', { name: 'Done', exact: true }).click();
|
|
await page.getByRole('button', { name: 'Create agent' }).click();
|
|
|
|
await expect(page).toHaveURL(/#\/settings\/agents$/);
|
|
const agent = await callCoreRpc<{
|
|
agent?: { id: string; model?: string; tool_allowlist?: string[] };
|
|
}>('openhuman.agent_registry_get', { id: agentId });
|
|
expect(agent.agent).toMatchObject({
|
|
id: agentId,
|
|
model: 'hint:reasoning',
|
|
tool_allowlist: ['*'],
|
|
});
|
|
});
|
|
|
|
test('task sources surface the web harness guard while preserving the create form', async ({
|
|
page,
|
|
}) => {
|
|
const name = `Playwright Issues ${Date.now()}`;
|
|
await openSettings(page, 'pw-settings-task-sources', '/settings/task-sources');
|
|
|
|
await expect(page.getByTestId('task-sources-panel')).toBeVisible();
|
|
await expect(page.getByText('Not running in Tauri')).toBeVisible();
|
|
await page.getByLabel('Provider').selectOption('github');
|
|
await page.getByLabel('Name (optional)').fill(name);
|
|
await page.getByLabel('Repository (owner/name, optional)').fill('tinyhumansai/openhuman');
|
|
await page.getByLabel('Labels (comma-separated)').fill('e2e, regression');
|
|
await expect(page.getByRole('button', { name: 'Add source' })).toBeEnabled();
|
|
await expect(page.getByRole('button', { name: 'Preview' })).toBeEnabled();
|
|
});
|
|
});
|