Files
openhuman/app/test/e2e/helpers/skill-e2e-runtime.ts
T
Mega MindandGitHub b7b3d62c58 test(e2e): skill execution flow (core RPC + Skills UI) (#283)
* 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
2026-04-03 12:00:01 +05:30

97 lines
3.0 KiB
TypeScript

/**
* Seeds the minimal QuickJS echo skill used by Rust `json_rpc_skills_runtime_start_tools_call_stop`
* so the desktop core can run `openhuman.skills_start` → `skills_call_tool` against a real skill tree.
*/
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
export const E2E_RUNTIME_SKILL_ID = 'e2e-runtime';
const MANIFEST = {
id: E2E_RUNTIME_SKILL_ID,
name: 'E2E Runtime Skill',
version: '1.0.0',
description: 'Minimal skill for desktop E2E (echo tool)',
/** Matches `SkillManifest` docs (`manifest.rs`); executed via QuickJS like `quickjs` alias. */
runtime: 'javascript',
entry: 'index.js',
auto_start: false,
};
const INDEX_JS = `globalThis.__skill = {
name: "E2E Runtime Skill",
tools: [
{
name: "echo",
description: "Echoes back the input message",
inputSchema: {
type: "object",
properties: {
message: { type: "string", description: "Message to echo" }
},
required: ["message"]
},
execute(args) {
return { type: "text", text: "echo: " + (args.message || "empty") };
}
}
]
};
function init() {
if (globalThis.__ops && globalThis.__ops.log) {
globalThis.__ops.log("info", "e2e-runtime-skill initialized");
}
}
init();
`;
/** Resolve directories that should contain `e2e-runtime/manifest.json` (core may use either). */
export function resolveE2eRuntimeSkillDirs(): string[] {
const dirs: string[] = [];
const homeDir = path.join(
os.homedir(),
'.openhuman',
'workspace',
'skills',
E2E_RUNTIME_SKILL_ID
);
dirs.push(homeDir);
const w = process.env.OPENHUMAN_WORKSPACE?.trim();
if (w) {
const resolved = path.resolve(w);
const nested =
path.basename(resolved) === 'workspace'
? path.join(resolved, 'skills', E2E_RUNTIME_SKILL_ID)
: path.join(resolved, 'workspace', 'skills', E2E_RUNTIME_SKILL_ID);
if (nested !== homeDir) {
dirs.push(nested);
}
}
return dirs;
}
export async function seedMinimalEchoSkill(): Promise<void> {
const manifestBody = JSON.stringify(MANIFEST, null, 2);
for (const skillRoot of resolveE2eRuntimeSkillDirs()) {
await fs.mkdir(skillRoot, { recursive: true });
await fs.writeFile(path.join(skillRoot, 'manifest.json'), manifestBody, 'utf-8');
await fs.writeFile(path.join(skillRoot, 'index.js'), INDEX_JS, 'utf-8');
}
}
/** Remove seeded `e2e-runtime` skill dirs so E2E runs stay isolated. */
export async function removeSeededEchoSkill(): Promise<void> {
for (const skillRoot of resolveE2eRuntimeSkillDirs()) {
try {
await fs.rm(skillRoot, { recursive: true, force: true });
} catch {
/* ignore */
}
}
}