Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/e2e-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ jobs:
with:
name: e2e-failure-logs-${{ runner.os }}-${{ github.run_id }}
path: |
${{ runner.temp }}/openhuman-e2e-app-*.log
/tmp/openhuman-e2e-app-*.log
app/test/e2e/artifacts/
retention-days: 7
Expand Down Expand Up @@ -351,6 +352,7 @@ jobs:
with:
name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
path: |
${{ runner.temp }}/openhuman-e2e-app-*.log
/tmp/openhuman-e2e-app-*.log
app/test/e2e/artifacts/
retention-days: 7
Expand Down Expand Up @@ -734,6 +736,7 @@ jobs:
with:
name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
path: |
${{ runner.temp }}/openhuman-e2e-app-*.log
/tmp/openhuman-e2e-app-*.log
app/test/e2e/artifacts/
retention-days: 7
Expand Down Expand Up @@ -877,9 +880,7 @@ jobs:
with:
name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
# e2e-run-session.sh writes its app log to `${RUNNER_TEMP:-${TMPDIR:-/tmp}}`.
# On Windows runners RUNNER_TEMP resolves to D:\a\_temp, not /tmp, so
# include the runner-temp pattern as well (Linux/macOS shards above
# use /tmp and don't need this).
# On Windows runners RUNNER_TEMP resolves to D:\a\_temp, not /tmp.
path: |
${{ runner.temp }}/openhuman-e2e-app-*.log
app/test/e2e/artifacts/
Expand Down
7 changes: 7 additions & 0 deletions app/scripts/e2e-run-session.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ else
echo "[runner] Using OPENHUMAN_WORKSPACE from environment: $OPENHUMAN_WORKSPACE"
fi

# Headless Linux CI does not always have a usable Secret Service/keychain.
# Keep E2E credentials under OPENHUMAN_WORKSPACE so auth state is deterministic
# and gets cleaned up with the rest of the test workspace.
: "${OPENHUMAN_KEYRING_BACKEND:=file}"
export OPENHUMAN_KEYRING_BACKEND
echo "[runner] Using OPENHUMAN_KEYRING_BACKEND: $OPENHUMAN_KEYRING_BACKEND"

# Place the CEF cache directory OUTSIDE the workspace. By default the Tauri
# shell roots it under `$OPENHUMAN_WORKSPACE/users/<id>/cef`, but our
# `mega-flow` spec calls `openhuman.config_reset_local_data` between
Expand Down
17 changes: 17 additions & 0 deletions app/test/core-rpc-node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';

import { formatRpcCallFailure } from './e2e/helpers/core-rpc-node';

describe('formatRpcCallFailure', () => {
it('includes the RPC method, status, and error text', () => {
expect(
formatRpcCallFailure('openhuman.composio_list_triggers', {
ok: false,
httpStatus: 500,
error: 'Backend returned 500: trigger store unavailable',
})
).toContain(
'openhuman.composio_list_triggers failed: httpStatus=500 error=Backend returned 500: trigger store unavailable'
);
});
});
42 changes: 42 additions & 0 deletions app/test/e2e/helpers/core-rpc-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,48 @@ let cachedRpcUrl: string | null = null;

const E2E_TOKEN_FILENAME = 'openhuman-e2e-rpc-token';

/** Keep diagnostic payloads compact enough for CI assertion output. */
function truncate(value: string, maxLength = 500): string {
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
}

/** Serialize arbitrary RPC payloads without throwing while formatting failures. */
function safeJson(value: unknown): string {
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}

/** Format failed RPC calls with the method name and any available transport/core error details. */
export function formatRpcCallFailure(method: string, result: RpcCallResult<unknown>): string {
const parts = [`[core-rpc] ${method} failed:`];
if (typeof result.httpStatus === 'number') {
parts.push(`httpStatus=${result.httpStatus}`);
}
if (result.error) {
parts.push(`error=${truncate(result.error)}`);
}
if (result.result !== undefined) {
parts.push(`result=${truncate(safeJson(result.result))}`);
}
if (parts.length === 1) {
parts.push(`payload=${truncate(safeJson(result))}`);
}
return parts.join(' ');
}

/** Assert a positive RPC result while preserving useful failure diagnostics in E2E logs. */
export function expectRpcOk<T>(
method: string,
result: RpcCallResult<T>
): asserts result is RpcCallResult<T> & { ok: true; result: T } {
if (!result.ok) {
throw new Error(formatRpcCallFailure(method, result));
}
}

function readBearerToken(): string | null {
const tokenPath = path.join(os.tmpdir(), E2E_TOKEN_FILENAME);
try {
Expand Down
1 change: 1 addition & 0 deletions app/test/e2e/helpers/core-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { callOpenhumanRpcNode } from './core-rpc-node';
import type { RpcCallResult } from './core-rpc-webview';

export type { RpcCallResult };
export { expectRpcOk, formatRpcCallFailure } from './core-rpc-node';

export async function callOpenhumanRpc<T = unknown>(
method: string,
Expand Down
26 changes: 15 additions & 11 deletions app/test/e2e/specs/mega-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import os from 'node:os';
import path from 'node:path';

import { waitForApp } from '../helpers/app-helpers';
import { callOpenhumanRpc } from '../helpers/core-rpc';
import { callOpenhumanRpc, expectRpcOk } from '../helpers/core-rpc';
import { triggerDeepLink } from '../helpers/deep-link-helpers';
import { hasAppChrome } from '../helpers/element-helpers';
import {
Expand Down Expand Up @@ -247,8 +247,10 @@ describe('Mega flow — login + Gmail OAuth + Composio in one session', () => {
composioActiveTriggers: JSON.stringify([]),
});

const before = await callOpenhumanRpc('openhuman.composio_list_triggers', {});
expect(before.ok).toBe(true);
const listTriggersMethod = 'openhuman.composio_list_triggers';
const enableTriggerMethod = 'openhuman.composio_enable_trigger';
const before = await callOpenhumanRpc(listTriggersMethod, {});
expectRpcOk(listTriggersMethod, before);
// list_triggers always emits a log line → RpcOutcome wraps in {result, logs}.
// JSON-RPC result shape: { result: { triggers: [...] }, logs: [...] }
// callResult.result = { result: { triggers: [...] }, logs: [...] }
Expand All @@ -258,14 +260,14 @@ describe('Mega flow — login + Gmail OAuth + Composio in one session', () => {
expect(Array.isArray(beforeList)).toBe(true);
expect(beforeList).toHaveLength(0);

const enable = await callOpenhumanRpc('openhuman.composio_enable_trigger', {
const enable = await callOpenhumanRpc(enableTriggerMethod, {
connection_id: 'c1',
slug: 'GMAIL_NEW_GMAIL_MESSAGE',
});
expect(enable.ok).toBe(true);
expectRpcOk(enableTriggerMethod, enable);

const after = await callOpenhumanRpc('openhuman.composio_list_triggers', {});
expect(after.ok).toBe(true);
const after = await callOpenhumanRpc(listTriggersMethod, {});
expectRpcOk(listTriggersMethod, after);
const afterList = (after.result?.result?.triggers ?? after.result?.triggers ?? []) as unknown[];
expect(afterList.length).toBeGreaterThan(0);
console.log(`${LOG} composio: enable mutated active list to`, afterList);
Expand Down Expand Up @@ -573,11 +575,13 @@ describe('Mega flow — login + Gmail OAuth + Composio in one session', () => {
});

// Step 1 — enable trigger.
const enable = await callOpenhumanRpc('openhuman.composio_enable_trigger', {
const enableTriggerMethod = 'openhuman.composio_enable_trigger';
const listTriggersMethod = 'openhuman.composio_list_triggers';
const enable = await callOpenhumanRpc(enableTriggerMethod, {
connection_id: 'c2',
slug: 'GITHUB_PULL_REQUEST_EVENT',
});
expect(enable.ok).toBe(true);
expectRpcOk(enableTriggerMethod, enable);
console.log(`${LOG} composio+webhook: trigger enabled`);

// Step 2 — register an echo tunnel so the core has a tunnel ID to work with.
Expand Down Expand Up @@ -617,8 +621,8 @@ describe('Mega flow — login + Gmail OAuth + Composio in one session', () => {

// Step 4 — verify the enabled trigger is still listed.
// list_triggers always emits a log line → {result: {triggers:[...]}, logs:[...]}
const list = await callOpenhumanRpc('openhuman.composio_list_triggers', {});
expect(list.ok).toBe(true);
const list = await callOpenhumanRpc(listTriggersMethod, {});
expectRpcOk(listTriggersMethod, list);
const triggers: unknown[] = list.result?.result?.triggers ?? list.result?.triggers ?? [];
expect(triggers.length).toBeGreaterThan(0);
console.log(
Expand Down
Loading