mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 06:32:24 +00:00
* test(e2e): skill execution flow (core RPC + Skills UI) - Add skill-execution-flow.spec: core.ping, skills start/list_tools/call_tool/stop via shared RPC helper, seeded QuickJS echo skill (same tree as Rust json_rpc_e2e). - Add core-rpc helpers: WebView invoke on tauri-driver, Node fetch + port probe on Appium Mac2 (no WKWebView execute). - Extend navigateViaHash with Mac2 sidebar label fallbacks for /skills, /home, etc. - Wire yarn test:e2e:skill-execution and e2e-run-all-flows. Refs: #68 Made-with: Cursor * fix(e2e): address CodeRabbit review for skill execution PR - core-rpc-node: honor OPENHUMAN_CORE_HOST and OPENHUMAN_CORE_PORT before port scan - shared-flows: Mac2 navigateViaHash for /settings/billing; throw on unmapped hash; navigateToBilling fallback without WebView execute on Mac2 - skill-e2e-runtime: javascript manifest + removeSeededEchoSkill teardown - skill-execution-flow: teardown seeded skill; document RPC shapes vs json_rpc_e2e; skip placeholder for future agent chat tool_calls Made-with: Cursor
142 lines
5.2 KiB
TypeScript
142 lines
5.2 KiB
TypeScript
// @ts-nocheck
|
|
/**
|
|
* End-to-end: core JSON-RPC skill runtime (UI WebView → HTTP POST to sidecar) plus Skills UI smoke.
|
|
* Mirrors the Rust integration test `json_rpc_skills_runtime_start_tools_call_stop` (tests/json_rpc_e2e.rs).
|
|
*
|
|
* JSON-RPC `result` shapes match that test: `skills_start` → `SkillSnapshot` (e.g. `status`, `skill_id`);
|
|
* `skills_call_tool` → `ToolResult` (`content[]`); `skills_stop` → `{ success, skill_id }`. Not wrapped in `{ skill }` / `{ result }`.
|
|
*
|
|
* Issue #68 also asks for model→agent→tool→conversation; that path is environment- and LLM-dependent.
|
|
* This spec validates the **skill runtime + RPC + Skills shell** deterministically; full chat tool-calls belong
|
|
* in agent integration tests when the mock/backend can return structured tool_calls.
|
|
*/
|
|
import { waitForApp, waitForAppReady } from '../helpers/app-helpers';
|
|
import { callOpenhumanRpc } from '../helpers/core-rpc';
|
|
import { triggerAuthDeepLinkBypass } from '../helpers/deep-link-helpers';
|
|
import {
|
|
dumpAccessibilityTree,
|
|
textExists,
|
|
waitForWebView,
|
|
waitForWindowVisible,
|
|
} from '../helpers/element-helpers';
|
|
import { supportsExecuteScript } from '../helpers/platform';
|
|
import { completeOnboardingIfVisible, navigateToSkills } from '../helpers/shared-flows';
|
|
import {
|
|
E2E_RUNTIME_SKILL_ID,
|
|
removeSeededEchoSkill,
|
|
seedMinimalEchoSkill,
|
|
} from '../helpers/skill-e2e-runtime';
|
|
import { clearRequestLog, getRequestLog, startMockServer, stopMockServer } from '../mock-server';
|
|
|
|
function stepLog(message: string, context?: unknown): void {
|
|
const stamp = new Date().toISOString();
|
|
if (context === undefined) {
|
|
console.log(`[SkillExecutionE2E][${stamp}] ${message}`);
|
|
return;
|
|
}
|
|
console.log(`[SkillExecutionE2E][${stamp}] ${message}`, JSON.stringify(context, null, 2));
|
|
}
|
|
|
|
describe('Skill execution (UI + core RPC)', () => {
|
|
before(async () => {
|
|
stepLog('Seeding minimal echo skill on disk');
|
|
await seedMinimalEchoSkill();
|
|
await startMockServer();
|
|
await waitForApp();
|
|
clearRequestLog();
|
|
});
|
|
|
|
after(async () => {
|
|
await stopMockServer();
|
|
await removeSeededEchoSkill();
|
|
});
|
|
|
|
it('authenticates and reaches a logged-in shell', async () => {
|
|
await triggerAuthDeepLinkBypass('e2e-skill-execution-token');
|
|
await waitForWindowVisible(25_000);
|
|
await waitForWebView(15_000);
|
|
await waitForAppReady(15_000);
|
|
await completeOnboardingIfVisible('[SkillExecutionE2E]');
|
|
const atHome =
|
|
(await textExists('Message OpenHuman')) ||
|
|
(await textExists('Good morning')) ||
|
|
(await textExists('Upgrade to Premium'));
|
|
expect(atHome).toBe(true);
|
|
});
|
|
|
|
it('core.ping responds over the same JSON-RPC URL as the UI', async () => {
|
|
const ping = await callOpenhumanRpc('core.ping', {});
|
|
if (!ping.ok) {
|
|
stepLog('core.ping failed', ping);
|
|
}
|
|
expect(ping.ok).toBe(true);
|
|
});
|
|
|
|
it('runs start → list_tools → call_tool → stop for the seeded echo skill', async () => {
|
|
const start = await callOpenhumanRpc('openhuman.skills_start', {
|
|
skill_id: E2E_RUNTIME_SKILL_ID,
|
|
});
|
|
if (!start.ok) {
|
|
stepLog('skills_start failed', start);
|
|
stepLog('Request log (mock API):', getRequestLog());
|
|
}
|
|
expect(start.ok).toBe(true);
|
|
const status = start.result?.status;
|
|
expect(status === 'running' || status === 'initializing').toBe(true);
|
|
|
|
await browser.pause(800);
|
|
|
|
const tools = await callOpenhumanRpc('openhuman.skills_list_tools', {
|
|
skill_id: E2E_RUNTIME_SKILL_ID,
|
|
});
|
|
expect(tools.ok).toBe(true);
|
|
const toolNames = (tools.result?.tools || []).map((t: { name?: string }) => t.name);
|
|
expect(toolNames.includes('echo')).toBe(true);
|
|
|
|
const call = await callOpenhumanRpc('openhuman.skills_call_tool', {
|
|
skill_id: E2E_RUNTIME_SKILL_ID,
|
|
tool_name: 'echo',
|
|
arguments: { message: 'hello from e2e skill execution' },
|
|
});
|
|
expect(call.ok).toBe(true);
|
|
const content = call.result?.content || [];
|
|
const echoed = content.some(
|
|
(c: { text?: string }) =>
|
|
typeof c?.text === 'string' && c.text.includes('hello from e2e skill execution')
|
|
);
|
|
expect(echoed).toBe(true);
|
|
|
|
const stop = await callOpenhumanRpc('openhuman.skills_stop', {
|
|
skill_id: E2E_RUNTIME_SKILL_ID,
|
|
});
|
|
expect(stop.ok).toBe(true);
|
|
expect(stop.result?.success === true).toBe(true);
|
|
});
|
|
|
|
it('Skills page loads (UI surface for installed tools)', async () => {
|
|
await navigateToSkills();
|
|
await browser.pause(2_000);
|
|
if (supportsExecuteScript()) {
|
|
const hash = await browser.execute(() => window.location.hash);
|
|
expect(String(hash)).toContain('/skills');
|
|
}
|
|
|
|
const visible =
|
|
(await textExists('Skills')) ||
|
|
(await textExists('Install')) ||
|
|
(await textExists('Available')) ||
|
|
(await textExists('Telegram')) ||
|
|
(await textExists('Notion'));
|
|
if (!visible) {
|
|
stepLog('Skills markers missing');
|
|
await dumpAccessibilityTree();
|
|
stepLog('Request log:', getRequestLog());
|
|
}
|
|
expect(visible).toBe(true);
|
|
});
|
|
|
|
it.skip('(future) agent chat issues model tool_calls to echo — needs LLM + mock tool_calls', async () => {
|
|
// Tracked under #68: drive Conversations with a prompt that forces tool use and assert echo in thread.
|
|
});
|
|
});
|