Files
openhuman/app/test/e2e/specs/skill-execution-flow.spec.ts
T
Mega MindandGitHub 9118bfb5d6 Fix/skill start issue (#498)
* chore: update .gitignore and bump openhuman version to 0.52.2

- Added `overlay/src-tauri/target/` to .gitignore to prevent tracking of build artifacts.
- Updated the openhuman package version from 0.52.0 to 0.52.2 in Cargo.lock files for both the main and app/src-tauri directories.
- Enhanced entitlements for macOS Hardened Runtime to allow outbound HTTPS calls and server connections.
- Refactored registry operations to use rustls explicitly, improving network reliability on macOS.

* refactor(logging): improve debug message formatting in fetch_url_bytes function

- Updated the logging statement in the fetch_url_bytes function to enhance readability by formatting the debug message across multiple lines. This change improves clarity in log outputs, making it easier to track the number of bytes fetched from URLs.

* feat(skill-setup): enhance OAuth handling and skill status synchronization

- Introduced a managed OAuth auto-advance mechanism to ensure it runs only once per login attempt, improving user experience during authentication.
- Updated the SkillSetupWizard to handle skill runtime checks more effectively, ensuring that the skill starts correctly and transitions to the setup phase seamlessly.
- Enhanced the useSkillSnapshot hook to provide a synthesized offline snapshot when the skill is not yet running, preventing UI stalls during loading.
- Implemented background synchronization after OAuth completion to ensure users see fresh data immediately without blocking the UI.
- Added tests to validate the new behavior for skills setup completion and status retrieval without requiring the skill to be started first.

* refactor(skills): streamline setup_complete retrieval in handle_skills_status function

- Simplified the retrieval of the `setup_complete` variable by removing unnecessary line breaks, enhancing code readability and maintainability.
- This change improves the clarity of the function's logic without altering its functionality.

* refactor(skills): simplify success message and remove initial sync from OAuth flow

- Updated the success message in the SkillSetupWizard to remove references to background syncing, streamlining user communication.
- Removed the initial sync trigger from the SkillManager after OAuth completion, shifting the responsibility for data synchronization to the user interface or cron jobs.
- Adjusted comments in the desktopDeepLinkListener to reflect the new sync behavior, clarifying that initial data sync is no longer automatic.

* fix(pr-498): address CodeRabbit review and CI failures

- SkillSetupWizard: only show complete after startSetup succeeds; error on failures
- hooks: merge prior snapshot into offline fallback; use const arrow for helper
- E2E: reset skills_set_setup_complete in finally for isolation
- json_rpc_e2e: assert oauth/complete returns start() result; add minimal start()
- Skills page tests: mock screen-intelligence/autocomplete/voice hooks (CoreStateProvider)

Made-with: Cursor

* fix: address follow-up CodeRabbit (readiness poll, shared test mocks)

- SkillSetupWizard: waitForSkillRunning after startSkill before startSetup/auth RPC
- json_rpc_e2e: poll skills_status until running instead of fixed 400ms sleep
- Consolidate Skills page vi.mocks in test/mockDefaultSkillStatusHooks.ts

Made-with: Cursor

* fix: CodeRabbit — legacy OAuth awaits setSetupComplete, const waitForSkillRunning, mock base

- Legacy OAuth: await persistence before complete; error on failure; guard ref + reset on skillId
- waitForSkillRunning: const arrow per TS style
- mockDefaultSkillStatusHooks: offlineStatusBase spread for shared literals

Made-with: Cursor
2026-04-11 04:16:15 +05:30

163 lines
5.9 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_set_setup_complete + skills_status without start (OAuth persistence path)', async () => {
try {
const set = await callOpenhumanRpc('openhuman.skills_set_setup_complete', {
skill_id: E2E_RUNTIME_SKILL_ID,
complete: true,
});
expect(set.ok).toBe(true);
const st = await callOpenhumanRpc('openhuman.skills_status', {
skill_id: E2E_RUNTIME_SKILL_ID,
});
expect(st.ok).toBe(true);
expect(st.result?.setup_complete === true).toBe(true);
} finally {
await callOpenhumanRpc('openhuman.skills_set_setup_complete', {
skill_id: E2E_RUNTIME_SKILL_ID,
complete: false,
});
}
});
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.
});
});