Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1e32479
refactor(move): relocate concurrency torture lane into daemon
thymikee Oct 16, 2026
b7a69fa
refactor(move): colocate daemon HTTP server integration test
thymikee Oct 16, 2026
c4216f9
refactor(tests): route daemon scenarios through public runtime
thymikee Oct 16, 2026
8b7a211
chore(gates): remove daemon test exports from root tests
thymikee Oct 16, 2026
25a5464
test(provider): allow recording retry scenario time
thymikee Oct 16, 2026
85906db
refactor(move): colocate remote artifact directory test
thymikee Oct 16, 2026
d9cb8f7
test(remote): colocate directory artifact download coverage
thymikee Oct 16, 2026
190cbe0
test(provider): scope fake Apple shutdown and recording
thymikee Oct 16, 2026
f030c3e
test: restore production coverage for daemon export sweep
thymikee Oct 16, 2026
c2745e1
fix: route output gates through daemon API
thymikee Oct 16, 2026
4963d2b
test: assert proxy route diagnostics cleanly
thymikee Oct 16, 2026
6e45c7f
test: keep proxy diagnostics and cleanup covered
thymikee Oct 16, 2026
7bca1ee
test: assert proxied diagnostics materialize
thymikee Oct 16, 2026
4536b2d
test: restore provider and settle assertions
thymikee Oct 16, 2026
c20ce89
fix(test): redact proxy upstream errors
thymikee Oct 16, 2026
8516a7b
fix(test): pin parity proxy requests to loopback
thymikee Oct 16, 2026
1f5090d
test: assert daemon artifact proxy sequence
thymikee Oct 16, 2026
c34cdac
test: avoid recording proxy error stacks
thymikee Oct 16, 2026
043d2b8
test: close daemon on proxy startup failure
thymikee Oct 16, 2026
bc87c96
test: align help recovery cases with samples
thymikee Oct 16, 2026
538580a
refactor(test): split provider scenario proxy harness
thymikee Oct 17, 2026
399f25b
test: pin audio evidence and real daemon HTTP statuses
thymikee Oct 17, 2026
9f058e9
test: expect daemon status for session conflict
thymikee Oct 17, 2026
025fd84
test(provider-scenarios): pin explicit error HTTP statuses
thymikee Oct 17, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ jobs:
with: { gate: integration-node }

- name: Run seeded concurrency torture lane (fast PR sweep)
# #1416's nightly torture lane lives under test/integration/nightly/, out
# #1416's nightly torture lane lives under packages/daemon/test/nightly/, out
# of the test:integration:node glob, so this is a *deliberate* fast PR
# sweep (TORTURE_RUNS default 128 seeds, ~sub-second) — not an accidental
# glob inclusion. The Concurrency Torture Nightly workflow sweeps a much
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@
"test:integration:node": "node --experimental-strip-types scripts/node-test-tmpdir.ts --test --experimental-test-isolation=none test/integration/*.test.ts",
"test:integration:macos-coverage": "node --experimental-strip-types scripts/node-test-tmpdir.ts --test test/integration/smoke-macos-coverage.test.ts",
"test:integration": "pnpm test:integration:node && pnpm test:integration:provider",
"test:concurrency-torture": "node --experimental-strip-types scripts/node-test-tmpdir.ts --test test/integration/nightly/concurrency-torture.test.ts",
"test:concurrency-torture": "node --experimental-strip-types scripts/node-test-tmpdir.ts --test packages/daemon/test/nightly/concurrency-torture.test.ts",
"test:replay:ios": "node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator",
"test:replay:ios-device": "node --experimental-strip-types src/bin.ts test test/integration/replays/ios/device",
"test:replay:android": "node --experimental-strip-types src/bin.ts test test/integration/replays/android/emulator",
Expand Down
60 changes: 0 additions & 60 deletions packages/daemon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,10 @@
"types": "./src/core/context.ts",
"default": "./src/core/context.ts"
},
"./testing/daemon-command-registry": {
"types": "./src/core/daemon-command-registry.ts",
"default": "./src/core/daemon-command-registry.ts"
},
"./testing/device/device-claim-conflict": {
"types": "./src/resources/device/device-claim-conflict.ts",
"default": "./src/resources/device/device-claim-conflict.ts"
},
"./testing/handlers/session-app-deployment": {
"types": "./src/sessions/handlers/session-app-deployment.ts",
"default": "./src/sessions/handlers/session-app-deployment.ts"
},
"./testing/interaction/index": {
"types": "./src/execution/interaction/index.ts",
"default": "./src/execution/interaction/index.ts"
},
"./testing/interaction/internal/interaction-touch-response": {
"types": "./src/execution/interaction/internal/interaction-touch-response.ts",
"default": "./src/execution/interaction/internal/interaction-touch-response.ts"
},
"./testing/interactor-resolution": {
"types": "./src/core/interactor-resolution.ts",
"default": "./src/core/interactor-resolution.ts"
},
"./testing/ios-app-session-hint": {
"types": "./src/core/ios-app-session-hint.ts",
"default": "./src/core/ios-app-session-hint.ts"
},
"./testing/lease-registry": {
"types": "./src/resources/lease-registry.ts",
"default": "./src/resources/lease-registry.ts"
Expand All @@ -100,42 +76,18 @@
"types": "./src/core/ref-frame.ts",
"default": "./src/core/ref-frame.ts"
},
"./testing/request-binding": {
"types": "./src/server/request-binding.ts",
"default": "./src/server/request-binding.ts"
},
"./testing/request-dispatch-ledger": {
"types": "./src/core/request-dispatch-ledger.ts",
"default": "./src/core/request-dispatch-ledger.ts"
},
"./testing/request-execution-scope": {
"types": "./src/server/request-execution-scope.ts",
"default": "./src/server/request-execution-scope.ts"
},
"./testing/request-finalization": {
"types": "./src/server/request-finalization.ts",
"default": "./src/server/request-finalization.ts"
},
"./testing/request-router": {
"types": "./src/server/request-router.ts",
"default": "./src/server/request-router.ts"
},
"./testing/request-runtime-binding": {
"types": "./src/core/request-runtime-binding.ts",
"default": "./src/core/request-runtime-binding.ts"
},
"./testing/response-views": {
"types": "./src/server/response-views.ts",
"default": "./src/server/response-views.ts"
},
"./testing/scroll-until": {
"types": "./src/execution/scroll-until.ts",
"default": "./src/execution/scroll-until.ts"
},
"./testing/selector-match-errors": {
"types": "./src/execution/selector-match-errors.ts",
"default": "./src/execution/selector-match-errors.ts"
},
"./testing/server/http-server": {
"types": "./src/server/http/http-server.ts",
"default": "./src/server/http/http-server.ts"
Expand All @@ -144,14 +96,6 @@
"types": "./src/core/session-action-recorder.ts",
"default": "./src/core/session-action-recorder.ts"
},
"./testing/session-observability/__tests__/*": {
"types": "./src/sessions/session-observability/__tests__/*.ts",
"default": "./src/sessions/session-observability/__tests__/*.ts"
},
"./testing/session-recovery-hints": {
"types": "./src/core/session-recovery-hints.ts",
"default": "./src/core/session-recovery-hints.ts"
},
"./testing/session-snapshot": {
"types": "./src/execution/session-snapshot.ts",
"default": "./src/execution/session-snapshot.ts"
Expand All @@ -163,10 +107,6 @@
"./testing/session-store": {
"types": "./src/core/session-store.ts",
"default": "./src/core/session-store.ts"
},
"./testing/web-session-names": {
"types": "./src/sessions/web-session-names.ts",
"default": "./src/sessions/web-session-names.ts"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isSessionRecording } from '../session-script-publication-capability.ts'
* (`selector-recording.test.ts` covers which commands set `interactiveObservation`).
*/
import { test, expect } from 'vitest';
import { recordActionEntry } from '../session-action-recorder.ts';
import { recordActionEntry, type RecordActionEntry } from '../session-action-recorder.ts';
import { authoringPublication, makeIosSession } from '../../__tests__/support/session-factories.ts';

test('an observation-only action is excluded while repair-armed and no --record is given', () => {
Expand Down Expand Up @@ -64,6 +64,38 @@ test('an observation-only action records normally outside a repair-armed session
expect(session.actions.map((a) => a.command)).toEqual(['is']);
});

test('ordinary action history preserves authored commands, positionals, and replay flags', () => {
const session = makeIosSession('default');
const entries = [
{ command: 'hover', positionals: ['@e4'], flags: {}, result: {} },
{ command: 'click', positionals: ['@e4'], flags: {}, result: {} },
{
command: 'fill',
positionals: ['@e3', 'qa@example.test'],
flags: { delayMs: 1 },
result: {},
},
{ command: 'type', positionals: [' ok'], flags: {}, result: {} },
{ command: 'pan', positionals: ['195', '320'], flags: { pointerCount: 2 }, result: {} },
{ command: 'tv-remote', positionals: ['select'], flags: {}, result: {} },
{ command: 'fold', positionals: [], flags: { keyframes: 'fold.json' }, result: {} },
{
command: 'trigger-app-event',
positionals: ['screenshot_taken'],
flags: {},
result: {},
},
{ command: 'back', positionals: [], flags: {}, result: {} },
{ command: 'home', positionals: [], flags: {}, result: {} },
] satisfies RecordActionEntry[];

for (const entry of entries) recordActionEntry(session, entry);

expect(
session.actions.map(({ command, positionals, flags }) => ({ command, positionals, flags })),
).toEqual(entries.map(({ command, positionals, flags }) => ({ command, positionals, flags })));
});

test('a mutating action is never excluded, repair-armed or not', () => {
const session = makeIosSession('default', {
scriptPublication: {
Expand Down Expand Up @@ -123,6 +155,19 @@ test('--no-record still takes precedence over an observation-only action, repair
expect(session.actions).toHaveLength(0);
});

test('--no-record suppresses a mutating coordinate press from action history', () => {
const session = makeIosSession('default');
const action = recordActionEntry(session, {
command: 'press',
positionals: ['50', '60'],
flags: { noRecord: true },
result: { x: 50, y: 60 },
});

expect(action).toBeUndefined();
expect(session.actions).toHaveLength(0);
});

test('parameterized fills keep literals out of recording state with deterministic placeholders', () => {
const session = makeIosSession('default');
const password = 'literal-password-1348';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, test } from 'vitest';
import type { PressCommandResult } from '@agent-device/contracts/interaction';
import { buildInteractionResponseData } from './interaction-touch-response.ts';

test('the response builder keeps capture evidence out of serialized touch payloads', () => {
const node = { ref: 'e1', index: 0, type: 'Button', label: 'Continue' };
const result = {
kind: 'selector',
point: { x: 100, y: 200 },
target: { kind: 'selector', selector: 'label=Continue' },
node,
selectorChain: ['label=Continue'],
preAction: { nodes: [node] },
backendResult: { ref: 'e1' },
targetEvidence: { id: 'target-evidence-private' },
} as PressCommandResult & { targetEvidence: { id: string } };

const payloads = buildInteractionResponseData({
source: { kind: 'runtime', result },
referenceFrame: undefined,
});

expect(payloads.recordedTarget).toEqual({ node, preActionNodes: [node] });
for (const payload of [payloads.result, payloads.responseData]) {
expect(payload).not.toHaveProperty('node');
expect(payload).not.toHaveProperty('preAction');
expect(payload).not.toHaveProperty('targetEvidence');
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
mockHoverRef,
mockLongPressPoint,
mockTapPoint,
mockTypeText,
resetGetRuntimeFixture,
runtimeBindingSpies,
} from '../../__tests__/interaction-get-runtime-fixture.ts';
Expand Down Expand Up @@ -237,7 +238,69 @@ test('hover @ref on web dispatches through the provider hoverRef route, not coor
});
expect(mockHoverRef).toHaveBeenCalledWith(expect.objectContaining({ ref: '@e1' }));
expect(mockHoverPoint).not.toHaveBeenCalled();
expect(sessionStore.get(sessionName)?.actions[0]?.command).toBe('hover');
const hoverAction = sessionStore.get(sessionName)?.actions[0];
expect(hoverAction?.command).toBe('hover');
expect(hoverAction?.result).toMatchObject({ ref: 'e1', gesture: 'hover' });
expect(hoverAction?.result).not.toHaveProperty('x');
expect(hoverAction?.result).not.toHaveProperty('y');

const typeResponse = await handleInteractionCommands({
createCommandSurface: touchCreateCommandSurface,
req: {
token: 't',
session: sessionName,
command: 'type',
positionals: [' ok'],
flags: {},
},
sessionName,
sessionStore,
contextFromFlags,
...getRuntimeBindings(),
});

expect(typeResponse?.ok).toBe(true);
expect(mockTypeText).toHaveBeenCalledOnce();
expect(sessionStore.get(sessionName)?.actions.at(-1)).toMatchObject({
command: 'type',
positionals: [' ok'],
});
});

test('click @ref on web records the ref result without fabricated coordinates', async () => {
const sessionStore = makeSessionStore();
const sessionName = 'click-ref';
const session = makeSession(sessionName);
session.device = WEB_DESKTOP_DEVICE;
session.snapshot = {
nodes: attachRefs([{ index: 0, role: 'link', label: 'Second message', enabled: true }]),
createdAt: Date.now(),
backend: 'web',
};
sessionStore.publish(sessionName, session);

const response = await handleInteractionCommands({
createCommandSurface: touchCreateCommandSurface,
req: {
token: 't',
session: sessionName,
command: 'click',
positionals: ['@e1'],
flags: {},
},
sessionName,
sessionStore,
contextFromFlags,
...getRuntimeBindings(),
});

expect(response).toMatchObject({ ok: true, data: { ref: 'e1' } });
const action = sessionStore.get(sessionName)?.actions[0];
expect(action?.command).toBe('click');
expect(action?.positionals).toEqual(['@e1']);
expect(action?.result).toMatchObject({ ref: 'e1' });
expect(action?.result).not.toHaveProperty('x');
expect(action?.result).not.toHaveProperty('y');
});

test('hover selector on web resolves the target and dispatches coordinate hover', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ test('find get-text digest keeps ref + text, drops the verbose node', () => {
expect('node' in digest).toBe(false);
});

test('a text read keeps every OTHER cheap field (e.g. warning) while dropping the node', () => {
test('selector text digest preserves a recovery warning while dropping the verbose node', () => {
const digest = findView!(
{
ref: '@e7',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import fs from 'node:fs';
import http from 'node:http';
import os from 'node:os';
import path from 'node:path';
import { test } from 'vitest';
import { AppError } from '@agent-device/kernel/errors';
import {
cleanupUploadedArtifact,
prepareUploadedArtifact,
trackDownloadableArtifact,
} from '@agent-device/daemon/testing/artifact-tracking';
} from '../../resources/artifact-tracking.ts';
import { DAEMON_RPC_PROTOCOL_VERSION } from '@agent-device/contracts/daemon-http';
import { createDaemonHttpServer } from '@agent-device/daemon/testing/server/http-server';
import { createDaemonHttpServer } from './http-server.ts';
import {
emitRequestProgress,
getRequestSignal,
Expand All @@ -24,9 +23,9 @@ import {
import {
closeLoopbackServer,
listenOnLoopback,
mkdtempForTestSync,
skipWhenLoopbackUnavailable,
} from '@agent-device/test-kit';
import { restoreEnv } from './harness.ts';

type RpcResponse = {
status: number;
Expand Down Expand Up @@ -476,7 +475,7 @@ test('Provider-backed integration daemon HTTP server accepts uploads and streams
return;
}

const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-http-server-'));
const root = mkdtempForTestSync('agent-device-http-server-');
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
const downloadablePath = path.join(root, 'screen.png');
fs.writeFileSync(downloadablePath, 'png-binary');
const artifactId = trackDownloadableArtifact({
Expand Down Expand Up @@ -526,7 +525,6 @@ test('Provider-backed integration daemon HTTP server accepts uploads and streams
assert.equal(rejectedUpload.status, 401);
} finally {
await closeLoopbackServer(server);
fs.rmSync(root, { recursive: true, force: true });
}
});

Expand Down Expand Up @@ -759,7 +757,7 @@ test('Provider-backed integration daemon HTTP auth hook can scope tenants and re
return;
}

const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-http-auth-hook-'));
const root = mkdtempForTestSync('agent-device-http-auth-hook-');
const hookPath = path.join(root, 'auth-hook.mjs');
fs.writeFileSync(
hookPath,
Expand Down Expand Up @@ -817,8 +815,8 @@ test('Provider-backed integration daemon HTTP auth hook can scope tenants and re
if (server) {
await closeLoopbackServer(server);
}
restoreEnv('AGENT_DEVICE_HTTP_AUTH_HOOK', previousHook);
fs.rmSync(root, { recursive: true, force: true });
if (previousHook === undefined) delete process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;
else process.env.AGENT_DEVICE_HTTP_AUTH_HOOK = previousHook;
}
});

Expand Down

This file was deleted.

Loading
Loading