Merge branch 'ota-w-testprune' of /tmp/orca-fix/testprune into ota-wave-integrate

# Conflicts:
#	mobile/src/mobile-web/mobile-web-shell-response-schema-corpus.test.ts
This commit is contained in:
Jinwoo-H
2026-09-07 16:12:03 -04:00
58 changed files with 0 additions and 8101 deletions
@@ -141,7 +141,6 @@ inspectable APK again.
## Gates
```bash
pnpm test mobile/src/mobile-web/hosted-webview-cdp-session.test.ts
pnpm test mobile/src/mobile-web/hosted-android-inspectable-release.test.ts
cd mobile && npx tsc --noEmit -p .
pnpm run check:code-quality:changed
@@ -1,112 +0,0 @@
import { readFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { hostedAdversarialGitHubResponse } from '../../scripts/hosted-adversarial-github-cli.mjs'
import {
createHostedAdversarialProviderFixture,
HOSTED_ADVERSARIAL_MERMAID_MARKER,
HOSTED_ADVERSARIAL_PROVIDER_ERROR_MARKER,
HOSTED_ADVERSARIAL_PROVIDER_MARKER,
HOSTED_ADVERSARIAL_PROVIDER_TITLE_MARKER,
removeHostedAdversarialProviderFixture
} from '../../scripts/hosted-adversarial-provider-fixture.mjs'
import {
createHostedAdversarialRepositoryFixture,
removeHostedAdversarialRepositoryFixture
} from '../../scripts/hosted-adversarial-repository-fixture.mjs'
const config = {
baseOid: 'a'.repeat(40),
branch: 'orca-adversarial-row',
body: 'provider body',
comment: `${HOSTED_ADVERSARIAL_MERMAID_MARKER}\n\n\`\`\`mermaid\ngraph TD\nStart --> Done\n\`\`\``,
error: `${HOSTED_ADVERSARIAL_PROVIDER_ERROR_MARKER}: hostile`,
headOid: 'b'.repeat(40),
title: `${HOSTED_ADVERSARIAL_PROVIDER_TITLE_MARKER}: hostile`,
updatedAt: '2026-07-29T00:00:00Z'
}
const androidHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-source-control-review-e2e.mjs', import.meta.url),
'utf8'
)
describe('hosted adversarial provider fixture', () => {
it('returns one hostile task while another repository error stays bounded', async () => {
const tasks = await hostedAdversarialGitHubResponse(
['api', 'search/issues?q=repo%3Aorca-e2e%2Fadversarial'],
config
)
const issues = await hostedAdversarialGitHubResponse(
['api', 'search/issues?q=repo%3Astablyai%2Forca'],
config
)
expect(tasks.code).toBe(0)
expect(JSON.parse(tasks.stdout)).toMatchObject([
{
number: 17,
title: expect.stringContaining(HOSTED_ADVERSARIAL_PROVIDER_TITLE_MARKER)
}
])
expect(issues).toMatchObject({
code: 1,
stderr: expect.stringContaining(HOSTED_ADVERSARIAL_PROVIDER_ERROR_MARKER)
})
expect(issues.stderr.length).toBeLessThanOrEqual(1_000)
})
it('registers success and error repositories in the Android runtime', () => {
expect(androidHarnessSource).toContain("stage('provider error workspace registration'")
expect(androidHarnessSource).toContain('registerWorktreeForPairingRuntime(runtime, worktree, {')
expect(androidHarnessSource).toContain(
'registerWorktreeForPairingRuntime(runtime, testWorkspace, {'
)
})
it('returns a provider comment with the Mermaid corpus', async () => {
const response = await hostedAdversarialGitHubResponse(
[
'api',
'graphql',
'-f',
'query=query { repository { pullRequest { reviewThreads(first: 100) { nodes { id } } } } }'
],
config
)
const parsed = JSON.parse(response.stdout)
expect(parsed.data.repository.pullRequest.comments.nodes[0].body).toContain(
HOSTED_ADVERSARIAL_MERMAID_MARKER
)
expect(parsed.data.repository.pullRequest.comments.nodes[0].body).toContain('```mermaid')
})
it('installs an isolated cross-platform gh shim and repository remote', async () => {
const repository = await createHostedAdversarialRepositoryFixture()
let fixture
try {
fixture = await createHostedAdversarialProviderFixture({
probePort: 43210,
repositoryRoot: repository.root
})
const fixtureConfig = JSON.parse(await readFile(fixture.configPath, 'utf8'))
expect(fixture.environment.PATH.split(path.delimiter)[0]).toContain(fixture.root)
expect(fixtureConfig.body).toContain(`\`${HOSTED_ADVERSARIAL_PROVIDER_MARKER}\``)
expect(fixtureConfig.comment).toContain(`\`${HOSTED_ADVERSARIAL_MERMAID_MARKER}\``)
expect(fixtureConfig.comment).toContain('http://127.0.0.1:43210/')
expect(fixtureConfig.comment).toContain(
'Start --> Parse --> Sanitize --> Render --> Verify --> Done'
)
expect(await readFile(path.join(fixture.root, 'bin', 'gh.cmd'), 'utf8')).toContain(
'hosted-adversarial-github-cli.mjs'
)
} finally {
if (fixture) {
await removeHostedAdversarialProviderFixture(fixture, repository.root)
}
await removeHostedAdversarialRepositoryFixture(repository)
}
})
})
@@ -1,299 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it, vi } from 'vitest'
import {
HOSTED_TERMINAL_HTTP_LINK_LABEL,
HOSTED_TERMINAL_JAVASCRIPT_LINK_LABEL,
HOSTED_TERMINAL_FILE_LINK_LABEL,
hostedAdversarialTerminalLinkCommand,
hostedAdversarialTerminalLinkSafetyEvidence,
stageHostedAdversarialTerminalLinksWithInput,
verifyHostedAdversarialTerminalLinks
} from '../../scripts/hosted-adversarial-terminal-links.mjs'
const androidHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-source-control-review-e2e.mjs', import.meta.url),
'utf8'
)
const androidTerminalHarnessSource = readFileSync(
new URL('../../scripts/hosted-android-adversarial-terminal-links.mjs', import.meta.url),
'utf8'
)
const iosHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-webview-simulator-e2e.mjs', import.meta.url),
'utf8'
)
describe('hosted adversarial terminal links', () => {
it('emits bounded OSC-8 HTTP and javascript rows through a portable Node command', () => {
const command = hostedAdversarialTerminalLinkCommand(43210, 'PROBE-TOKEN', '001-adversarial.md')
const encoded = command.match(/Buffer\.from\('([^']+)'/)?.[1]
expect(command).not.toContain('javascript:')
expect(encoded).toBeTruthy()
const output = Buffer.from(encoded ?? '', 'base64').toString()
expect(output).toContain('\u001B]8;;http://127.0.0.1:43210/terminal-link/PROBE-TOKEN\u001B\\')
expect(output).toContain(
"\u001B]8;;javascript:globalThis.__ORCA_HOSTED_TERMINAL_LINK_EXECUTED__='executed'\u001B\\"
)
expect(output).toContain('\u001B]8;;001-adversarial.md:1\u001B\\')
expect(output.match(new RegExp(HOSTED_TERMINAL_HTTP_LINK_LABEL, 'gu'))).toHaveLength(2)
expect(output.match(new RegExp(HOSTED_TERMINAL_JAVASCRIPT_LINK_LABEL, 'gu'))).toHaveLength(2)
expect(output.match(new RegExp(HOSTED_TERMINAL_FILE_LINK_LABEL, 'gu'))).toHaveLength(2)
})
it('proves the allowed file tap before rejecting a native javascript tap', async () => {
const initialDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
const resumedDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace',
targetId: 'resumed'
}
const filePoint = { x: 0.2, y: 0.7 }
const httpPoint = { x: 0.2, y: 0.6 }
const javascriptPoint = { x: 0.2, y: 0.8 }
const probe = {
observations: [],
port: 43210,
reset() {
this.observations.splice(0)
},
token: 'PROBE-TOKEN'
}
const tapPoint = vi.fn().mockResolvedValue(undefined)
const waitForDocument = vi.fn().mockResolvedValue(resumedDocument)
const writeLinks = vi.fn().mockResolvedValue('terminal-handle')
await expect(
verifyHostedAdversarialTerminalLinks(
{
discoveryUrl: 'http://127.0.0.1:9222',
document: initialDocument,
emulator: { deviceUdid: 'simulator' },
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
positiveFilePath: '001-adversarial.md',
probe,
tapPoint,
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
},
{
activateTerminal: vi.fn().mockResolvedValue(undefined),
enableDiagnostics: vi.fn().mockResolvedValue(undefined),
readPoints: vi
.fn()
.mockResolvedValueOnce({
file: filePoint,
http: httpPoint,
javascript: javascriptPoint
})
.mockResolvedValueOnce({
file: filePoint,
http: httpPoint,
javascript: javascriptPoint
}),
readState: vi.fn().mockResolvedValue({
href: resumedDocument.href,
javascriptMarkerPresent: false,
javascriptMarkerValue: null
}),
settle: vi.fn().mockResolvedValue(undefined),
waitForDocument,
writeLinks
}
)
).resolves.toEqual({
evidence: {
fileLinkOpenedAfterNativeTap: true,
httpLinkRequestedWithoutTap: false,
javascriptLinkEscaped: false,
javascriptLinkExecuted: false,
javascriptTapPoint: javascriptPoint,
routeRetained: true,
terminalHandle: 'terminal-handle'
},
sessionDocument: resumedDocument
})
expect(tapPoint).toHaveBeenNthCalledWith(1, { deviceUdid: 'simulator' }, filePoint)
expect(tapPoint).toHaveBeenNthCalledWith(2, { deviceUdid: 'simulator' }, javascriptPoint)
expect(writeLinks).toHaveBeenCalledWith({
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
positiveFilePath: '001-adversarial.md',
probePort: 43210,
probeToken: 'PROBE-TOKEN',
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
})
expect(waitForDocument).toHaveBeenCalledWith({
discoveryUrl: 'http://127.0.0.1:9222',
expectedHrefIncludes: '/files/preview/',
expectedText: '001-adversarial.md',
requireInteractiveControls: false,
timeoutMs: 5_000
})
expect(probe.observations).toEqual([])
})
it('fails closed on execution, navigation, or escaped network traffic', () => {
const safeState = {
href: 'orca-mobile-web://build/h/host/session/workspace',
javascriptMarkerPresent: false,
javascriptMarkerValue: null
}
expect(
hostedAdversarialTerminalLinkSafetyEvidence({
observations: [],
state: safeState
})
).toEqual({
javascriptLinkEscaped: false,
javascriptLinkExecuted: false,
routeRetained: true
})
expect(() =>
hostedAdversarialTerminalLinkSafetyEvidence({
observations: [],
state: {
...safeState,
javascriptMarkerPresent: true,
javascriptMarkerValue: 'executed'
}
})
).toThrow('javascript link executed')
expect(() =>
hostedAdversarialTerminalLinkSafetyEvidence({
observations: ['http:/terminal-link/PROBE-TOKEN'],
state: safeState
})
).toThrow('javascript link escaped')
})
it('retries a missed native file-link activation before testing javascript', async () => {
const document = { href: 'orca-mobile-web://build/h/host/session/workspace' }
const tapPoint = vi.fn().mockResolvedValue(undefined)
const waitForDocument = vi
.fn()
.mockRejectedValueOnce(new Error('missed native touch'))
.mockResolvedValue(document)
const readPoints = vi.fn().mockResolvedValue({
file: { x: 0.2, y: 0.7 },
http: { x: 0.2, y: 0.6 },
javascript: { x: 0.2, y: 0.8 }
})
await verifyHostedAdversarialTerminalLinks(
{
discoveryUrl: 'http://127.0.0.1:9222',
document,
emulator: {},
positiveFilePath: '001-adversarial.md',
probe: { observations: [], port: 43210, token: 'PROBE-TOKEN' },
tapPoint,
timeoutMs: 30_000
},
{
activateTerminal: vi.fn().mockResolvedValue(undefined),
enableDiagnostics: vi.fn().mockResolvedValue(undefined),
readPoints,
readState: vi.fn().mockResolvedValue({
href: document.href,
javascriptMarkerPresent: false,
javascriptMarkerValue: null
}),
settle: vi.fn().mockResolvedValue(undefined),
waitForDocument,
writeLinks: vi.fn().mockResolvedValue('terminal-handle')
}
)
expect(waitForDocument).toHaveBeenCalledTimes(3)
expect(readPoints).toHaveBeenCalledTimes(3)
expect(tapPoint).toHaveBeenCalledTimes(3)
})
it('taps the duplicate file-link row after a missed native activation', async () => {
const document = { href: 'orca-mobile-web://build/h/host/session/workspace' }
const file = { x: 0.2, y: 0.7 }
const fileAlternate = { x: 0.2, y: 0.72 }
const tapPoint = vi.fn().mockResolvedValue(undefined)
const prepareFileTap = vi.fn().mockResolvedValue(undefined)
await verifyHostedAdversarialTerminalLinks(
{
discoveryUrl: 'http://127.0.0.1:9222',
document,
emulator: {},
positiveFilePath: '001-adversarial.md',
probe: { observations: [], port: 43210, token: 'PROBE-TOKEN' },
tapPoint,
timeoutMs: 30_000
},
{
activateTerminal: vi.fn().mockResolvedValue(undefined),
enableDiagnostics: vi.fn().mockResolvedValue(undefined),
prepareFileTap,
readPoints: vi.fn().mockResolvedValue({
file,
fileAlternate,
http: { x: 0.2, y: 0.6 },
javascript: { x: 0.2, y: 0.8 }
}),
readState: vi.fn().mockResolvedValue({
href: document.href,
javascriptMarkerPresent: false,
javascriptMarkerValue: null
}),
settle: vi.fn().mockResolvedValue(undefined),
waitForDocument: vi
.fn()
.mockRejectedValueOnce(new Error('missed'))
.mockResolvedValue(document),
writeLinks: vi.fn().mockResolvedValue('terminal-handle')
}
)
expect(tapPoint).toHaveBeenNthCalledWith(1, {}, file)
expect(tapPoint).toHaveBeenNthCalledWith(2, {}, fileAlternate)
expect(prepareFileTap).toHaveBeenCalledTimes(2)
})
it('retries missed live terminal activations without changing the staged command', async () => {
const inputCommand = vi.fn().mockResolvedValue(undefined)
const waitForStage = vi
.fn()
.mockRejectedValueOnce(new Error('missed'))
.mockRejectedValueOnce(new Error('missed'))
.mockResolvedValue(undefined)
const prepared = {
command: 'node .git/stage.cjs',
terminalHandle: 'terminal-handle'
}
const prepare = vi.fn().mockResolvedValue(prepared)
await expect(
stageHostedAdversarialTerminalLinksWithInput({ timeoutMs: 30_000 }, inputCommand, {
prepare,
waitForStage
})
).resolves.toBe('terminal-handle')
expect(inputCommand).toHaveBeenCalledTimes(3)
expect(inputCommand).toHaveBeenCalledWith(prepared.command)
expect(prepare).toHaveBeenCalledWith({ timeoutMs: 30_000 })
expect(waitForStage).toHaveBeenCalledTimes(3)
expect(waitForStage).toHaveBeenCalledWith(prepared, 5_000)
})
it('keeps the exact iOS and Android adversarial gates wired to the same corpus', () => {
expect(iosHarnessSource).toContain('verifyHostedIosAdversarialTerminalLinks')
expect(androidHarnessSource).toContain('verifyHostedAndroidAdversarialTerminalLinks')
expect(androidTerminalHarnessSource).toContain('verifyHostedAdversarialTerminalLinks')
expect(iosHarnessSource).toContain('adversarial terminal links')
expect(androidHarnessSource).toContain('adversarial terminal links')
})
})
@@ -1,97 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
dismissHostedAndroidKeyboardIfShown,
hostedAndroidAdversarialTerminalInputText,
verifyHostedAndroidAdversarialTerminalLinks
} from '../../scripts/hosted-android-adversarial-terminal-links.mjs'
describe('hosted Android adversarial terminal links', () => {
it('stages links through the focused live terminal input', async () => {
const activateTerminal = vi.fn().mockResolvedValue(undefined)
const dismissKeyboard = vi.fn().mockResolvedValue(false)
const runAdb = vi.fn().mockResolvedValue('')
const stageWithInput = vi.fn(async (_args, inputCommand) => {
await inputCommand('node ".git/orca-mobile-terminal-links.cjs"')
return 'terminal-handle'
})
const tapControl = vi.fn().mockResolvedValue(undefined)
const verifyLinks = vi.fn(async (_args, operations) => {
return operations.writeLinks({ timeoutMs: 30_000 })
})
const args = {
document: { targetId: 'android-webview' },
emulator: { adb: '/sdk/platform-tools/adb' },
timeoutMs: 30_000
}
await expect(
verifyHostedAndroidAdversarialTerminalLinks(args, {
activateTerminal,
dismissKeyboard,
runAdb,
stageWithInput,
tapControl,
verifyLinks
})
).resolves.toBe('terminal-handle')
expect(activateTerminal).toHaveBeenCalledWith(args.document, {
kind: 'text',
value: 'Mobile Emulator'
})
expect(tapControl).toHaveBeenCalledWith(
args.emulator,
'Show keyboard for live terminal input',
30_000
)
expect(runAdb).toHaveBeenNthCalledWith(1, args.emulator.adb, [
'shell',
'input',
'text',
'node%s.git/orca-mobile-terminal-links.cjs'
])
expect(runAdb).toHaveBeenNthCalledWith(2, args.emulator.adb, [
'shell',
'input',
'keyevent',
'KEYCODE_ENTER'
])
expect(runAdb).toHaveBeenCalledTimes(2)
expect(dismissKeyboard).toHaveBeenCalledWith(args.emulator.adb, runAdb)
})
it('dismisses only an observed Android input method', async () => {
const visibleRunAdb = vi
.fn()
.mockResolvedValueOnce(
'InsetsSource id=20b40001 type=navigationBars frame=[0,2712][1280,2856] visible=true\nmImeShowing=true'
)
.mockResolvedValueOnce('')
.mockResolvedValueOnce('mImeShowing=false')
await expect(
dismissHostedAndroidKeyboardIfShown('/sdk/platform-tools/adb', visibleRunAdb)
).resolves.toBe(true)
expect(visibleRunAdb).toHaveBeenNthCalledWith(2, '/sdk/platform-tools/adb', [
'shell',
'input',
'tap',
'128',
'2784'
])
const hiddenRunAdb = vi.fn().mockResolvedValue('mImeShowing=false')
await expect(
dismissHostedAndroidKeyboardIfShown('/sdk/platform-tools/adb', hiddenRunAdb)
).resolves.toBe(false)
expect(hiddenRunAdb).toHaveBeenCalledOnce()
})
it('accepts only the generated disposable script command', () => {
expect(
hostedAndroidAdversarialTerminalInputText('node ".git/orca-mobile-terminal-links.cjs"')
).toBe('node%s.git/orca-mobile-terminal-links.cjs')
expect(() => hostedAndroidAdversarialTerminalInputText('node other.cjs')).toThrow(
'staging command is invalid'
)
})
})
@@ -1,103 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateControl: vi.fn(),
readControlPoint: vi.fn(),
readState: vi.fn(),
readStablePoint: vi.fn(),
readTextPoint: vi.fn(),
setInput: vi.fn(),
tapPoint: vi.fn(),
waitForDocument: vi.fn()
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activateControl,
readHostedWebViewState: mocks.readState,
readHostedWebViewTextPoint: mocks.readTextPoint,
setHostedWebViewInput: mocks.setInput,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
vi.mock('../../scripts/hosted-android-emulator-accessibility.mjs', () => ({
tapHostedAndroidPoint: mocks.tapPoint
}))
vi.mock('../../scripts/hosted-android-webview-touch-point.mjs', () => ({
readStableHostedAndroidWebViewPoint: mocks.readStablePoint
}))
vi.mock('../../scripts/hosted-webview-control-point.mjs', () => ({
readHostedWebViewControlPoint: mocks.readControlPoint
}))
import { verifyHostedAndroidAgentHistoryJourney } from '../../scripts/hosted-android-agent-history-journey.mjs'
describe('hosted Android Agent History journey', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.activateControl.mockResolvedValue(undefined)
mocks.readControlPoint.mockResolvedValue({ x: 0.4, y: 0.3 })
mocks.readStablePoint.mockImplementation((readPoint) => readPoint())
mocks.readTextPoint.mockResolvedValue({ x: 0.5, y: 0.4 })
mocks.setInput.mockResolvedValue(undefined)
mocks.tapPoint.mockImplementation((_emulator, point) => point)
mocks.readState.mockResolvedValue({
href: 'orca-mobile-web://build/h/host/agent-history/workspace',
bodyText: 'Workspace Project All',
labels: ['Back', 'Refresh agent sessions', 'Resume agent session'],
placeholders: ['Search sessions, repo:, path:']
})
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes, expectedText }) => ({
href: `orca-mobile-web://build/h/host${
expectedHrefIncludes ??
(expectedText === '2 tabs' || expectedText === 'Agent History'
? '/session/'
: '/agent-history/')
}workspace`
}))
})
it('covers the existing scopes, preview, search, native resume, and Back flow', async () => {
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
const result = await verifyHostedAndroidAgentHistoryJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { adb: '/sdk/adb' },
sessionDocument,
timeoutMs: 30_000
})
const { returnedSessionDocument, ...evidence } = result
expect(evidence).toEqual({
route: 'orca-mobile-web://build/h/host/agent-history/workspace',
scopes: ['Workspace', 'Project', 'All'],
search: 'present',
row: 'Hybrid Agent History Fixture',
preview: 'E2E_AGENT_HISTORY_PREVIEW_MARKER',
resume: {
native: 'queued',
nativeTouchPoint: { x: 0.4, y: 0.3 },
resumedRoute: 'orca-mobile-web://build/h/host/session/workspace'
},
headerControls: ['Back', 'Refresh agent sessions']
})
expect(returnedSessionDocument.href).toContain('/session/')
expect(mocks.setInput).toHaveBeenNthCalledWith(1, expect.anything(), {
placeholder: 'Search sessions, repo:, path:',
value: 'NO_MATCHING_AGENT_HISTORY_SESSION'
})
expect(mocks.setInput).toHaveBeenNthCalledWith(2, expect.anything(), {
placeholder: 'Search sessions, repo:, path:',
value: ''
})
expect(mocks.readControlPoint.mock.calls.map((call) => call[1])).toEqual([
'More session actions',
'Resume agent session'
])
expect(mocks.readTextPoint.mock.calls.map((call) => call[1])).toEqual(['Agent History'])
expect(mocks.tapPoint).toHaveBeenCalledTimes(3)
})
})
@@ -1,75 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
parseHostedAndroidAccessibilityControls,
parseHostedAndroidDisplaySize,
tapHostedAndroidAccessibilityControl,
tapHostedAndroidPoint
} from '../../scripts/hosted-android-emulator-accessibility.mjs'
const emulator = { adb: '/sdk/platform-tools/adb' }
describe('hosted Android emulator accessibility controls', () => {
it('parses enabled labels, descriptions, entities, and bounds', () => {
const controls = parseHostedAndroidAccessibilityControls(`
<hierarchy>
<node text="Pair &amp; continue" content-desc="" enabled="true"
bounds="[10,20][110,220]" />
<node text="" content-desc="Open source control" enabled="true"
bounds="[20,30][220,330]" />
<node text="Disabled" content-desc="" enabled="false"
bounds="[0,0][10,10]" />
</hierarchy>
`)
expect(controls).toEqual([
{
label: 'Pair & continue',
enabled: true,
bounds: { left: 10, top: 20, right: 110, bottom: 220 }
},
{
label: 'Open source control',
enabled: true,
bounds: { left: 20, top: 30, right: 220, bottom: 330 }
},
{
label: 'Disabled',
enabled: false,
bounds: { left: 0, top: 0, right: 10, bottom: 10 }
}
])
})
it('prefers the override display size', () => {
expect(
parseHostedAndroidDisplaySize('Physical size: 1280x2856\nOverride size: 1000x2200')
).toEqual({ width: 1000, height: 2200 })
})
it('taps the center of an exact accessible label', async () => {
const runAdb = vi
.fn()
.mockResolvedValueOnce(
'<hierarchy><node text="Pair" enabled="true" bounds="[100,200][300,400]" /></hierarchy>'
)
.mockResolvedValueOnce('')
await expect(
tapHostedAndroidAccessibilityControl(emulator, 'Pair', 1_000, runAdb)
).resolves.toEqual({ x: 200, y: 300 })
expect(runAdb).toHaveBeenLastCalledWith(emulator.adb, ['shell', 'input', 'tap', '200', '300'])
})
it('maps a normalized WebView point to physical display pixels', async () => {
const runAdb = vi
.fn()
.mockResolvedValueOnce('Physical size: 1280x2856')
.mockResolvedValueOnce('')
await expect(tapHostedAndroidPoint(emulator, { x: 0.25, y: 0.5 }, runAdb)).resolves.toEqual({
x: 320,
y: 1428
})
expect(runAdb).toHaveBeenLastCalledWith(emulator.adb, ['shell', 'input', 'tap', '320', '1428'])
})
})
@@ -1,83 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
assertHostedAndroidBridgeLogClean,
findHostedAndroidBridgeLogFailures,
installAndResetHostedAndroidApp,
waitForHostedAndroidReactReady
} from '../../scripts/hosted-android-emulator-session.mjs'
describe('hosted Android emulator session', () => {
it('finds native bridge rejection, conversion, cast, and process failures', () => {
const failures = findHostedAndroidBridgeLogFailures(`
E/ReactNativeJS: Call to function 'ExpoMobileWebShell.postViewMessage' has been rejected.
E/ReactNativeJS: java.lang.IllegalArgumentException: mobile_web_shell_view_unavailable
E/ExpoModules: Cannot convert '{}' to a Kotlin type
E/AndroidRuntime: java.lang.ClassCastException: View cannot be cast to MobileWebShellView
E/AndroidRuntime: FATAL EXCEPTION: main
`)
expect(failures).toHaveLength(5)
})
it('ignores normal Android runtime and WebView output', () => {
expect(
findHostedAndroidBridgeLogFailures(`
D/AndroidRuntime: Calling main entry com.android.commands.uiautomator.Launcher
I/chromium: source: https://orca-mobile-web.invalid/
`)
).toEqual([])
})
it('ignores automation-process fatals while retaining app-process fatals', () => {
const logcat = `
E/AndroidRuntime(20196): FATAL EXCEPTION: UiAutomation
E/AndroidRuntime(20200): FATAL EXCEPTION: main
`
expect(findHostedAndroidBridgeLogFailures(logcat, '20200')).toEqual([
' E/AndroidRuntime(20200): FATAL EXCEPTION: main'
])
})
it('waits for the current Android app process to mount React main', async () => {
const responses = [
'7301',
'I/ReactNativeJS: Loading bundle',
'7301',
'I/ReactNativeJS: Running "main"'
]
const runAdb = async () => responses.shift() ?? ''
await expect(waitForHostedAndroidReactReady('adb', 2_000, runAdb)).resolves.toBe('7301')
})
it('allows an exact debug APK to replace a higher-version beta install', async () => {
const calls: { args: string[]; timeout?: number }[] = []
const runAdb = async (_adb: string, args: string[], timeout?: number) => {
calls.push({ args, timeout })
return ''
}
await installAndResetHostedAndroidApp('adb', '/tmp/app-debug.apk', runAdb)
expect(calls[0]).toEqual({
args: ['install', '-r', '-d', '-t', '/tmp/app-debug.apk'],
timeout: 120_000
})
expect(calls[1]?.args).toEqual(['shell', 'pm', 'clear', 'com.stably.orca.mobile'])
})
it('scopes the bridge audit to the current Android app process', async () => {
const calls: string[][] = []
const runAdb = async (_adb: string, args: string[]) => {
calls.push(args)
return calls.length === 1 ? '7301' : 'I/chromium: hosted route'
}
await expect(assertHostedAndroidBridgeLogClean('adb', runAdb)).resolves.toBeUndefined()
expect(calls).toEqual([
['shell', 'pidof', 'com.stably.orca.mobile'],
['logcat', '--pid', '7301', '-d', '-v', 'brief']
])
})
})
@@ -1,157 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
openHostedAndroidHardwareBackNestedRoute,
sendHostedAndroidHardwareBack,
waitForHostedAndroidWorkspaceRoot,
verifyHostedAndroidHardwareBackJourney
} from '../../scripts/hosted-android-hardware-back-journey.mjs'
describe('hosted Android hardware Back journey', () => {
it('uses the real Android Back key event', async () => {
const runAdb = vi.fn(async () => '')
await sendHostedAndroidHardwareBack('/sdk/adb', runAdb)
expect(runAdb).toHaveBeenCalledWith('/sdk/adb', ['shell', 'input', 'keyevent', 'KEYCODE_BACK'])
})
it('opens the nested route through the hosted UI navigation stack', async () => {
const sessionDocument = { href: 'orca-mobile-web://build/h/host/session/workspace' }
const actionsDocument = { href: sessionDocument.href }
const historyDocument = {
href: 'orca-mobile-web://build/h/host/agent-history/workspace'
}
const activate = vi.fn().mockResolvedValue(undefined)
const settle = vi.fn().mockResolvedValue(undefined)
const waitForDocument = vi
.fn()
.mockResolvedValueOnce(actionsDocument)
.mockResolvedValueOnce(historyDocument)
await expect(
openHostedAndroidHardwareBackNestedRoute(
{
discoveryUrl: 'http://127.0.0.1:9222',
sessionDocument,
timeoutMs: 30_000
},
{ activate, settle, waitForDocument }
)
).resolves.toBe(historyDocument)
expect(activate.mock.calls.map((call) => call[1])).toEqual([
{ kind: 'label', value: 'More session actions' },
{ kind: 'text', value: 'Agent History' }
])
expect(waitForDocument).toHaveBeenLastCalledWith(
expect.objectContaining({ expectedHrefIncludes: '/agent-history/' })
)
expect(settle).toHaveBeenCalledWith(500)
})
it('waits for the Session route to leave before accepting the workspace root', async () => {
const waitForDocument = vi
.fn()
.mockResolvedValueOnce({ href: '/session/workspace' })
.mockResolvedValueOnce({ href: '/session/workspace' })
.mockResolvedValueOnce({ href: '/' })
const settle = vi.fn().mockResolvedValue(undefined)
await expect(
waitForHostedAndroidWorkspaceRoot({
discoveryUrl: 'http://127.0.0.1:9222',
settle,
timeoutMs: 30_000,
waitForDocument,
workspaceMarker: 'MOBILE-REARCH'
})
).resolves.toEqual({ href: '/' })
expect(settle).toHaveBeenCalledTimes(2)
})
it('returns through Session and workspace root before dismissing to native home', async () => {
const events: string[] = []
const pressBack = vi.fn(async () => events.push('back'))
const openAgentHistory = vi.fn(async () => {
events.push('agent-history')
return { href: 'orca-mobile-web://build/h/host/agent-history/workspace' }
})
const waitForDocument = vi.fn(async ({ expectedHrefIncludes }) => {
const route = expectedHrefIncludes ? 'session' : 'workspace'
events.push(route)
return { href: `orca-mobile-web://build/h/host/${route}` }
})
const waitForNativeControl = vi.fn(async () => {
events.push('native-shell')
return { label: 'Open settings' }
})
const evidence = await verifyHostedAndroidHardwareBackJourney(
{
adb: '/sdk/adb',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { adb: '/sdk/adb' },
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
timeoutMs: 30_000,
workspaceMarker: 'MOBILE-REARCH'
},
{ openAgentHistory, pressBack, waitForDocument, waitForNativeControl }
)
expect(events).toEqual([
'agent-history',
'back',
'session',
'back',
'workspace',
'back',
'native-shell'
])
expect(evidence).toEqual({
nestedRoute: 'orca-mobile-web://build/h/host/agent-history/workspace',
returnedSessionRoute: 'orca-mobile-web://build/h/host/session',
workspaceRootRoute: 'orca-mobile-web://build/h/host/workspace',
nativeShellControl: 'Open settings',
hardwareBackPresses: 3
})
expect(waitForDocument).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ expectedHrefIncludes: '/session/', expectedText: '1 tab' })
)
expect(waitForNativeControl).toHaveBeenCalledWith(
{ adb: '/sdk/adb' },
[
'Open settings',
'Open sessions in Chat UI',
'Open sessions in the terminal',
'Show paired hosts'
],
30_000
)
})
it('rejects a nested route that remains visible at the workspace step', async () => {
await expect(
verifyHostedAndroidHardwareBackJourney(
{
adb: '/sdk/adb',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { adb: '/sdk/adb' },
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
timeoutMs: 30_000,
workspaceMarker: 'MOBILE-REARCH'
},
{
openAgentHistory: async () => ({ href: '/agent-history/' }),
pressBack: async () => {},
waitForWorkspaceRoot: async () => ({ href: '/agent-history/' }),
waitForDocument: async ({ expectedHrefIncludes }) => ({
href: expectedHrefIncludes ? '/session/' : '/agent-history/'
}),
waitForNativeControl: vi.fn()
}
)
).rejects.toThrow('did not reach the workspace root')
})
})
@@ -1,71 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it, vi } from 'vitest'
import {
parseAndroidMobileWebActivation,
readAndroidRollbackActivation,
readSingleAndroidActivation
} from '../../scripts/hosted-android-mobile-web-cache.mjs'
const active = 'a'.repeat(64)
const previous = 'b'.repeat(64)
const harnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-corrupt-cache.mjs', import.meta.url),
'utf8'
)
describe('hosted Android mobile web cache evidence', () => {
it('normalizes an omitted previous generation and rejects invalid identities', () => {
expect(parseAndroidMobileWebActivation(JSON.stringify({ active }))).toEqual({
active,
previous: null
})
expect(() => parseAndroidMobileWebActivation(JSON.stringify({ active: '../active' }))).toThrow(
'Android cache returned an invalid activation record'
)
})
it('selects the only rollback candidate', async () => {
const runAdb = activationExecutor([
['cache/one/activation.json', { active }],
['cache/two/activation.json', { active, previous }]
])
await expect(readAndroidRollbackActivation('adb', runAdb)).resolves.toEqual({
path: 'cache/two/activation.json',
active,
previous
})
})
it('requires exactly one activation record for corruption drills', async () => {
const runAdb = activationExecutor([['cache/one/activation.json', { active }]])
await expect(readSingleAndroidActivation('adb', runAdb)).resolves.toEqual({
path: 'cache/one/activation.json',
active,
previous: null
})
})
it('keeps the offline corruption drill bounded and reversible', () => {
expect(harnessSource).toContain("process.kill(options.desktopPid, 'SIGSTOP')")
expect(harnessSource).toContain("process.kill(options.desktopPid, 'SIGCONT')")
expect(harnessSource).toContain('repairFixture(')
expect(harnessSource).toContain('requireGenerationRemoved(')
expect(harnessSource).toContain('expectedText: options.expectedText')
})
})
function activationExecutor(records: [string, { active: string; previous?: string }][]) {
return vi.fn(async (_command: string, args: string[]) => {
if (args.includes('find')) {
return records.map(([path]) => path).join('\n')
}
const path = args.at(-1)
const record = records.find(([candidate]) => candidate === path)?.[1]
if (!record) {
throw new Error('unexpected Android activation path')
}
return JSON.stringify(record)
})
}
@@ -1,115 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import {
hostedAndroidExitInfoEvidence,
hostedAndroidPrivacyLogEvidence
} from '../../scripts/hosted-android-privacy-audit.mjs'
const priorExit = exitInfo(0, 100, 10, 'USER REQUESTED')
const routeHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-source-control-review-e2e.mjs', import.meta.url),
'utf8'
)
describe('hosted Android privacy audit', () => {
it('keeps DOM, log, and process-exit audits in the live route gate', () => {
expect(routeHarnessSource).toContain('verifyHostedWebViewPrivacyIsolation')
expect(routeHarnessSource).toContain('readHostedAndroidExitInfo')
expect(routeHarnessSource).toContain('verifyHostedAndroidPrivacyAudit')
})
it('accepts fixed-category logs', () => {
expect(hostedAndroidPrivacyLogEvidence('I/Orca: state=connected retries=1')).toMatchObject({
logBytes: 33,
counts: {
privilegedField: 0,
tokenStorage: 0,
nativeAuthority: 0,
privateOriginUrl: 0,
webSocketUrl: 0,
fixtureMarker: 0
}
})
})
it.each([
'deviceToken',
'orca.host-token.host-a',
'openHostLogicalClient',
'orca-mobile-web://abcdefghijklmnopqrstuvwxyz/',
'wss://paired-host.invalid/socket',
'ORCA_E2E_MOBILE_WEB_HOST_PUBLIC_KEY'
])('rejects privileged log marker %s', (marker) => {
expect(() => hostedAndroidPrivacyLogEvidence(marker)).toThrow(
'Hosted Android privacy log audit failed'
)
})
it('allows only exact debug-server and deliberate probe WebSockets', () => {
const evidence = hostedAndroidPrivacyLogEvidence(
[
'ws://10.0.2.2:8081/message?device=Pixel%209%20-%2016%20-%20API%2036&app=com.stably.orca.mobile&clientid=BridgelessDevSupportManager',
'ws://10.0.2.2:8081/message?device=Pixel%209%20-%2016%20-%20API%2036&app=com.stably.orca.mobile&clientid=DevLauncherBridgelessDevSupportManager',
'ws://10.0.2.2:57999/message?device=Pixel%209&app=com.stably.orca.mobile&clientid=BridgelessDevSupportManager',
'ws://127.0.0.1:53250/socket-probe'
].join('\n'),
{ devServerPort: 57999, probePort: 53250 }
)
expect(evidence.expectedDebugWebSocketUrls).toBe(4)
expect(evidence.counts.webSocketUrl).toBe(0)
})
it.each([
'ws://10.0.2.2:8081/message?device=Pixel&app=com.stably.orca.mobile&clientid=BridgelessDevSupportManager&token=secret',
'ws://10.0.2.2:8081/message?device=Pixel&app=other&clientid=BridgelessDevSupportManager',
'ws://10.0.2.2:8082/message?device=Pixel&app=com.stably.orca.mobile&clientid=BridgelessDevSupportManager',
'wss://127.0.0.1:53250/socket-probe',
'ws://127.0.0.1:53250/socket-probe?token=secret'
])('rejects mutated debug WebSocket %s', (url) => {
expect(() =>
hostedAndroidPrivacyLogEvidence(url, { devServerPort: 8081, probePort: 53250 })
).toThrow('Hosted Android privacy log audit failed')
})
it('ignores renumbered baseline exits and accepts a new force-stop', () => {
const evidence = hostedAndroidExitInfoEvidence(
priorExit,
`${exitInfo(0, 200, 10, 'USER REQUESTED')}\n${exitInfo(1, 100, 10, 'USER REQUESTED')}`
)
expect(evidence).toMatchObject({
currentRecords: 2,
newRecords: 1,
failureRecords: 0
})
})
it.each([
[4, 'CRASH'],
[5, 'NATIVE CRASH'],
[6, 'ANR'],
[7, 'INITIALIZATION FAILURE'],
[9, 'EXCESSIVE RESOURCE USAGE']
])('rejects new process failure reason %s', (reason, label) => {
expect(() =>
hostedAndroidExitInfoEvidence(priorExit, `${exitInfo(0, 200, reason, label)}\n${priorExit}`)
).toThrow('recorded a process failure')
})
it('rejects privileged markers in new exit records', () => {
expect(() =>
hostedAndroidExitInfoEvidence(
priorExit,
`${exitInfo(0, 200, 10, 'USER REQUESTED', 'publicKeyB64')}\n${priorExit}`
)
).toThrow('Hosted Android exit-info privacy log audit failed')
})
})
function exitInfo(index, timestamp, reason, label, description = 'test') {
return `ApplicationExitInfo #${index}:
timestamp=${timestamp} pid=${timestamp}
process=com.stably.orca.mobile reason=${reason} (${label}) status=0
importance=100 description=${description} trace=null`
}
@@ -1,90 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateControl: vi.fn(),
readTextPoint: vi.fn(),
tapPoint: vi.fn()
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activateControl,
readHostedWebViewTextPoint: mocks.readTextPoint
}))
vi.mock('../../scripts/hosted-android-emulator-accessibility.mjs', () => ({
tapHostedAndroidPoint: mocks.tapPoint
}))
import {
activateHostedAndroidWorkspaceControl,
prepareHostedAndroidWorkspaceInput
} from '../../scripts/hosted-android-workspace-activation.mjs'
describe('hosted Android workspace activation', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.clearAllMocks()
mocks.activateControl.mockResolvedValue(undefined)
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.3 })
mocks.tapPoint.mockResolvedValue(undefined)
})
afterEach(() => {
vi.useRealTimers()
})
it('primes WebView focus before workspace input', async () => {
const emulator = { adb: '/sdk/adb' }
const result = prepareHostedAndroidWorkspaceInput(emulator)
await vi.advanceTimersByTimeAsync(10_000)
await result
expect(mocks.tapPoint).toHaveBeenCalledWith(emulator, { x: 0.5, y: 0.75 })
})
it('uses the grouped text control instead of a non-hittable ARIA node', async () => {
const document = { href: 'https://orca-mobile-web.invalid/' }
await expect(
activateHostedAndroidWorkspaceControl({}, document, {
kind: 'label',
value: 'Open mobile-rearch',
reveal: true
})
).rejects.toThrow('Hosted WebView control was not found')
await activateHostedAndroidWorkspaceControl({}, document, {
kind: 'text',
value: 'mobile-rearch',
reveal: true
})
expect(mocks.activateControl).toHaveBeenCalledOnce()
})
it('waits for visible row coordinates to stabilize before tapping', async () => {
const emulator = { adb: '/sdk/adb' }
const result = activateHostedAndroidWorkspaceControl(
emulator,
{},
{
kind: 'text',
value: 'mobile-rearch',
occurrence: 1,
reveal: true
}
)
await vi.advanceTimersByTimeAsync(3_000)
await result
expect(mocks.readTextPoint).toHaveBeenCalledWith(
{},
'mobile-rearch',
undefined,
expect.objectContaining({ occurrence: 1 })
)
expect(mocks.tapPoint).toHaveBeenCalledWith(emulator, { x: 0.2, y: 0.3 })
})
})
@@ -1,101 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
captureScreenshot: vi.fn(),
compareScreenshots: vi.fn(),
dismissDeveloperMenu: vi.fn(),
readTextPoint: vi.fn(),
tapPoint: vi.fn(),
waitForControl: vi.fn(),
waitForControlByPrefix: vi.fn(),
waitForDocument: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: mocks.captureScreenshot
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: mocks.dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
tapHostedIosPoint: mocks.tapPoint,
waitForHostedIosAccessibilityControl: mocks.waitForControl,
waitForHostedIosAccessibilityControlByLabelPrefix: mocks.waitForControlByPrefix
}))
vi.mock('../../scripts/hosted-ios-screenshot-parity.mjs', () => ({
assertHostedIosScreenshotParity: mocks.compareScreenshots
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
readHostedWebViewTextPoint: mocks.readTextPoint,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
import {
captureHostedAccountsParity,
captureNativeAccountsBaseline
} from '../../scripts/hosted-ios-accounts-parity.mjs'
describe('hosted iOS Accounts parity', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.captureScreenshot.mockImplementation((_command, _args, callback) =>
callback(null, '', '')
)
mocks.compareScreenshots.mockResolvedValue({ changedPixelRatio: 0.01 })
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.tapPoint.mockResolvedValue(undefined)
mocks.waitForControl.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControlByPrefix.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes }) => ({
href: `orca-mobile-web://build${expectedHrefIncludes ?? '/h/host'}`,
targetId: expectedHrefIncludes ?? 'workspace'
}))
})
it('captures native Accounts and returns to worktrees', async () => {
const baseline = await captureNativeAccountsBaseline({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.tapPoint).toHaveBeenNthCalledWith(
1,
{ deviceUdid: 'simulator' },
{ x: 0.8, y: 0.1 }
)
expect(mocks.waitForControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Add or re-authenticate accounts from desktop Settings → Accounts.',
30_000
)
expect(baseline).toEqual({
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-accounts-portrait.png'
})
})
it('compares hosted Accounts and returns to the workspace route', async () => {
const result = await captureHostedAccountsParity({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
nativeBaseline: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-accounts-portrait.png'
},
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' }
})
expect(mocks.readTextPoint).toHaveBeenCalledWith(expect.anything(), 'Filter')
expect(mocks.tapPoint).toHaveBeenCalledWith({ deviceUdid: 'simulator' }, { x: 0.8, y: 0.1 })
expect(mocks.compareScreenshots).toHaveBeenCalledOnce()
expect(result.evidence.screenshotParity).toEqual({ changedPixelRatio: 0.01 })
expect(result.workspaceDocument.href).toContain('/h/host')
})
})
@@ -1,34 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { verifyHostedIosAdversarialTerminalLinks } from '../../scripts/hosted-ios-adversarial-terminal-links.mjs'
describe('hosted iOS adversarial terminal staging', () => {
it('reuses the registration-stage corpus without a second terminal send', async () => {
const stageLinks = vi.fn()
const verifyLinks = vi.fn(async (args, operations) => ({
evidence: {
terminalHandle: await operations.writeLinks({
terminalHandle: args.terminalHandle
})
},
sessionDocument: { href: 'orca-mobile-web://build/session' }
}))
await expect(
verifyHostedIosAdversarialTerminalLinks(
{
stagedTerminalHandle: 'pre-staged-terminal'
},
{ stageLinks, verifyLinks }
)
).resolves.toMatchObject({
evidence: { terminalHandle: 'pre-staged-terminal' },
yOffset: 0
})
expect(verifyLinks).toHaveBeenCalledWith(
expect.objectContaining({ terminalHandle: 'pre-staged-terminal' }),
expect.any(Object)
)
expect(stageLinks).not.toHaveBeenCalled()
})
})
@@ -1,66 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
dismissDeveloperMenu,
rotateEmulator,
tapControl,
tapControlByPrefix,
typeText,
waitByPrefix
} = vi.hoisted(() => ({
dismissDeveloperMenu: vi.fn(),
rotateEmulator: vi.fn(),
tapControl: vi.fn(),
tapControlByPrefix: vi.fn(),
typeText: vi.fn(),
waitByPrefix: vi.fn()
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
rotateHostedIosEmulator: rotateEmulator,
tapHostedIosAccessibilityControl: tapControl,
tapHostedIosAccessibilityControlByLabelPrefix: tapControlByPrefix,
typeHostedIosText: typeText,
waitForHostedIosAccessibilityControl: vi.fn(),
waitForHostedIosAccessibilityControlByLabelPrefix: waitByPrefix
}))
import { openNativeAgentHistoryBaseline } from '../../scripts/hosted-ios-agent-history-parity.mjs'
describe('native Agent History baseline', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
})
it('dismisses a reopened Expo developer overlay before navigating', async () => {
const emulator = { deviceUdid: 'simulator' }
const opened = openNativeAgentHistoryBaseline({
emulator,
expectedWorkspace: 'mobile-rearch',
timeoutMs: 30_000
})
await vi.runAllTimersAsync()
await opened
expect(dismissDeveloperMenu).toHaveBeenCalledWith(emulator)
expect(rotateEmulator).toHaveBeenCalledWith(emulator, 'portrait')
expect(tapControlByPrefix).toHaveBeenNthCalledWith(1, emulator, 'mobile-rearch', 30_000)
expect(tapControl).toHaveBeenCalledWith(emulator, 'More session actions', 30_000)
expect(tapControlByPrefix).toHaveBeenNthCalledWith(2, emulator, 'Agent History', 30_000)
expect(tapControl).toHaveBeenCalledWith(emulator, 'Search sessions, repo:, path:', 30_000)
expect(typeText).toHaveBeenCalledWith(emulator, 'hybrid agent history fixture')
expect(waitByPrefix).toHaveBeenCalledWith(emulator, 'Hybrid Agent History Fixture', 30_000)
expect(tapControl).toHaveBeenCalledWith(emulator, 'Agent Session History', 30_000)
expect(rotateEmulator.mock.invocationCallOrder[0]).toBeLessThan(
dismissDeveloperMenu.mock.invocationCallOrder[0]
)
expect(dismissDeveloperMenu.mock.invocationCallOrder[0]).toBeLessThan(
tapControlByPrefix.mock.invocationCallOrder[0]
)
})
})
@@ -1,115 +0,0 @@
import { createHash } from 'node:crypto'
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { waitForHostedIosBuildActivation } from '../../scripts/hosted-ios-build-activation.mjs'
const temporaryDirectories: string[] = []
afterEach(async () => {
await Promise.all(
temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true }))
)
})
describe('hosted iOS build activation wait', () => {
it('waits against the installed app data container', async () => {
const appDataPath = await mkdtemp(path.join(os.tmpdir(), 'orca-ios-activation-'))
const runtimeDirectory = await mkdtemp(path.join(os.tmpdir(), 'orca-ios-runtime-'))
temporaryDirectories.push(appDataPath)
temporaryDirectories.push(runtimeDirectory)
const publicKeyB64 = 'paired-public-key'
const hostIdentity = createHash('sha256').update(publicKeyB64).digest('hex')
const buildId = 'a'.repeat(64)
const hostRoot = path.join(
appDataPath,
'Library',
'Application Support',
'OrcaMobileWeb',
hostIdentity
)
await mkdir(hostRoot, { recursive: true })
await writeFile(
path.join(hostRoot, 'activation.json'),
JSON.stringify({ active: buildId, previous: null })
)
const keypairRoot = path.join(runtimeDirectory, 'paired-host', 'userData')
await mkdir(keypairRoot, { recursive: true })
await writeFile(
path.join(keypairRoot, 'orca-e2ee-keypair.json'),
JSON.stringify({ publicKeyB64 })
)
const runCommand = vi.fn().mockResolvedValue({ stdout: `${appDataPath}\n` })
await expect(
waitForHostedIosBuildActivation(
'simulator',
{ expectedBuild: buildId, timeoutMs: 1_000 },
runtimeDirectory,
runCommand
)
).resolves.toBeUndefined()
expect(runCommand).toHaveBeenCalledWith('xcrun', [
'simctl',
'get_app_container',
'simulator',
'com.stably.orca.mobile',
'data'
])
})
it('does not inspect the simulator without an expected build', async () => {
const runCommand = vi.fn()
await waitForHostedIosBuildActivation(
'simulator',
{ expectedBuild: undefined, timeoutMs: 1_000 },
'/runtime',
runCommand
)
expect(runCommand).not.toHaveBeenCalled()
})
it('ignores the same active build under an unrelated host identity', async () => {
const appDataPath = await mkdtemp(path.join(os.tmpdir(), 'orca-ios-activation-'))
const runtimeDirectory = await mkdtemp(path.join(os.tmpdir(), 'orca-ios-runtime-'))
temporaryDirectories.push(appDataPath)
temporaryDirectories.push(runtimeDirectory)
const publicKeyB64 = 'expected-paired-public-key'
const expectedIdentity = createHash('sha256').update(publicKeyB64).digest('hex')
const unrelatedIdentity = 'f'.repeat(64)
const buildId = 'b'.repeat(64)
const cacheRoot = path.join(appDataPath, 'Library', 'Application Support', 'OrcaMobileWeb')
const keypairRoot = path.join(runtimeDirectory, 'paired-host', 'userData')
await mkdir(path.join(cacheRoot, unrelatedIdentity), { recursive: true })
await mkdir(keypairRoot, { recursive: true })
await writeFile(
path.join(cacheRoot, unrelatedIdentity, 'activation.json'),
JSON.stringify({ active: buildId })
)
await writeFile(
path.join(keypairRoot, 'orca-e2ee-keypair.json'),
JSON.stringify({ publicKeyB64 })
)
const expectedHostRoot = path.join(cacheRoot, expectedIdentity)
const activateExpectedHost = setTimeout(async () => {
await mkdir(expectedHostRoot, { recursive: true })
await writeFile(
path.join(expectedHostRoot, 'activation.json'),
JSON.stringify({ active: buildId })
)
}, 20)
const runCommand = vi.fn().mockResolvedValue({ stdout: appDataPath })
await expect(
waitForHostedIosBuildActivation(
'simulator',
{ expectedBuild: buildId, timeoutMs: 1_000 },
runtimeDirectory,
runCommand
)
).resolves.toBeUndefined()
clearTimeout(activateExpectedHost)
})
})
@@ -1,213 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateControl: vi.fn(),
activateWorkspace: vi.fn(),
captureScreenshot: vi.fn(),
compareScreenshots: vi.fn(),
dismissDeveloperMenu: vi.fn(),
readControlPoint: vi.fn(),
readTextPoint: vi.fn(),
tapControl: vi.fn(),
tapControlByPrefix: vi.fn(),
tapPoint: vi.fn(),
waitForControl: vi.fn(),
waitForControlByPrefix: vi.fn(),
waitForControlMatch: vi.fn(),
waitForControlMatching: vi.fn(),
waitForDocument: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: mocks.captureScreenshot
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: mocks.dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
tapHostedIosAccessibilityControl: mocks.tapControl,
tapHostedIosAccessibilityControlByLabelPrefix: mocks.tapControlByPrefix,
tapHostedIosPoint: mocks.tapPoint,
waitForHostedIosAccessibilityControl: mocks.waitForControl,
waitForHostedIosAccessibilityControlByLabelPrefix: mocks.waitForControlByPrefix,
waitForHostedIosAccessibilityControlMatch: mocks.waitForControlMatch,
waitForHostedIosAccessibilityControlMatching: mocks.waitForControlMatching
}))
vi.mock('../../scripts/hosted-ios-screenshot-parity.mjs', () => ({
assertHostedIosScreenshotParity: mocks.compareScreenshots
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activateControl,
readHostedWebViewTextPoint: mocks.readTextPoint,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
vi.mock('../../scripts/hosted-webview-control-point.mjs', () => ({
readHostedWebViewControlPoint: mocks.readControlPoint
}))
vi.mock('../../scripts/hosted-webview-workspace-activation.mjs', () => ({
activateHostedWorkspaceRow: mocks.activateWorkspace
}))
import {
captureHostedCoreRouteParity,
captureNativeCoreRouteBaselines
} from '../../scripts/hosted-ios-core-route-parity.mjs'
describe('hosted iOS core-route parity', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.captureScreenshot.mockImplementation((_command, _args, callback) =>
callback(null, '', '')
)
mocks.compareScreenshots.mockResolvedValue({ changedPixelRatio: 0.01 })
mocks.readControlPoint.mockResolvedValue({ x: 0.8, y: 0.1 })
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.tapControl.mockResolvedValue(undefined)
mocks.tapControlByPrefix.mockResolvedValue(undefined)
mocks.tapPoint.mockResolvedValue(undefined)
mocks.waitForControl.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControlByPrefix.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControlMatch.mockResolvedValue({
label: 'Connect your Linear account',
x: 0.5,
y: 0.5
})
mocks.waitForControlMatching.mockResolvedValue({
label: 'Stable task title, 3m, Issue · orca #1, Open',
value: '',
x: 0.5,
y: 0.5
})
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes }) => ({
href: `orca-mobile-web://build${expectedHrefIncludes ?? '/h/host'}`
}))
})
it('captures native Tasks and Session from the same presentation source', async () => {
const baselines = await captureNativeCoreRouteBaselines({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(baselines.tasks.stableText).toBe('Connect your Linear account')
expect(mocks.tapPoint).toHaveBeenNthCalledWith(
1,
{ deviceUdid: 'simulator' },
{ x: 0.87, y: 0.1 }
)
expect(mocks.tapPoint).toHaveBeenNthCalledWith(
2,
{ deviceUdid: 'simulator' },
expect.objectContaining({ y: 0.1 })
)
expect(mocks.tapPoint.mock.calls[1][1].x).toBeCloseTo(0.08)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Back to worktrees',
30_000
)
expect(baselines.session.screenshot).toBe('/tmp/parity/native-session-portrait.png')
})
it('captures a populated GitHub Tasks list after pagination appears', async () => {
mocks.waitForControlMatch.mockRejectedValueOnce(new Error('no empty state'))
const baselines = await captureNativeCoreRouteBaselines({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.waitForControlMatch).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
expect.any(Array),
5_000
)
expect(mocks.waitForControlMatching).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
expect.any(Function),
25_000
)
expect(baselines.tasks.stableText).toBe('Stable task title')
})
it('compares hosted Tasks and Session and returns to the workspace route', async () => {
const nativeBaselines = {
tasks: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-tasks-portrait.png',
stableText: 'Connect your Linear account'
},
session: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-session-portrait.png'
}
}
const result = await captureHostedCoreRouteParity({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
nativeBaselines,
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' }
})
expect(result.evidence.tasks.screenshotParity).toEqual({
changedPixelRatio: 0.01
})
expect(result.evidence.session.screenshotParity).toEqual({
changedPixelRatio: 0.01
})
expect(result.workspaceDocument.href).toContain('/h/host')
expect(mocks.activateWorkspace).toHaveBeenCalledWith(
expect.anything(),
'mobile-rearch',
mocks.activateControl,
30_000,
expect.any(Function)
)
expect(mocks.readTextPoint).toHaveBeenCalledWith(expect.anything(), 'Filter')
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Back to worktrees',
5_000
)
expect(mocks.compareScreenshots).toHaveBeenCalledTimes(2)
})
it('uses a measured point when the hosted session back control is missing from AX', async () => {
mocks.tapControl.mockRejectedValueOnce(new Error('missing accessibility descendant'))
await captureHostedCoreRouteParity({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
nativeBaselines: {
tasks: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-tasks-portrait.png',
stableText: 'Connect your Linear account'
},
session: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-session-portrait.png'
}
},
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' }
})
expect(mocks.readControlPoint).toHaveBeenCalledWith(expect.anything(), 'Back to worktrees')
expect(mocks.tapPoint).toHaveBeenCalledWith({ deviceUdid: 'simulator' }, { x: 0.8, y: 0.1 })
})
})
@@ -1,277 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
HOSTED_IOS_DOCUMENT_FIXTURE_NAME,
openHostedIosDocumentPicker,
parseLocalFileProviderGroupPath,
seedHostedIosDocumentFixture,
selectHostedIosDocumentFixture,
uploadedPathsFromTerminalSnapshot,
verifyHostedIosDocumentUpload
} from '../../scripts/hosted-ios-document-upload.mjs'
const fixture = {
destinationPath: '/simulator/File Provider Storage/orca-document-upload-fixture.png',
fixtureName: HOSTED_IOS_DOCUMENT_FIXTURE_NAME,
sha256: 'dec4a91731905b9e8ed450a6c46931258528fc034fcfc64d95b0b23264f8e9d4',
size: 123
}
const uploadedPath =
'/var/folders/x/y/T/orca-paste-1780000000000-00000000-0000-4000-8000-000000000000.png'
const args = {
discoveryUrl: 'http://127.0.0.1:9222',
documentFixture: fixture,
emulator: { deviceUdid: 'simulator' },
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
}
const fixtureBytes = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7Q', 'base64')
describe('hosted iOS document upload', () => {
it('resolves and seeds the simulator Local File Provider container', async () => {
const runCommand = vi.fn().mockResolvedValue({
stdout:
'group.com.apple.DocumentManager\t/simulator/document-manager\n' +
'group.com.apple.FileProvider.LocalStorage\t/simulator/local-files\n'
})
const copy = vi.fn().mockResolvedValue(undefined)
const createDirectory = vi.fn().mockResolvedValue(undefined)
const readFixture = vi.fn().mockResolvedValue(Buffer.alloc(123))
const touch = vi.fn().mockResolvedValue(undefined)
await seedHostedIosDocumentFixture(
{
deviceUdid: 'simulator',
fixturePath: '/repo/mobile/assets/favicon.png'
},
{ copy, createDirectory, readFixture, runCommand, touch }
)
expect(runCommand).toHaveBeenCalledWith(
'xcrun',
['simctl', 'get_app_container', 'simulator', 'com.apple.DocumentsApp', 'groups'],
{ encoding: 'utf8' }
)
expect(createDirectory).toHaveBeenCalledWith('/simulator/local-files/File Provider Storage', {
recursive: true
})
expect(copy).toHaveBeenCalledWith(
'/repo/mobile/assets/favicon.png',
'/simulator/local-files/File Provider Storage/orca-document-upload-fixture.png'
)
expect(touch).toHaveBeenCalled()
})
it('rejects a missing Local File Provider group', () => {
expect(() =>
parseLocalFileProviderGroupPath(
'group.com.apple.DocumentManager\t/simulator/document-manager'
)
).toThrow('Local File Provider container was not found')
})
it('falls back through Browse and On My iPhone to select the seeded file', async () => {
const tapByPrefix = vi.fn().mockResolvedValueOnce({ x: 0.5, y: 0.4 })
const tapFixture = vi
.fn()
.mockRejectedValueOnce(new Error('not in Recents'))
.mockRejectedValueOnce(new Error('not in Browse root'))
.mockResolvedValueOnce({ x: 0.5, y: 0.3 })
const tapControl = vi.fn().mockResolvedValue({ x: 0.2, y: 0.9 })
const waitForPickerDismissal = vi.fn().mockResolvedValue(undefined)
await expect(
selectHostedIosDocumentFixture(
args.emulator,
HOSTED_IOS_DOCUMENT_FIXTURE_NAME,
args.timeoutMs,
{ tapByPrefix, tapControl, tapFixture, waitForPickerDismissal }
)
).resolves.toEqual({ x: 0.5, y: 0.3 })
expect(tapControl).toHaveBeenCalledWith(args.emulator, 'Browse', 5_000)
expect(tapByPrefix).toHaveBeenCalledWith(args.emulator, 'On My iPhone', 5_000)
expect(tapFixture).toHaveBeenLastCalledWith(
args.emulator,
'orca-document-upload-fixture',
{ x: 0.5, y: 0.25 },
args.timeoutMs
)
expect(waitForPickerDismissal).toHaveBeenCalledWith(args.emulator, 'Browse', 3_000)
})
it('splits abutting uploaded paths instead of fusing them', () => {
const first =
'/var/folders/x/y/T/orca-paste-1780000000000-00000000-0000-4000-8000-000000000000.png'
const second =
'/var/folders/x/y/T/orca-paste-1780000000001-00000000-0000-4000-8000-000000000001.png'
expect(uploadedPathsFromTerminalSnapshot([first + second])).toEqual([first, second])
})
it('reconstructs wrapped uploaded paths from terminal rows', () => {
expect(
uploadedPathsFromTerminalSnapshot([
'/orca/ORCA_HOSTED_CLIPBOARD_TEXT_PASTE/var/folders/x/y/T/orca-paste-1780000',
'000000-00000000-0000-4000-8000-000000000000.png'
])
).toEqual([
'/var/folders/x/y/T/orca-paste-1780000000000-00000000-0000-4000-8000-000000000000.png'
])
})
it('uses the physical long press when it opens the native picker', async () => {
const longPressPoint = vi.fn().mockResolvedValue(undefined)
const waitForPicker = vi.fn().mockResolvedValue({ x: 0.2, y: 0.9 })
const dispatchWebLongPress = vi.fn()
await expect(
openHostedIosDocumentPicker(
{
document: args.sessionDocument,
emulator: args.emulator,
point: { x: 0.75, y: 0.9 }
},
{ dispatchWebLongPress, longPressPoint, waitForPicker }
)
).resolves.toBe('native-long-press')
expect(dispatchWebLongPress).not.toHaveBeenCalled()
})
it('retains native gesture authority while completing the RNW long press', async () => {
const longPressPoint = vi.fn().mockResolvedValue(undefined)
const waitForPicker = vi
.fn()
.mockRejectedValueOnce(new Error('picker not visible'))
.mockResolvedValueOnce({ x: 0.2, y: 0.9 })
const dispatchWebLongPress = vi.fn().mockResolvedValue(undefined)
await expect(
openHostedIosDocumentPicker(
{
document: args.sessionDocument,
emulator: args.emulator,
point: { x: 0.75, y: 0.9 }
},
{ dispatchWebLongPress, longPressPoint, waitForPicker }
)
).resolves.toBe('native-touch-plus-web-responder')
expect(dispatchWebLongPress).toHaveBeenCalledWith(args.sessionDocument, 'Attach a photo')
expect(waitForPicker).toHaveBeenLastCalledWith(args.emulator, 'Browse', 5_000)
})
it('repeats the combined gesture when WebKit drops the first responder hold', async () => {
const longPressPoint = vi.fn().mockResolvedValue(undefined)
const waitForPicker = vi
.fn()
.mockRejectedValueOnce(new Error('physical hold did not open picker'))
.mockRejectedValueOnce(new Error('first responder hold did not open picker'))
.mockRejectedValueOnce(new Error('second physical hold did not open picker'))
.mockResolvedValueOnce({ x: 0.2, y: 0.9 })
const dispatchWebLongPress = vi.fn().mockResolvedValue(undefined)
await expect(
openHostedIosDocumentPicker(
{
document: args.sessionDocument,
emulator: args.emulator,
point: { x: 0.75, y: 0.9 }
},
{ dispatchWebLongPress, longPressPoint, waitForPicker }
)
).resolves.toBe('native-touch-plus-web-responder')
expect(longPressPoint).toHaveBeenCalledTimes(2)
expect(dispatchWebLongPress).toHaveBeenCalledTimes(2)
})
it('selects through unchanged Attach and verifies terminal-only byte identity', async () => {
const before = ['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE']
const after = [...before, uploadedPath]
const operations = {
fileStat: vi.fn().mockResolvedValue({ size: fixtureBytes.byteLength }),
openPicker: vi.fn().mockResolvedValue('native-touch-plus-web-responder'),
readAccessibilityPoint: vi.fn().mockResolvedValue({ x: 0.74, y: 0.89 }),
readControlPoint: vi.fn().mockResolvedValue({ x: 0.75, y: 0.9 }),
readState: vi.fn().mockResolvedValue({ bodyText: 'Mobile Emulator' }),
readTerminal: vi.fn().mockResolvedValueOnce(before).mockResolvedValueOnce(after),
readUploadedFile: vi.fn().mockResolvedValue(fixtureBytes),
selectFixture: vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 }),
waitForDocument: vi.fn().mockResolvedValue(args.sessionDocument)
}
const documentFixture = {
...fixture,
sha256: '1b0a38dc09fb7fe56599abeab066965c1b7791815e7dd08483938630b0d15226',
size: fixtureBytes.byteLength
}
await expect(
verifyHostedIosDocumentUpload({ ...args, documentFixture }, operations)
).resolves.toEqual({
evidence: {
attachAccessibilityPoint: { x: 0.74, y: 0.89 },
attachControlPoint: { x: 0.75, y: 0.9 },
fixtureName: HOSTED_IOS_DOCUMENT_FIXTURE_NAME,
privilegedPageMarkers: 'absent',
pickerActivation: 'native-touch-plus-web-responder',
route: args.sessionDocument.href,
selectedFilePoint: { x: 0.5, y: 0.5 },
sha256: documentFixture.sha256,
size: fixtureBytes.byteLength,
terminalPathInjected: true
},
sessionDocument: args.sessionDocument
})
expect(operations.readControlPoint).toHaveBeenCalledWith(args.sessionDocument, 'Attach a photo')
expect(operations.openPicker).toHaveBeenCalledWith({
document: args.sessionDocument,
emulator: args.emulator,
point: { x: 0.75, y: 0.9 }
})
expect(operations.readUploadedFile).toHaveBeenCalledWith(uploadedPath)
})
it('rejects selected bytes exposed in hosted page state', async () => {
const documentFixture = {
...fixture,
sha256: '1b0a38dc09fb7fe56599abeab066965c1b7791815e7dd08483938630b0d15226',
size: fixtureBytes.byteLength
}
const operations = {
fileStat: vi.fn().mockResolvedValue({ size: fixtureBytes.byteLength }),
openPicker: vi.fn().mockResolvedValue('native-touch-plus-web-responder'),
readAccessibilityPoint: vi.fn().mockResolvedValue({ x: 0.74, y: 0.89 }),
readControlPoint: vi.fn().mockResolvedValue({ x: 0.75, y: 0.9 }),
readState: vi.fn().mockResolvedValue({
bodyText: `Mobile Emulator ${fixtureBytes.toString('base64').slice(0, 16)}`
}),
readTerminal: vi.fn().mockResolvedValueOnce([]).mockResolvedValueOnce([uploadedPath]),
readUploadedFile: vi.fn().mockResolvedValue(fixtureBytes),
selectFixture: vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 }),
waitForDocument: vi.fn().mockResolvedValue(args.sessionDocument)
}
await expect(
verifyHostedIosDocumentUpload({ ...args, documentFixture }, operations)
).rejects.toThrow('exposed privileged page marker')
})
it('reports the native shell attachment error before the terminal timeout', async () => {
const operations = {
openPicker: vi.fn().mockResolvedValue('native-touch-plus-web-responder'),
readAccessibilityPoint: vi.fn().mockResolvedValue({ x: 0.74, y: 0.89 }),
readControlPoint: vi.fn().mockResolvedValue({ x: 0.75, y: 0.9 }),
readState: vi.fn().mockResolvedValue({ bodyText: 'Mobile Emulator Attach failed' }),
readTerminal: vi.fn().mockResolvedValue([]),
selectFixture: vi.fn().mockResolvedValue({ x: 0.5, y: 0.3 })
}
await expect(
verifyHostedIosDocumentUpload({ ...args, timeoutMs: 100 }, operations)
).rejects.toThrow('Selected document upload failed in the native shell: Attach failed')
})
})
@@ -1,426 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
rotateHostedIosEmulator,
tapHostedIosAccessibilityControl,
tapHostedIosAccessibilityControlAtOccurrence,
tapHostedIosAccessibilityControlAtLastOccurrence,
tapHostedIosAccessibilityControlByLabelPrefix,
tapHostedIosAccessibilityControlByLabelPrefixAtPosition,
tapHostedIosAccessibilityControlStartingWith,
tapHostedIosPoint,
typeHostedIosText,
waitForHostedIosAccessibilityControl,
waitForHostedIosAccessibilityControlByLabelPrefix,
waitForHostedIosAccessibilityControlEndingWith,
waitForHostedIosAccessibilityControlMatching,
waitForHostedIosAccessibilityLabel,
waitForHostedIosAccessibilityLabelToDisappear
} from '../../scripts/hosted-ios-emulator-accessibility.mjs'
const emulator = {
deviceUdid: 'simulator-a',
orcaCli: '/repo/config/scripts/orca-dev.mjs',
userDataDir: '/tmp/orca-mobile/userData',
worktree: '/repo'
}
describe('hosted iOS emulator accessibility controls', () => {
it('rotates through the isolated Orca emulator controller', async () => {
const runCommand = vi.fn().mockResolvedValue({ stderr: '', stdout: '{}' })
await rotateHostedIosEmulator(emulator, 'landscape_left', runCommand)
expect(runCommand).toHaveBeenCalledWith(emulator, ['rotate', 'landscape_left'])
})
it('finds a nested enabled control and taps its normalized center', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'Orca',
children: [
{
label: 'Resume agent session',
enabled: true,
frame: { x: 0.6, y: 0.7, width: 0.2, height: 0.1 }
}
]
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControl(emulator, 'Resume agent session', 1_000, runCommand)
).resolves.toEqual({ x: 0.7, y: 0.75 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.7', '0.75'])
})
it('selects a specific visible occurrence without changing the label contract', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'mobile-rearch',
enabled: true,
frame: { x: 0.1, y: 0.1, width: 0.3, height: 0.1 }
},
{
label: 'mobile-rearch',
enabled: true,
frame: { x: 0.1, y: 0.3, width: 0.3, height: 0.1 }
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControlAtOccurrence(emulator, 'mobile-rearch', 1, 1_000, runCommand)
).resolves.toEqual({ x: 0.25, y: 0.35 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.25', '0.35'])
})
it('targets the last visible occurrence for native photo grids', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'Photo',
enabled: true,
frame: { x: 0, y: 0.2, width: 0.3, height: 0.1 }
},
{
label: 'Photo',
enabled: true,
frame: { x: 0.3, y: 0.4, width: 0.3, height: 0.1 }
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControlAtLastOccurrence(emulator, 'Photo', 1_000, runCommand)
).resolves.toEqual({ x: 0.44999999999999996, y: 0.45 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.44999999999999996', '0.45'])
})
it('targets a composite native row by its leading visible label', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'mobile-rearch, mobile-rearch, 1',
enabled: true,
frame: { x: 0, y: 0.2, width: 1, height: 0.1 }
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControlByLabelPrefix(emulator, 'mobile-rearch', 1_000, runCommand)
).resolves.toEqual({ x: 0.5, y: 0.25 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.5', '0.25'])
})
it('taps a relative position within a composite native control', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'orca-document-upload-fixture, Image',
enabled: true,
frame: { x: 0.1, y: 0.2, width: 0.2, height: 0.4 }
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControlByLabelPrefixAtPosition(
emulator,
'orca-document-upload-fixture',
{ x: 0.5, y: 0.25 },
1_000,
runCommand
)
).resolves.toEqual({ x: 0.2, y: 0.30000000000000004 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.2', '0.30000000000000004'])
})
it('targets a dynamic control whose label starts with a stable action prefix', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'Open changed file mobile/app/index.tsx',
enabled: true,
frame: { x: 0.1, y: 0.3, width: 0.8, height: 0.1 }
}
]
})
})
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
tapHostedIosAccessibilityControlStartingWith(
emulator,
'Open changed file ',
1_000,
runCommand
)
).resolves.toEqual({ x: 0.5, y: 0.35 })
expect(runCommand).toHaveBeenLastCalledWith(emulator, ['tap', '0.5', '0.35'])
})
it.each(['label', 'value'])(
'reads a composite row from its %s without tapping it',
async (field) => {
const runCommand = vi.fn().mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
[field]: 'Hybrid Agent History Fixture, 2h, Preview',
enabled: true,
frame: { x: 0.1, y: 0.2, width: 0.8, height: 0.1 }
}
]
})
})
await expect(
waitForHostedIosAccessibilityControlByLabelPrefix(
emulator,
'Hybrid Agent History Fixture',
1_000,
runCommand
)
).resolves.toEqual({ x: 0.5, y: 0.25 })
expect(runCommand).toHaveBeenCalledTimes(1)
}
)
it('waits for a dynamic count by its stable suffix', async () => {
const runCommand = vi.fn().mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: '128 on branch',
enabled: true,
frame: { x: 0.6, y: 0.3, width: 0.3, height: 0.1 }
}
]
})
})
await expect(
waitForHostedIosAccessibilityControlEndingWith(emulator, ' on branch', 1_000, runCommand)
).resolves.toEqual({ x: 0.75, y: 0.35 })
})
it('returns metadata for a visible control matched by shape', async () => {
const runCommand = vi.fn().mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'Stable task title, 3m, Issue · orca #1, Open',
value: '',
enabled: true,
frame: { x: 0.1, y: 0.3, width: 0.8, height: 0.1 }
}
]
})
})
await expect(
waitForHostedIosAccessibilityControlMatching(
emulator,
(node) => node.label?.endsWith(', Open'),
1_000,
runCommand
)
).resolves.toEqual({
label: 'Stable task title, 3m, Issue · orca #1, Open',
value: '',
x: 0.5,
y: 0.35
})
})
it('accepts a tall visible label whose center is below the viewport', async () => {
const runCommand = vi.fn().mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'File preview',
value: '',
enabled: true,
frame: { x: 0.03, y: 0.28, width: 0.94, height: 1.52 }
}
]
})
})
await expect(
waitForHostedIosAccessibilityLabel(emulator, 'File preview', 1_000, runCommand)
).resolves.toEqual({
frame: { x: 0.03, y: 0.28, width: 0.94, height: 1.52 },
label: 'File preview',
value: ''
})
})
it('rejects an invalid accessibility response', async () => {
await expect(
waitForHostedIosAccessibilityControl(emulator, 'Resume agent session', 1_000, async () => ({
stderr: '',
stdout: JSON.stringify({ ok: true, result: null })
}))
).rejects.toThrow('invalid accessibility response')
})
it('restarts a wedged emulator controller before retrying accessibility', async () => {
const runCommand = vi.fn(async (_args, command) => {
if (command[0] === 'ax' && runCommand.mock.calls.length === 1) {
throw new Error('emulator_helper_failed: request timed out')
}
if (command[0] === 'ax') {
return {
stderr: '',
stdout: JSON.stringify({
ok: true,
result: [
{
label: 'Resume agent session',
enabled: true,
frame: { x: 0.6, y: 0.7, width: 0.2, height: 0.1 }
}
]
})
}
}
return { stderr: '', stdout: JSON.stringify({ ok: true }) }
})
await expect(
waitForHostedIosAccessibilityControl(emulator, 'Resume agent session', 1_000, runCommand)
).resolves.toEqual({ x: 0.7, y: 0.75 })
expect(runCommand.mock.calls.map(([, command]) => command[0])).toEqual([
'ax',
'kill',
'attach',
'ax'
])
expect(runCommand).toHaveBeenNthCalledWith(3, emulator, ['attach', emulator.deviceUdid])
})
it('reattaches an explicit device before retrying a point tap', async () => {
const runCommand = vi
.fn()
.mockRejectedValueOnce(new Error('emulator_no_active'))
.mockResolvedValue({ stderr: '', stdout: JSON.stringify({ ok: true }) })
const point = { x: 0.25, y: 0.75 }
await expect(tapHostedIosPoint(emulator, point, runCommand)).resolves.toEqual(point)
expect(runCommand.mock.calls.map(([, command]) => command[0])).toEqual([
'tap',
'kill',
'attach',
'tap'
])
expect(runCommand).toHaveBeenNthCalledWith(3, emulator, ['attach', emulator.deviceUdid])
})
it('types through the isolated emulator controller', async () => {
const runCommand = vi.fn().mockResolvedValue({
stderr: '',
stdout: JSON.stringify({ ok: true })
})
await typeHostedIosText(emulator, 'Hybrid Agent History Fixture', runCommand)
expect(runCommand.mock.calls.map(([, command]) => command)).toEqual(
Array.from('Hybrid Agent History Fixture', (character) => ['type', character])
)
})
it('retries transient helper failures while reattaching a point tap', async () => {
let attachAttempts = 0
const runCommand = vi.fn(async (_args, command) => {
if (command[0] === 'tap' && runCommand.mock.calls.length === 1) {
throw new Error('emulator_no_active')
}
if (command[0] === 'attach' && attachAttempts++ === 0) {
throw new Error('emulator_helper_failed: stream endpoints unavailable')
}
return { stderr: '', stdout: JSON.stringify({ ok: true }) }
})
await expect(tapHostedIosPoint(emulator, { x: 0.25, y: 0.75 }, runCommand)).resolves.toEqual({
x: 0.25,
y: 0.75
})
expect(runCommand.mock.calls.map(([, command]) => command[0])).toEqual([
'tap',
'kill',
'attach',
'attach',
'tap'
])
})
it('waits for a reconnect label to leave the native accessibility tree', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({ ok: true, result: [{ label: 'Reconnecting' }] })
})
.mockResolvedValueOnce({
stderr: '',
stdout: JSON.stringify({ ok: true, result: [{ label: '1 tab' }] })
})
await expect(
waitForHostedIosAccessibilityLabelToDisappear(emulator, 'Reconnecting', 1_000, runCommand)
).resolves.toBeUndefined()
})
})
@@ -1,50 +0,0 @@
import path from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { startHostedIosEmulatorController } from '../../scripts/hosted-ios-emulator-controller.mjs'
const args = {
orcaCli: '/repo/config/scripts/orca-dev.mjs',
runtimeDirectory: '/tmp/mobile-e2e',
worktree: '/repo'
}
describe('hosted iOS emulator controller', () => {
it('registers the worktree in an isolated stable runtime', async () => {
const runtime = { env: { ORCA_USER_DATA_PATH: '/controller' }, stop: vi.fn() }
const startRuntime = vi.fn(async () => runtime)
const runCli = vi.fn(async () => ({ stdout: '{}', stderr: '' }))
await expect(startHostedIosEmulatorController(args, startRuntime, runCli)).resolves.toBe(
runtime
)
expect(startRuntime).toHaveBeenCalledWith(
expect.objectContaining({
enabled: true,
orcaCli: args.orcaCli,
cwd: args.worktree,
runDirectory: path.join(args.runtimeDirectory, 'emulator-control')
})
)
expect(runCli).toHaveBeenCalledWith(
args.orcaCli,
['repo', 'add', '--path', args.worktree, '--json'],
expect.objectContaining({ cwd: args.worktree, env: runtime.env })
)
})
it('stops the controller when worktree registration fails', async () => {
const runtime = { env: {}, stop: vi.fn(async () => {}) }
const error = new Error('registration failed')
await expect(
startHostedIosEmulatorController(
args,
async () => runtime,
async () => {
throw error
}
)
).rejects.toBe(error)
expect(runtime.stop).toHaveBeenCalledOnce()
})
})
@@ -1,92 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { longPressHostedIosAccessibilityControlByLabelPrefix } from '../../scripts/hosted-ios-emulator-long-press.mjs'
const emulator = {
deviceUdid: 'simulator-a',
orcaCli: '/repo/config/scripts/orca-dev.mjs',
userDataDir: '/tmp/orca-mobile/userData',
worktree: '/repo'
}
const rowTree = {
ok: true,
result: [
{
label: 'mobile-rearch, mobile-rearch, 1',
enabled: true,
frame: { x: 0, y: 0.2, width: 1, height: 0.1 }
}
]
}
const sheetTree = {
ok: true,
result: [
{ label: 'Source Control', enabled: true, frame: { x: 0, y: 0.7, width: 1, height: 0.05 } }
]
}
function axResponse(tree: unknown) {
return { stderr: '', stdout: JSON.stringify(tree) }
}
function gesturePoints(call: [unknown, string[]] | undefined) {
return JSON.parse(call?.[1][1] as string)
}
describe('hosted iOS emulator long press', () => {
it('holds the touch down across two gesture commands', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce(axResponse(rowTree))
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
.mockResolvedValueOnce(axResponse(sheetTree))
.mockResolvedValueOnce({ stderr: '', stdout: JSON.stringify({ ok: true }) })
await expect(
longPressHostedIosAccessibilityControlByLabelPrefix(
emulator,
'mobile-rearch',
20_000,
runCommand,
'Source Control'
)
).resolves.toEqual({ x: 0.5, y: 0.25 })
// Why: a single command batches its points, so the press must span two commands.
expect(runCommand.mock.calls[1]?.[1][0]).toBe('gesture')
expect(gesturePoints(runCommand.mock.calls[1])).toEqual([
{ type: 'begin', x: 0.5, y: 0.25 },
{ type: 'move', x: 0.5, y: 0.25 }
])
expect(runCommand.mock.calls[3]?.[1][0]).toBe('gesture')
expect(gesturePoints(runCommand.mock.calls[3])).toEqual([
{ type: 'move', x: 0.5, y: 0.25 },
{ type: 'end', x: 0.5, y: 0.25 }
])
})
it('releases the touch when the press never settles', async () => {
const runCommand = vi.fn(async (_args: unknown, command: string[]) => {
if (command[0] === 'ax') {
return axResponse(rowTree)
}
return { stderr: '', stdout: JSON.stringify({ ok: true }) }
})
await expect(
longPressHostedIosAccessibilityControlByLabelPrefix(
emulator,
'mobile-rearch',
2_000,
runCommand,
'Source Control'
)
).rejects.toThrow('Source Control was not accessible')
const endGestures = runCommand.mock.calls.filter(
(call) => call[1][0] === 'gesture' && gesturePoints(call).at(-1).type === 'end'
)
expect(endGestures).toHaveLength(1)
})
})
@@ -1,187 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateControl: vi.fn(),
activateWorkspace: vi.fn(),
captureScreenshot: vi.fn(),
compareScreenshots: vi.fn(),
dismissDeveloperMenu: vi.fn(),
readControlPoint: vi.fn(),
readState: vi.fn(),
readTextPoint: vi.fn(),
tapControl: vi.fn(),
tapControlByPrefix: vi.fn(),
tapPoint: vi.fn(),
waitForControl: vi.fn(),
waitForControlByPrefix: vi.fn(),
waitForLabel: vi.fn(),
waitForDocument: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: mocks.captureScreenshot
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: mocks.dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
tapHostedIosAccessibilityControl: mocks.tapControl,
tapHostedIosAccessibilityControlByLabelPrefix: mocks.tapControlByPrefix,
tapHostedIosPoint: mocks.tapPoint,
waitForHostedIosAccessibilityControl: mocks.waitForControl,
waitForHostedIosAccessibilityControlByLabelPrefix: mocks.waitForControlByPrefix,
waitForHostedIosAccessibilityLabel: mocks.waitForLabel
}))
vi.mock('../../scripts/hosted-ios-screenshot-parity.mjs', () => ({
assertHostedIosScreenshotParity: mocks.compareScreenshots
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activateControl,
readHostedWebViewState: mocks.readState,
readHostedWebViewTextPoint: mocks.readTextPoint,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
vi.mock('../../scripts/hosted-webview-control-point.mjs', () => ({
readHostedWebViewControlPoint: mocks.readControlPoint
}))
vi.mock('../../scripts/hosted-webview-workspace-activation.mjs', () => ({
activateHostedWorkspaceRow: mocks.activateWorkspace
}))
import {
captureHostedFilesPreviewParity,
captureNativeFilesPreviewBaselines
} from '../../scripts/hosted-ios-files-preview-parity.mjs'
describe('hosted iOS Files and Preview parity', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.captureScreenshot.mockImplementation((_command, _args, callback) =>
callback(null, '', '')
)
mocks.compareScreenshots.mockResolvedValue({ changedPixelRatio: 0.01 })
mocks.readControlPoint.mockResolvedValue({ x: 0.8, y: 0.1 })
mocks.readState.mockResolvedValue({
href: 'orca-mobile-web://build/h/host/files/worktree',
bodyText: 'Files Casks orca.rb',
labels: ['Open folder Casks', 'Preview file orca.rb', 'File preview'],
placeholders: []
})
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.tapControl.mockResolvedValue(undefined)
mocks.tapControlByPrefix.mockResolvedValue(undefined)
mocks.tapPoint.mockResolvedValue(undefined)
mocks.waitForControl.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControlByPrefix.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForLabel.mockResolvedValue({
frame: { x: 0, y: 0.3, width: 1, height: 1.5 },
label: 'File preview',
value: ''
})
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes }) => ({
href: `orca-mobile-web://build${expectedHrefIncludes ?? '/h/host'}`,
targetId: expectedHrefIncludes ?? 'workspace'
}))
})
it('captures native Files and Preview and returns to worktrees', async () => {
const baselines = await captureNativeFilesPreviewBaselines({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Open file explorer',
30_000
)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Preview file orca.rb',
30_000
)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Back to worktrees',
30_000
)
expect(baselines).toEqual({
files: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-files-portrait.png'
},
preview: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-file-preview-portrait.png'
}
})
})
it('compares hosted Files and Preview and returns to the workspace route', async () => {
const result = await captureHostedFilesPreviewParity({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
nativeBaselines: {
files: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-files-portrait.png'
},
preview: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-file-preview-portrait.png'
}
},
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' }
})
expect(result.evidence.files.screenshotParity).toEqual({ changedPixelRatio: 0.01 })
expect(result.evidence.preview.screenshotParity).toEqual({ changedPixelRatio: 0.01 })
expect(result.workspaceDocument.href).toContain('/h/host')
expect(mocks.activateWorkspace).toHaveBeenCalledOnce()
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Open file explorer',
5_000
)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Preview file orca.rb',
5_000
)
expect(mocks.compareScreenshots).toHaveBeenCalledTimes(2)
})
it('uses a measured WebView point when a control is missing from native accessibility', async () => {
mocks.tapControl.mockRejectedValueOnce(new Error('missing accessibility descendant'))
await captureHostedFilesPreviewParity({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
nativeBaselines: {
files: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-files-portrait.png'
},
preview: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-file-preview-portrait.png'
}
},
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' }
})
expect(mocks.readControlPoint).toHaveBeenCalledWith(expect.anything(), 'Open file explorer')
expect(mocks.tapPoint).toHaveBeenCalledWith({ deviceUdid: 'simulator' }, { x: 0.8, y: 0.1 })
})
})
@@ -1,159 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
runHostedHostOriginSourceControlStep,
verifyHostedHostOriginSourceControlJourney
} from '../../scripts/hosted-ios-host-origin-source-control-journey.mjs'
const tapByLabelPrefix = vi.hoisted(() => vi.fn())
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
tapHostedIosAccessibilityControlByLabelPrefix: tapByLabelPrefix
}))
const CHANGED_FILE = 'mobile/src/mobile-web/bridge.ts'
const CHANGED_FILE_LABEL = `Open changed file ${CHANGED_FILE}`
function journeyOperations(labels: string[]) {
const sourceControl = {
href: 'orca-mobile-web://build/h/host/source-control/workspace'
}
const review = { href: 'orca-mobile-web://build/h/host/review/workspace' }
const returnedWorkspace = { href: 'orca-mobile-web://build/' }
const activate = vi.fn().mockResolvedValue(undefined)
const longPress = vi.fn().mockResolvedValue(undefined)
const tapNative = vi.fn().mockResolvedValue(undefined)
const waitForDocument = vi
.fn()
.mockResolvedValueOnce(sourceControl)
.mockResolvedValueOnce(review)
.mockResolvedValueOnce(sourceControl)
.mockResolvedValueOnce(sourceControl)
.mockResolvedValueOnce(returnedWorkspace)
const readState = vi
.fn()
.mockResolvedValueOnce({ href: sourceControl.href, labels })
.mockResolvedValueOnce({ href: review.href, labels: ['Show all review files'] })
.mockResolvedValueOnce({ href: review.href, labels: ['Back', 'Open review actions'] })
return {
documents: { returnedWorkspace, review, sourceControl },
operations: { activate, longPress, readState, tapNative, waitForDocument }
}
}
describe('hosted iOS host-origin Source Control journey', () => {
it.each([
['native baseline', { changedFileLabel: CHANGED_FILE_LABEL }, undefined],
['fixture changed file', null, CHANGED_FILE]
])('opens mobile Review from the %s oracle', async (_name, nativeBaseline, changedFilePath) => {
const { documents, operations } = journeyOperations([
'Refresh source control',
CHANGED_FILE_LABEL
])
const result = await verifyHostedHostOriginSourceControlJourney({
changedFilePath,
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { udid: 'SIMULATOR-1' },
nativeBaseline,
timeoutMs: 30_000,
workspaceName: 'mobile-rearch',
operations
})
expect(operations.longPress).toHaveBeenCalledWith(
{ udid: 'SIMULATOR-1' },
'mobile-rearch',
30_000,
undefined,
'Source Control'
)
expect(operations.tapNative).toHaveBeenCalledWith(
{ udid: 'SIMULATOR-1' },
'Source Control',
30_000
)
expect(operations.activate.mock.calls.map((call) => call[1])).toEqual([
{ kind: 'label', value: CHANGED_FILE_LABEL },
{ kind: 'label', value: 'Back' },
{ kind: 'label', value: 'Back to session' }
])
expect(operations.waitForDocument).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ expectedHrefIncludes: '/review/' })
)
expect(operations.waitForDocument).toHaveBeenCalledTimes(5)
expect(result).toMatchObject({
changedFileLabel: CHANGED_FILE_LABEL,
reviewRoute: documents.review.href,
sourceControlRoute: documents.sourceControl.href,
workspaceDocument: documents.returnedWorkspace
})
})
it('fails when the fixture changed file is absent from Source Control', async () => {
const { operations } = journeyOperations([
'Refresh source control',
'Open changed file mobile/app/index.tsx'
])
await expect(
verifyHostedHostOriginSourceControlJourney({
changedFilePath: CHANGED_FILE,
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { udid: 'SIMULATOR-1' },
nativeBaseline: null,
timeoutMs: 30_000,
workspaceName: 'mobile-rearch',
operations
})
).rejects.toThrow(`Host-origin Source Control is missing ${CHANGED_FILE_LABEL}`)
expect(operations.activate).not.toHaveBeenCalled()
})
it('refuses to run without an independent expected changed file', async () => {
const { operations } = journeyOperations(['Refresh source control', CHANGED_FILE_LABEL])
await expect(
verifyHostedHostOriginSourceControlJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { udid: 'SIMULATOR-1' },
nativeBaseline: null,
timeoutMs: 30_000,
workspaceName: 'mobile-rearch',
operations
})
).rejects.toThrow('needs a native baseline or a fixture changed file path')
expect(operations.activate).not.toHaveBeenCalled()
})
it.each([
[
'the adversarial fixture row',
{ workspaceRowName: 'orca-adversarial-row' },
'orca-adversarial-row'
],
['the worktree basename', undefined, 'mobile-rearch']
])('lands back on %s after the host-origin step', async (_name, adversarialFixture, expected) => {
const hostOrigin = { workspaceDocument: { href: 'orca-mobile-web://build/' } }
// The journey itself is covered above; the step only owns the workspace row it returns to.
const evidenceStep = vi.fn().mockResolvedValue(hostOrigin)
await expect(
runHostedHostOriginSourceControlStep({
adversarialFixture,
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { udid: 'SIMULATOR-1' },
evidenceStep,
expectedWorkspace: 'mobile-rearch',
nativeBaseline: null,
timeoutMs: 30_000
})
).resolves.toBe(hostOrigin)
expect(evidenceStep).toHaveBeenCalledWith(
'host-origin Source Control journey',
expect.any(Function)
)
expect(tapByLabelPrefix).toHaveBeenLastCalledWith({ udid: 'SIMULATOR-1' }, expected, 30_000)
})
})
@@ -1,12 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { openHostedIosHybridRoute } from '../../scripts/hosted-ios-hybrid-route-handoff.mjs'
describe('hosted iOS hybrid route handoff', () => {
it('opens the production route without an Experimental Settings entry', async () => {
const openUrl = vi.fn().mockResolvedValue(undefined)
await openHostedIosHybridRoute({ deviceUdid: 'simulator' }, 10_000, openUrl)
expect(openUrl).toHaveBeenCalledWith('simulator', 'orca:///hybrid')
})
})
@@ -1,87 +0,0 @@
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { readFileSync } from 'node:fs'
import { afterEach, describe, expect, it } from 'vitest'
import {
readIosActivation,
readIosRollbackActivation
} from '../../scripts/hosted-ios-mobile-web-cache.mjs'
import { selectHostedIosWebContentPid } from '../../scripts/hosted-ios-webcontent-process.mjs'
const active = 'a'.repeat(64)
const previous = 'b'.repeat(64)
const temporaryRoots: string[] = []
const harnessSource = readFileSync(
new URL('../../scripts/run-hosted-ios-webview-crash-loop.mjs', import.meta.url),
'utf8'
)
afterEach(async () => {
await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true })))
})
describe('hosted iOS mobile web cache evidence', () => {
it('selects the only host with an active and previous generation', async () => {
const root = await createAppData()
await writeActivation(root, '1'.repeat(64), { active })
const candidate = await writeActivation(root, '2'.repeat(64), { active, previous })
await expect(readIosRollbackActivation(root)).resolves.toEqual({
path: candidate,
active,
previous
})
})
it('normalizes an omitted previous generation and rejects invalid identities', async () => {
const root = await createAppData()
const valid = await writeActivation(root, '1'.repeat(64), { active })
const invalid = await writeActivation(root, '2'.repeat(64), { active: '../active' })
await expect(readIosActivation(valid)).resolves.toEqual({ active, previous: null })
await expect(readIosActivation(invalid)).rejects.toThrow(
'iOS cache returned an invalid activation record'
)
})
it('selects only the simulator WebContent child', () => {
const processes = [
' 100 1 /System/Library/com.apple.WebKit.WebContent',
' 200 67323 /Runtime/WebContentExtension.appex/com.apple.WebKit.WebContent -LaunchArguments',
' 300 90000 /Runtime/WebContentExtension.appex/com.apple.WebKit.WebContent -LaunchArguments'
].join('\n')
expect(selectHostedIosWebContentPid(processes, 67323)).toBe(200)
expect(() => selectHostedIosWebContentPid(`${processes}\n${processes}`, 67323)).toThrow(
'Expected one iOS WebContent process, found 2'
)
})
it('kills three WebContent processes and requires native activation rollback', () => {
expect(harnessSource).toContain('const failureCount = 3')
expect(harnessSource).toContain('terminateHostedIosWebContent(deviceUdid)')
expect(harnessSource).toContain('initial.previous')
expect(harnessSource).toContain('waitForIosActivation(')
expect(harnessSource).toContain('>= 60_000')
expect(harnessSource).toContain('documents.at(-1)?.href === documents[0]?.href')
})
})
async function createAppData() {
const root = await mkdtemp(path.join(tmpdir(), 'orca-ios-cache-evidence-'))
temporaryRoots.push(root)
return root
}
async function writeActivation(
root: string,
hostIdentity: string,
value: { active: string; previous?: string }
) {
const hostRoot = path.join(root, 'Library', 'Application Support', 'OrcaMobileWeb', hostIdentity)
await mkdir(hostRoot, { recursive: true })
const activationPath = path.join(hostRoot, 'activation.json')
await writeFile(activationPath, JSON.stringify(value))
return activationPath
}
@@ -1,66 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { verifyHostedIosNativeAlertJourney } from '../../scripts/hosted-ios-native-alert-journey.mjs'
describe('hosted iOS native Alert journey', () => {
it('presents an OS alert, correlates its action, and returns to workspaces', async () => {
const workspaceDocument = { href: 'orca-mobile-web://build/h/host' }
const sessionDocument = { href: 'orca-mobile-web://build/h/host/session/workspace' }
const returnedDocument = { href: 'orca-mobile-web://build/h/host' }
const evaluate = vi
.fn()
.mockResolvedValueOnce(JSON.stringify({ started: true }))
.mockResolvedValueOnce(JSON.stringify({ posted: true }))
.mockResolvedValueOnce(
JSON.stringify({ status: 'success', payload: { kind: 'button', buttonIndex: 0 } })
)
const waitForDocument = vi
.fn()
.mockResolvedValueOnce(sessionDocument)
.mockResolvedValueOnce(returnedDocument)
const activateWorkspace = vi.fn().mockResolvedValue(undefined)
const activateControl = vi.fn().mockResolvedValue(undefined)
const waitForLabel = vi.fn().mockResolvedValue({ frame: { x: 0, y: 0, width: 1, height: 1 } })
const tapControl = vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 })
const waitForLabelToDisappear = vi.fn().mockResolvedValue(undefined)
const result = await verifyHostedIosNativeAlertJourney(
{
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
timeoutMs: 30_000,
workspaceDocument
},
{
activateControl,
activateWorkspace,
evaluate,
tapControl,
waitForDocument,
waitForLabel,
waitForLabelToDisappear
}
)
expect(tapControl).toHaveBeenCalledWith({ deviceUdid: 'simulator' }, 'Keep editing', 30_000)
expect(activateControl).toHaveBeenCalledWith(sessionDocument, {
kind: 'label',
value: 'Back to worktrees'
})
expect(waitForDocument).toHaveBeenLastCalledWith({
discoveryUrl: 'http://127.0.0.1:9222',
expectedText: 'mobile-rearch',
expectedPathname: '/h/host',
timeoutMs: 30_000
})
expect(result).toEqual({
evidence: {
button: { x: 0.5, y: 0.5 },
buttonIndex: 0,
presentation: 'native',
title: { x: 0, y: 0, width: 1, height: 1 }
},
workspaceDocument: returnedDocument
})
})
})
@@ -1,63 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
tap: vi.fn(),
wait: vi.fn()
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
tapHostedIosAccessibilityControl: mocks.tap,
waitForHostedIosAccessibilityControlByLabelPrefix: mocks.wait
}))
import { completeHostedIosNativeOnboarding } from '../../scripts/hosted-ios-native-onboarding.mjs'
describe('hosted iOS native onboarding', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.tap.mockResolvedValue({ x: 0.5, y: 0.5 })
mocks.wait.mockResolvedValue({ x: 0.5, y: 0.5 })
})
it('completes the existing native decisions before entering the hosted workspace', async () => {
mocks.wait.mockRejectedValueOnce(new Error('workspace is not visible'))
const emulator = { deviceUdid: 'simulator' }
await expect(
completeHostedIosNativeOnboarding(emulator, 'mobile-rearch', 30_000)
).resolves.toEqual({
sessionView: 'terminal',
notifications: 'skipped'
})
expect(mocks.tap).toHaveBeenNthCalledWith(1, emulator, 'Open sessions in the terminal', 30_000)
expect(mocks.tap).toHaveBeenNthCalledWith(2, emulator, 'Skip notifications for now', 30_000)
expect(mocks.wait).toHaveBeenNthCalledWith(1, emulator, 'mobile-rearch', 1_000)
expect(mocks.wait).toHaveBeenNthCalledWith(2, emulator, 'mobile-rearch', 5_000)
})
it('returns from an automatically opened session before selecting the workspace', async () => {
mocks.wait
.mockRejectedValueOnce(new Error('workspace is not visible'))
.mockRejectedValueOnce(new Error('workspace is not visible'))
.mockResolvedValueOnce({ x: 0.5, y: 0.5 })
const emulator = { deviceUdid: 'simulator' }
await completeHostedIosNativeOnboarding(emulator, 'mobile-rearch', 30_000)
expect(mocks.tap).toHaveBeenNthCalledWith(3, emulator, 'Back to worktrees', 5_000)
expect(mocks.wait).toHaveBeenNthCalledWith(3, emulator, 'mobile-rearch', 30_000)
})
it('preserves an already completed native route', async () => {
const emulator = { deviceUdid: 'simulator' }
await expect(
completeHostedIosNativeOnboarding(emulator, 'mobile-rearch', 30_000)
).resolves.toEqual({
sessionView: 'retained',
notifications: 'retained'
})
expect(mocks.wait).toHaveBeenCalledWith(emulator, 'mobile-rearch', 1_000)
expect(mocks.tap).not.toHaveBeenCalled()
})
})
@@ -1,86 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activate: vi.fn(),
restartController: vi.fn(),
waitForDocument: vi.fn(),
tapAccessibility: vi.fn(),
waitForAccessibility: vi.fn()
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activate,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
restartHostedIosEmulatorController: mocks.restartController,
tapHostedIosAccessibilityControl: mocks.tapAccessibility,
waitForHostedIosAccessibilityControl: mocks.waitForAccessibility
}))
import { verifyHostedNativeTerminalSettingsHandoff } from '../../scripts/hosted-ios-native-settings-handoff.mjs'
describe('hosted iOS native settings handoff', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.activate.mockResolvedValue(undefined)
mocks.restartController.mockResolvedValue(undefined)
mocks.waitForDocument
.mockResolvedValueOnce({ href: 'orca-mobile-web://build/session/workspace' })
.mockResolvedValueOnce({ href: 'orca-mobile-web://build/session/workspace' })
mocks.tapAccessibility.mockResolvedValue({ x: 0.5, y: 0.7 })
mocks.waitForAccessibility.mockResolvedValue({ x: 0.5, y: 0.2 })
})
it('uses a native touch and returns to the same hosted session', async () => {
const emulator = { deviceUdid: 'simulator' }
await expect(
verifyHostedNativeTerminalSettingsHandoff({
discoveryUrl: 'http://127.0.0.1:9222',
emulator,
sessionDocument: { href: 'orca-mobile-web://build/session/workspace' },
timeoutMs: 30_000
})
).resolves.toEqual({
route: 'orca-mobile-web://build/session/workspace',
nativeTapPoint: { x: 0.5, y: 0.7 }
})
expect(mocks.activate).toHaveBeenCalledWith(expect.anything(), {
kind: 'label',
value: 'Add custom shortcut',
reveal: true
})
expect(mocks.restartController).toHaveBeenCalledWith(emulator)
expect(mocks.tapAccessibility).toHaveBeenCalledWith(emulator, 'Manage Shortcuts', 30_000)
expect(mocks.waitForAccessibility).toHaveBeenCalledWith(
emulator,
'WHEN YOU LEAVE THE APP',
30_000
)
expect(mocks.tapAccessibility).toHaveBeenNthCalledWith(
2,
emulator,
'Back to hosted session',
30_000
)
})
it('rejects a native return that changes the hosted session route', async () => {
mocks.waitForDocument
.mockReset()
.mockResolvedValueOnce({ href: 'orca-mobile-web://build/session/one' })
.mockResolvedValueOnce({ href: 'orca-mobile-web://build/session/two' })
await expect(
verifyHostedNativeTerminalSettingsHandoff({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: {},
sessionDocument: {},
timeoutMs: 30_000
})
).rejects.toThrow('same hosted session route')
})
})
@@ -1,141 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
resetHostedIosPhotosPermission,
verifyHostedIosPhotoPermissionDenial
} from '../../scripts/hosted-ios-photo-permission-denial.mjs'
const args = {
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
}
function createOperations() {
return {
readState: vi.fn().mockResolvedValue({
bodyText: 'Mobile Emulator\nPhoto permission denied'
}),
readTerminal: vi.fn().mockResolvedValue(['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE']),
tapControl: vi.fn().mockResolvedValue({ x: 0.75, y: 0.9 }),
tapPoint: vi.fn().mockResolvedValue(undefined),
wait: vi.fn().mockResolvedValue(undefined),
waitForDocument: vi.fn().mockResolvedValue(args.sessionDocument),
waitForPrompt: vi.fn().mockResolvedValue({
label: 'Don’t Allow',
x: 0.5,
y: 0.6
})
}
}
describe('hosted iOS Photos permission denial', () => {
it('resets only the app Photos permission before launch', async () => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '', stderr: '' })
await resetHostedIosPhotosPermission('simulator', runCommand)
expect(runCommand).toHaveBeenCalledWith('xcrun', [
'simctl',
'privacy',
'simulator',
'reset',
'photos',
'com.stably.orca.mobile'
])
})
it('denies through the unchanged Attach flow without changing terminal output', async () => {
const operations = createOperations()
await expect(verifyHostedIosPhotoPermissionDenial(args, operations)).resolves.toEqual({
evidence: {
activationAttempts: 1,
attachControlPoint: { x: 0.75, y: 0.9 },
denialControl: { label: 'Don’t Allow', x: 0.5, y: 0.6 },
privilegedPageMarkers: 'absent',
route: args.sessionDocument.href,
terminalOutput: 'unchanged',
toast: 'Photo permission denied'
},
sessionDocument: args.sessionDocument
})
expect(operations.tapControl).toHaveBeenCalledWith(
args.emulator,
'Attach a photo',
args.timeoutMs
)
expect(operations.waitForPrompt).toHaveBeenCalledWith(
args.emulator,
['Don’t Allow', "Don't Allow"],
5_000
)
expect(operations.tapPoint).toHaveBeenCalledWith(args.emulator, {
label: 'Don’t Allow',
x: 0.5,
y: 0.6
})
expect(operations.readTerminal).toHaveBeenCalledTimes(2)
})
it('retries a silent Attach activation before denying the prompt', async () => {
const operations = createOperations()
operations.waitForPrompt
.mockRejectedValueOnce(new Error('prompt not shown'))
.mockResolvedValueOnce({ label: "Don't Allow", x: 0.5, y: 0.6 })
const result = await verifyHostedIosPhotoPermissionDenial(args, operations)
expect(result.evidence).toMatchObject({
activationAttempts: 2,
denialControl: { label: "Don't Allow" }
})
expect(operations.tapControl).toHaveBeenCalledTimes(2)
})
it('rejects a privileged image marker in hosted page state', async () => {
const operations = createOperations()
operations.readState.mockResolvedValue({
bodyText: 'Photo permission denied\ndata:image/png;base64,AAAA'
})
await expect(verifyHostedIosPhotoPermissionDenial(args, operations)).rejects.toThrow(
'exposed privileged page marker: data:image/'
)
})
it('rejects any terminal output change after denial', async () => {
const operations = createOperations()
operations.readTerminal
.mockResolvedValueOnce(['before'])
.mockResolvedValueOnce(['before', '/tmp/orca-paste-private.png'])
await expect(verifyHostedIosPhotoPermissionDenial(args, operations)).rejects.toThrow(
'changed the Desktop terminal'
)
})
it('accepts terminal row reflow when the character stream is unchanged', async () => {
const operations = createOperations()
operations.readTerminal
.mockResolvedValueOnce([
'jinwoo ~/orca/',
'ORCA_HOSTED_CLIPBOARD_TEXT_PASTE',
'/tmp/orca-paste.png'
])
.mockResolvedValueOnce([
'jinwoo ~/orca/workspaces/orca/mobile-rearch [mobile-rearch] $ ',
'ORCA_HOSTED_CLIPBOARD_',
'TEXT_PASTE/tmp/orca-paste.png'
])
await expect(verifyHostedIosPhotoPermissionDenial(args, operations)).resolves.toBeDefined()
})
})
@@ -1,331 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
backgroundHostedIosMobileApp,
grantHostedIosPhotosPermission,
launchHostedIosMobileApp,
revokeHostedIosPhotosPermission,
verifyHostedIosPhotoPermissionRevocation
} from '../../scripts/hosted-ios-photo-permission-revocation.mjs'
const args = {
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
}
const grantedDocument = {
href: args.sessionDocument.href
.replace('://build/', '://grant-restart/')
.replace('/workspace', '/grant-workspace')
}
const revokedDocument = {
href: args.sessionDocument.href
.replace('://build/', '://revocation-restart/')
.replace('/workspace', '/revoked-workspace')
}
function createOperations() {
return {
activateWorkspace: vi.fn().mockResolvedValue(undefined),
backgroundApp: vi.fn().mockResolvedValue(undefined),
dismissDeveloperMenu: vi.fn().mockResolvedValue(false),
grantPermission: vi.fn().mockResolvedValue(undefined),
launchApp: vi.fn().mockResolvedValue(undefined),
openHybridRoute: vi.fn().mockResolvedValue(undefined),
readState: vi.fn().mockResolvedValue({
bodyText: 'Mobile Emulator\nPhoto permission denied'
}),
readTerminal: vi.fn().mockResolvedValue(['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE']),
revokePermission: vi.fn().mockResolvedValue(undefined),
tapControl: vi
.fn()
.mockResolvedValueOnce({ x: 0.75, y: 0.9 })
.mockResolvedValueOnce({ x: 0.75, y: 0.9 }),
tapPoint: vi.fn().mockResolvedValue(undefined),
wait: vi.fn().mockResolvedValue(undefined),
waitForControl: vi.fn().mockResolvedValue({ label: 'Cancel', x: 0.9, y: 0.1 }),
waitForPickerDismissal: vi.fn().mockResolvedValue(undefined),
waitForDocument: vi
.fn()
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(revokedDocument)
}
}
describe('hosted iOS Photos permission revocation', () => {
it.each([
['grant', grantHostedIosPhotosPermission],
['revoke', revokeHostedIosPhotosPermission]
])('%ss only the app Photos permission', async (action, changePermission) => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '', stderr: '' })
await changePermission('simulator', runCommand)
expect(runCommand).toHaveBeenCalledWith('xcrun', [
'simctl',
'privacy',
'simulator',
action,
'photos',
'com.stably.orca.mobile'
])
})
it('launches only the exact mobile app', async () => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '', stderr: '' })
await launchHostedIosMobileApp('simulator', runCommand)
expect(runCommand).toHaveBeenCalledWith('xcrun', [
'simctl',
'launch',
'simulator',
'com.stably.orca.mobile'
])
})
it('backgrounds through the emulator Home button', async () => {
const runCommand = vi.fn().mockResolvedValue(undefined)
await backgroundHostedIosMobileApp(args.emulator, runCommand)
expect(runCommand).toHaveBeenCalledWith(args.emulator, ['button', 'home'])
})
it('opens under a grant, revokes, and denies without changing terminal output', async () => {
const operations = createOperations()
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).resolves.toEqual({
evidence: {
grantedAttachControlPoint: { x: 0.75, y: 0.9 },
grantPrivateOriginRotated: true,
grantPermissionPrompt: 'not-shown',
grantSessionRecovery: 'session-retained',
grantWorkspaceAuthorityRotated: true,
interruptionPrivateOrigin: 'retained',
interruptionSessionRecovery: 'session-retained',
interruptionWorkspaceAuthority: 'retained',
permissionState: 'revoked-after-grant',
pickerInterruption: 'resumed-then-cancelled',
pickerCancelControl: { label: 'Cancel', x: 0.9, y: 0.1 },
privilegedPageMarkers: 'absent',
revokedAttachControlPoint: { x: 0.75, y: 0.9 },
revocationPrivateOriginRotated: true,
revocationSessionRecovery: 'session-retained',
revocationWorkspaceAuthorityRotated: true,
route: revokedDocument.href,
routeRestored: true,
resumedPickerControl: { label: 'Cancel', x: 0.9, y: 0.1 },
terminalOutput: 'unchanged',
toast: 'Photo permission denied'
},
sessionDocument: revokedDocument
})
expect(operations.grantPermission).toHaveBeenCalledWith('simulator')
expect(operations.revokePermission).toHaveBeenCalledWith('simulator')
expect(operations.backgroundApp).toHaveBeenCalledOnce()
expect(operations.backgroundApp).toHaveBeenCalledWith(args.emulator)
expect(operations.waitForPickerDismissal).toHaveBeenCalledWith(args.emulator, 'Cancel', 5_000)
expect(operations.launchApp).toHaveBeenCalledTimes(3)
expect(operations.dismissDeveloperMenu).toHaveBeenCalledTimes(3)
expect(operations.tapControl).toHaveBeenNthCalledWith(
1,
args.emulator,
'Attach a photo',
args.timeoutMs
)
expect(operations.tapControl).toHaveBeenNthCalledWith(
2,
args.emulator,
'Attach a photo',
args.timeoutMs
)
expect(operations.waitForControl).toHaveBeenCalledWith(
args.emulator,
['Cancel', 'Allow Full Access'],
10_000
)
expect(operations.waitForControl).toHaveBeenCalledWith(args.emulator, ['Cancel'], 5_000)
expect(operations.readTerminal).toHaveBeenCalledTimes(2)
})
it('rejects privileged page markers after revocation', async () => {
const operations = createOperations()
operations.readState.mockResolvedValue({
bodyText: 'Photo permission denied\norca-paste-secret.png'
})
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'exposed privileged page marker: orca-paste-'
)
})
it('accepts the real full-access prompt before cancelling the granted picker', async () => {
const operations = createOperations()
operations.waitForControl
.mockResolvedValueOnce({ label: 'Allow Full Access', x: 0.5, y: 0.7 })
.mockResolvedValueOnce({ label: 'Cancel', x: 0.9, y: 0.1 })
const result = await verifyHostedIosPhotoPermissionRevocation(args, operations)
expect(result.evidence.grantPermissionPrompt).toBe('allowed-full-access')
expect(operations.tapPoint).toHaveBeenNthCalledWith(1, args.emulator, {
label: 'Allow Full Access',
x: 0.5,
y: 0.7
})
expect(operations.tapPoint).toHaveBeenNthCalledWith(2, args.emulator, {
label: 'Cancel',
x: 0.9,
y: 0.1
})
expect(operations.backgroundApp).toHaveBeenCalledWith(args.emulator)
})
it('rejects a private origin reused across the grant restart', async () => {
const operations = createOperations()
operations.waitForDocument.mockReset().mockResolvedValue(args.sessionDocument)
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'grant reused the private WebView origin'
)
})
it('rejects a private origin reused across the revocation restart', async () => {
const operations = createOperations()
operations.waitForDocument
.mockReset()
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'revocation reused the private WebView origin'
)
})
it('rejects private origin replacement across picker interruption', async () => {
const operations = createOperations()
const replacedOrigin = {
href: grantedDocument.href.replace('://grant-restart/', '://interruption-restart/')
}
operations.waitForDocument
.mockReset()
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(replacedOrigin)
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'picker interruption authority did not match session-retained recovery'
)
})
it('rejects opaque authority replacement across picker interruption', async () => {
const operations = createOperations()
const replacedAuthority = {
href: grantedDocument.href.replace('/grant-workspace', '/interrupted-workspace')
}
operations.waitForDocument
.mockReset()
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(replacedAuthority)
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'picker interruption authority did not match session-retained recovery'
)
})
it('requires rotated authority when picker interruption needs route handoff', async () => {
const operations = createOperations()
const workspaceDocument = { href: 'orca-mobile-web://workspace/h/host' }
const interruptedDocument = {
href: grantedDocument.href
.replace('://grant-restart/', '://interruption-restart/')
.replace('/grant-workspace', '/interrupted-workspace')
}
operations.waitForDocument
.mockReset()
.mockResolvedValueOnce(grantedDocument)
.mockRejectedValueOnce(new Error('session not retained'))
.mockResolvedValueOnce(workspaceDocument)
.mockResolvedValueOnce(interruptedDocument)
.mockResolvedValueOnce(revokedDocument)
const result = await verifyHostedIosPhotoPermissionRevocation(args, operations)
expect(result.evidence).toMatchObject({
interruptionPrivateOrigin: 'rotated',
interruptionSessionRecovery: 'hybrid-route-handoff',
interruptionWorkspaceAuthority: 'rotated'
})
})
it('skips row activation when the hybrid route restores the session itself', async () => {
const operations = createOperations()
operations.waitForDocument
.mockReset()
.mockRejectedValueOnce(new Error('session not retained'))
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(revokedDocument)
const result = await verifyHostedIosPhotoPermissionRevocation(args, operations)
expect(result.evidence).toMatchObject({ grantSessionRecovery: 'hybrid-route-handoff' })
expect(operations.openHybridRoute).toHaveBeenCalledOnce()
expect(operations.activateWorkspace).not.toHaveBeenCalled()
})
it('reopens the production hybrid route after permission-triggered termination', async () => {
const operations = createOperations()
const workspaceDocument = { href: 'orca-mobile-web://workspace/h/host' }
operations.waitForDocument
.mockReset()
.mockRejectedValueOnce(new Error('session not retained'))
.mockResolvedValueOnce(workspaceDocument)
.mockResolvedValueOnce(grantedDocument)
.mockResolvedValueOnce(grantedDocument)
.mockRejectedValueOnce(new Error('session not retained'))
.mockResolvedValueOnce(workspaceDocument)
.mockResolvedValueOnce(revokedDocument)
const result = await verifyHostedIosPhotoPermissionRevocation(args, operations)
expect(result.evidence).toMatchObject({
grantSessionRecovery: 'hybrid-route-handoff',
interruptionSessionRecovery: 'session-retained',
revocationSessionRecovery: 'hybrid-route-handoff',
routeRestored: true
})
expect(operations.openHybridRoute).toHaveBeenCalledTimes(2)
expect(operations.openHybridRoute).toHaveBeenCalledWith(args.emulator, args.timeoutMs)
expect(operations.activateWorkspace).toHaveBeenCalledTimes(2)
expect(operations.activateWorkspace).toHaveBeenCalledWith(
workspaceDocument,
args.expectedWorkspace,
expect.any(Function),
args.timeoutMs,
expect.any(Function)
)
})
it('rejects terminal mutations after revocation', async () => {
const operations = createOperations()
operations.readTerminal
.mockResolvedValueOnce(['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE'])
.mockResolvedValueOnce(['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE', '/tmp/orca-paste-private.png'])
await expect(verifyHostedIosPhotoPermissionRevocation(args, operations)).rejects.toThrow(
'changed the Desktop terminal'
)
})
})
@@ -1,42 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
hostedIosLogStartTime,
hostedIosPrivacyLogEvidence
} from '../../scripts/hosted-ios-privacy-log-audit.mjs'
describe('hosted iOS privacy log audit', () => {
it('formats simulator log start times without ISO separators', () => {
const value = hostedIosLogStartTime(new Date(2026, 6, 29, 12, 34, 56).getTime())
expect(value).toBe('2026-07-29 12:34:56')
})
it('accepts fixed-category connection and recovery logs', () => {
expect(
hostedIosPrivacyLogEvidence(
'[net] state {"endpoint":"websocket","from":"connecting","to":"connected"}'
)
).toEqual({
logBytes: 73,
counts: {
privilegedField: 0,
tokenStorage: 0,
nativeAuthority: 0,
privateOriginUrl: 0,
webSocketUrl: 0,
fixtureMarker: 0
}
})
})
it.each([
'deviceToken',
'orca.host-token.host-a',
'openHostLogicalClient',
'orca-mobile-web://abcdefghijklmnopqrstuvwxyz/',
'wss://paired-host.invalid/socket',
'ORCA_E2E_MOBILE_WEB_HOST_PUBLIC_KEY'
])('rejects privileged log marker %s', (marker) => {
expect(() => hostedIosPrivacyLogEvidence(marker)).toThrow('privacy log audit failed')
})
})
@@ -1,131 +0,0 @@
import { mkdtemp, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { PNG } from 'pngjs'
import { describe, expect, it } from 'vitest'
import { assertHostedIosScreenshotParity } from '../../scripts/hosted-ios-screenshot-parity.mjs'
describe('hosted iOS screenshot parity', () => {
it('masks status-bar changes while enforcing the shared content surface', async () => {
const native = createScreenshot(100, 100)
const hosted = createScreenshot(100, 100)
paintRows(hosted, 0, 6, 255)
const paths = await writeScreenshots(native, hosted)
const result = await assertHostedIosScreenshotParity({
...paths,
nativeLandmark: { x: 0.5, y: 0.1 },
hostedLandmark: { x: 0.7, y: 0.104 }
})
expect(result.changedPixelRatio).toBe(0)
expect(result.landmarkDelta.x).toBeCloseTo(0.2)
expect(result.landmarkDelta.y).toBeCloseTo(0.004)
})
it('rejects a material change below the status bar', async () => {
const native = createScreenshot(100, 100)
const hosted = createScreenshot(100, 100)
paintRows(hosted, 20, 40, 255)
const paths = await writeScreenshots(native, hosted)
await expect(
assertHostedIosScreenshotParity({
...paths,
nativeLandmark: { x: 0.5, y: 0.1 },
hostedLandmark: { x: 0.5, y: 0.1 }
})
).rejects.toThrow('exceeded parity budgets')
})
it('tolerates local text-rasterization shifts without hiding the raw difference', async () => {
const native = createScreenshot(200, 200)
const hosted = createScreenshot(200, 200)
paintVerticalStripes(native, 20, 0)
paintVerticalStripes(hosted, 20, 4)
const paths = await writeScreenshots(native, hosted)
const result = await assertHostedIosScreenshotParity({
...paths,
nativeLandmark: { x: 0.5, y: 0.1 },
hostedLandmark: { x: 0.5, y: 0.1 }
})
expect(result.rawChangedPixelRatio).toBeGreaterThan(0.1)
expect(result.changedPixelRatio).toBe(0)
expect(result.neighborhoodRadiusPixels).toBe(16)
})
it('rejects a safe-area landmark shift even when the pixels match', async () => {
const screenshot = createScreenshot(100, 100)
const paths = await writeScreenshots(screenshot, screenshot)
await expect(
assertHostedIosScreenshotParity({
...paths,
nativeLandmark: { x: 0.5, y: 0.1 },
hostedLandmark: { x: 0.5, y: 0.11 }
})
).rejects.toThrow('exceeded parity budgets')
})
it('supports pixel-only evidence when rotated WebView accessibility is unavailable', async () => {
const screenshot = createScreenshot(100, 100)
const paths = await writeScreenshots(screenshot, screenshot)
await expect(
assertHostedIosScreenshotParity({
...paths,
nativeLandmark: null,
hostedLandmark: null
})
).resolves.toMatchObject({ changedPixelRatio: 0, landmarkDelta: null })
})
})
function createScreenshot(width: number, height: number) {
const png = new PNG({ width, height })
png.data.fill(0)
return png
}
function paintRows(png: PNG, firstRow: number, lastRow: number, value: number) {
for (let y = firstRow; y <= lastRow; y++) {
for (let x = 0; x < png.width; x++) {
const offset = (y * png.width + x) * 4
png.data[offset] = value
png.data[offset + 1] = value
png.data[offset + 2] = value
png.data[offset + 3] = 255
}
}
}
function paintVerticalStripes(png: PNG, firstRow: number, xOffset: number) {
for (let x = xOffset; x < png.width; x += 12) {
for (let y = firstRow; y < png.height; y++) {
paintPixel(png, x, y, 255)
paintPixel(png, x + 1, y, 128)
}
}
}
function paintPixel(png: PNG, x: number, y: number, value: number) {
if (x >= png.width) {
return
}
const offset = (y * png.width + x) * 4
png.data[offset] = value
png.data[offset + 1] = value
png.data[offset + 2] = value
png.data[offset + 3] = 255
}
async function writeScreenshots(native: PNG, hosted: PNG) {
const directory = await mkdtemp(path.join(os.tmpdir(), 'orca-hosted-ios-parity-'))
const nativeScreenshot = path.join(directory, 'native.png')
const hostedScreenshot = path.join(directory, 'hosted.png')
await Promise.all([
writeFile(nativeScreenshot, PNG.sync.write(native)),
writeFile(hostedScreenshot, PNG.sync.write(hosted))
])
return { nativeScreenshot, hostedScreenshot }
}
@@ -1,54 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { buildAndInstallHostedIosSimulatorApp } from '../../scripts/hosted-ios-simulator-app-build.mjs'
describe('hosted iOS simulator app build', () => {
it('removes persisted app state before installing the exact build', async () => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '', stderr: '' })
const appPath = await buildAndInstallHostedIosSimulatorApp({
deviceUdid: 'simulator-1',
worktree: '/worktree',
runCommand
})
expect(runCommand).toHaveBeenNthCalledWith(2, 'xcrun', [
'simctl',
'uninstall',
'simulator-1',
'com.stably.orca.mobile'
])
expect(runCommand).toHaveBeenNthCalledWith(
3,
'xcrun',
['simctl', 'install', 'simulator-1', appPath],
{ maxBuffer: 4 * 1024 * 1024 }
)
expect(runCommand).toHaveBeenNthCalledWith(4, 'xcrun', [
'simctl',
'spawn',
'simulator-1',
'defaults',
'write',
'com.stably.orca.mobile',
'EXDevMenuShowFloatingActionButton',
'-bool',
'false'
])
})
it('continues when no prior app is installed', async () => {
const runCommand = vi
.fn()
.mockResolvedValueOnce({ stdout: '', stderr: '' })
.mockRejectedValueOnce(new Error('not installed'))
.mockResolvedValueOnce({ stdout: '', stderr: '' })
await expect(
buildAndInstallHostedIosSimulatorApp({
deviceUdid: 'simulator-1',
worktree: '/worktree',
runCommand
})
).resolves.toContain('Orca.app')
})
})
@@ -1,72 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
buildAndInstall: vi.fn(),
install: vi.fn()
}))
vi.mock('../../scripts/hosted-ios-simulator-app-build.mjs', () => ({
buildAndInstallHostedIosSimulatorApp: mocks.buildAndInstall,
hostedIosSimulatorAppPath: (worktree: string) => `${worktree}/Orca.app`,
installHostedIosSimulatorApp: mocks.install
}))
import { hostedIosSimulatorAppPreparation } from '../../scripts/hosted-ios-simulator-app-preparation.mjs'
describe('hosted iOS simulator app preparation', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.buildAndInstall.mockResolvedValue('/worktree/Orca.app')
mocks.install.mockResolvedValue(undefined)
})
it('reuses the installed app without reinstalling it', async () => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '/installed/Orca.app' })
const preparation = hostedIosSimulatorAppPreparation({
deviceUdid: 'simulator',
reuseNativeInstall: true,
skipNativeBuild: false,
worktree: '/worktree',
runCommand
})
await expect(preparation.run()).resolves.toBe('/installed/Orca.app')
expect(preparation.label).toBe('existing native simulator app')
expect(runCommand).toHaveBeenCalledWith('xcrun', [
'simctl',
'get_app_container',
'simulator',
'com.stably.orca.mobile',
'app'
])
expect(mocks.install).not.toHaveBeenCalled()
})
it('retains the cached reinstall and build paths', async () => {
const cached = hostedIosSimulatorAppPreparation({
deviceUdid: 'simulator',
reuseNativeInstall: false,
skipNativeBuild: true,
worktree: '/worktree'
})
const build = hostedIosSimulatorAppPreparation({
deviceUdid: 'simulator',
reuseNativeInstall: false,
skipNativeBuild: false,
worktree: '/worktree'
})
await expect(cached.run()).resolves.toBe('/worktree/Orca.app')
await expect(build.run()).resolves.toBe('/worktree/Orca.app')
expect(mocks.install).toHaveBeenCalledWith({
deviceUdid: 'simulator',
appPath: '/worktree/Orca.app',
runCommand: expect.any(Function)
})
expect(mocks.buildAndInstall).toHaveBeenCalledWith({
deviceUdid: 'simulator',
worktree: '/worktree',
runCommand: expect.any(Function)
})
})
})
@@ -1,531 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
captureParityScreen: vi.fn(),
readBridgeErrors: vi.fn(),
navigateRoute: vi.fn(),
parityEvidence: vi.fn(),
readControlPoint: vi.fn(),
readState: vi.fn(),
tapAccessibilityControl: vi.fn(),
tapPoint: vi.fn(),
startBridgeErrorObservation: vi.fn(),
waitForDocument: vi.fn()
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
readHostedWebViewState: mocks.readState,
waitForVisibleHostedWebView: mocks.waitForDocument
}))
vi.mock('../../scripts/hosted-webview-control-point.mjs', () => ({
readHostedWebViewControlPoint: mocks.readControlPoint
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
tapHostedIosAccessibilityControl: mocks.tapAccessibilityControl,
tapHostedIosPoint: mocks.tapPoint
}))
vi.mock('../../scripts/hosted-webview-route-navigation.mjs', () => ({
navigateHostedWebViewRoute: mocks.navigateRoute
}))
vi.mock('../../scripts/hosted-webview-bridge-error-observation.mjs', () => ({
readHostedWebViewBridgeErrors: mocks.readBridgeErrors,
startHostedWebViewBridgeErrorObservation: mocks.startBridgeErrorObservation
}))
vi.mock('../../scripts/hosted-ios-source-control-review-parity.mjs', () => ({
captureHostedSourceControlReviewScreen: mocks.captureParityScreen,
HEADLESS_REVIEW_OPEN_ERROR: 'renderer_unavailable',
sourceControlReviewParityEvidence: mocks.parityEvidence
}))
import { verifyHostedSourceControlReviewJourney } from '../../scripts/hosted-ios-source-control-review-journey.mjs'
describe('hosted iOS Source Control and Review journey', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.readControlPoint
.mockResolvedValueOnce({ x: 0.4, y: 0.2 })
.mockResolvedValueOnce({ x: 0.5, y: 0.4 })
mocks.navigateRoute.mockResolvedValue(undefined)
mocks.captureParityScreen
.mockResolvedValueOnce({ screenshot: '/tmp/hosted-source-control.png' })
.mockResolvedValueOnce({ screenshot: '/tmp/hosted-review.png' })
mocks.parityEvidence
.mockReturnValueOnce({ screen: 'source-control' })
.mockReturnValueOnce({ screen: 'review' })
mocks.tapAccessibilityControl.mockResolvedValue(undefined)
mocks.tapPoint.mockResolvedValue(undefined)
mocks.startBridgeErrorObservation.mockResolvedValue(undefined)
mocks.readBridgeErrors.mockResolvedValue([])
mocks.waitForDocument
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace'
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/session/workspace'
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace'
})
mocks.readState
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control Changes Pull Request Commits Create pull request 128 on branch',
labels: ['Refresh source control', 'Open changed file mobile/app/index.tsx']
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace',
bodyText: 'reviewed',
labels: ['Back', 'Open review actions']
})
})
it('opens the unchanged Session diff flow and standalone Review route', async () => {
const emulator = { deviceUdid: 'simulator' }
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
await expect(
verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator,
sessionDocument,
timeoutMs: 30_000
})
).resolves.toEqual({
sourceControlRoute: 'orca-mobile-web://build/h/host/source-control/workspace',
sourceControlSegments: ['Changes', 'Pull Request', 'Commits'],
sessionDiffRoute: 'orca-mobile-web://build/h/host/session/workspace',
reviewOpen: { headless: false, native: null },
reviewRoute: 'orca-mobile-web://build/h/host/review/workspace',
reviewControls: ['Back', 'Open review actions']
})
expect(mocks.readControlPoint).toHaveBeenNthCalledWith(
1,
sessionDocument,
'Open source control'
)
expect(mocks.tapAccessibilityControl).toHaveBeenNthCalledWith(
1,
emulator,
'Open source control',
5_000
)
expect(mocks.tapAccessibilityControl).toHaveBeenNthCalledWith(
2,
emulator,
'Open changed file mobile/app/index.tsx',
5_000
)
expect(mocks.tapPoint).not.toHaveBeenCalled()
expect(mocks.navigateRoute).toHaveBeenCalledWith(
{ href: 'orca-mobile-web://build/h/host/session/workspace' },
'/h/host/review/workspace?scope=all'
)
})
it('accepts a platform-specific native tap implementation', async () => {
const tapPoint = vi.fn().mockResolvedValue(undefined)
const inspectChangedContent = vi.fn().mockResolvedValue(undefined)
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { adb: '/sdk/adb' },
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
expectedSessionDiffText: '3 tabs',
inspectChangedContent,
timeoutMs: 30_000,
tapPoint
})
expect(tapPoint).toHaveBeenCalledTimes(2)
expect(tapPoint.mock.calls.map((call) => call[2])).toEqual([
'Open source control',
'Open changed file mobile/app/index.tsx'
])
expect(inspectChangedContent.mock.calls.map((call) => call[0].phase)).toEqual([
'sourceControl',
'sessionDiff',
'review'
])
expect(mocks.waitForDocument).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ expectedText: '3 tabs' })
)
})
it('captures strict Source Control and Review parity when native baselines are provided', async () => {
const result = await verifyHostedSourceControlReviewJourney({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
nativeBaselines: {
sourceControl: {
changedFileLabel: 'Open changed file mobile/app/index.tsx',
pullRequestState: {
kind: 'create',
label: 'Create pull request'
},
screenshot: '/tmp/native-source-control.png'
},
review: { screenshot: '/tmp/native-review.png' }
},
runtimeDirectory: '/tmp/parity',
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
expect(mocks.captureParityScreen).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
deviceUdid: 'simulator',
screenshotName: 'hosted-source-control-portrait.png',
title: 'Source Control'
})
)
expect(mocks.captureParityScreen).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
deviceUdid: 'simulator',
screenshotName: 'hosted-review-portrait.png',
title: 'Changes'
})
)
expect(result.parityFixture).toEqual({
sourceControl: { screen: 'source-control' },
review: { screen: 'review' }
})
})
it('settles parity against the existing pull request captured by native', async () => {
const pullRequestLabel = 'Pull request #13386, Draft, No checks. Open pull request.'
mocks.readState
.mockReset()
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control Changes Pull Request Commits #13386 Draft 128 on branch',
labels: [
'Refresh source control',
pullRequestLabel,
'Open changed file mobile/app/index.tsx'
]
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace',
bodyText: 'reviewed',
labels: ['Back', 'Open review actions']
})
await expect(
verifyHostedSourceControlReviewJourney({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
nativeBaselines: {
sourceControl: {
changedFileLabel: 'Open changed file mobile/app/index.tsx',
pullRequestState: { kind: 'ready', label: pullRequestLabel, number: '13386' },
screenshot: '/tmp/native-source-control.png'
},
review: { screenshot: '/tmp/native-review.png' }
},
runtimeDirectory: '/tmp/parity',
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
).resolves.toMatchObject({ sourceControlSegments: ['Changes', 'Pull Request', 'Commits'] })
})
it('opens the exact changed file captured by the native baseline', async () => {
const nativeChangedFileLabel = 'Open changed file mobile/src/mobile-web/bridge.ts'
mocks.readState
.mockReset()
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control Changes Pull Request Commits Create pull request 128 on branch',
labels: [
'Refresh source control',
'Open changed file mobile/app/index.tsx',
nativeChangedFileLabel
]
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace',
bodyText: 'reviewed',
labels: ['Back', 'Open review actions']
})
await verifyHostedSourceControlReviewJourney({
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
nativeBaselines: {
sourceControl: {
changedFileLabel: nativeChangedFileLabel,
pullRequestState: { kind: 'create', label: 'Create pull request' },
screenshot: '/tmp/native-source-control.png'
},
review: { screenshot: '/tmp/native-review.png' }
},
runtimeDirectory: '/tmp/parity',
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
timeoutMs: 30_000
})
expect(mocks.tapAccessibilityControl).toHaveBeenNthCalledWith(
2,
{ deviceUdid: 'simulator' },
nativeChangedFileLabel,
5_000
)
})
it('falls back to measured points when WebKit omits accessibility descendants', async () => {
mocks.tapAccessibilityControl.mockRejectedValue(new Error('missing descendant'))
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
expect(mocks.tapPoint).toHaveBeenNthCalledWith(1, expect.anything(), {
x: 0.4,
y: 0.2
})
expect(mocks.tapPoint).toHaveBeenNthCalledWith(2, expect.anything(), {
x: 0.5,
y: 0.4
})
})
it('uses measured native taps for duplicated Source Control segment labels', async () => {
const sourceState = {
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control Changes Pull Request Commits Create pull request 128 on branch',
labels: ['Refresh source control', 'Open changed file mobile/app/index.tsx']
}
mocks.readControlPoint
.mockReset()
.mockResolvedValueOnce({ x: 0.4, y: 0.2 })
.mockResolvedValueOnce({ x: 0.6, y: 0.3 })
.mockResolvedValueOnce({ x: 0.3, y: 0.3 })
.mockResolvedValueOnce({ x: 0.5, y: 0.4 })
mocks.readState
.mockReset()
.mockResolvedValueOnce(sourceState)
.mockResolvedValueOnce(sourceState)
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace',
bodyText: 'reviewed',
labels: ['Back', 'Open review actions']
})
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
inspectProviderContent: vi.fn().mockResolvedValue(undefined),
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
expect(mocks.tapPoint).toHaveBeenNthCalledWith(1, expect.anything(), {
x: 0.6,
y: 0.3
})
expect(mocks.tapPoint).toHaveBeenNthCalledWith(2, expect.anything(), {
x: 0.3,
y: 0.3
})
})
it('transforms only the measured fallback while retaining semantic activation', async () => {
const transformPoint = vi.fn((point) => ({ ...point, y: point.y + 0.1 }))
mocks.tapAccessibilityControl.mockRejectedValue(new Error('missing descendant'))
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000,
transformPoint
})
expect(mocks.tapAccessibilityControl).toHaveBeenCalledTimes(2)
expect(mocks.tapPoint.mock.calls[0][1]).toMatchObject({ x: 0.4 })
expect(mocks.tapPoint.mock.calls[0][1].y).toBeCloseTo(0.3)
expect(mocks.tapPoint.mock.calls[1][1]).toEqual({ x: 0.5, y: 0.5 })
})
it('falls back to a measured point when an accessibility tap silently misses', async () => {
mocks.readControlPoint
.mockReset()
.mockResolvedValueOnce({ x: 0.4, y: 0.2 })
.mockResolvedValueOnce({ x: 0.5, y: 0.4 })
.mockResolvedValueOnce({ x: 0.5, y: 0.4 })
mocks.waitForDocument
.mockReset()
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace'
})
.mockRejectedValueOnce(new Error('route unchanged'))
.mockRejectedValueOnce(new Error('route unchanged'))
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/session/workspace'
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace'
})
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
expect(mocks.tapAccessibilityControl).toHaveBeenCalledTimes(2)
expect(mocks.tapPoint).toHaveBeenCalledWith(expect.anything(), {
x: 0.5,
y: 0.4
})
})
it('waits for tab state after the route changes', async () => {
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
mocks.waitForDocument
.mockReset()
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace'
})
.mockRejectedValueOnce(new Error('tab count pending'))
.mockResolvedValueOnce(sessionDocument)
.mockResolvedValueOnce(sessionDocument)
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace'
})
await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
sessionDocument: {
href: 'orca-mobile-web://build/h/host/session/workspace'
},
timeoutMs: 30_000
})
expect(mocks.tapAccessibilityControl).toHaveBeenCalledTimes(2)
expect(mocks.tapPoint).not.toHaveBeenCalled()
expect(mocks.waitForDocument).toHaveBeenNthCalledWith(
3,
expect.objectContaining({ expectedText: '' })
)
expect(mocks.waitForDocument).toHaveBeenNthCalledWith(
4,
expect.objectContaining({ expectedText: '2 tabs' })
)
})
it('records the headless host outcome when the diff route never opens', async () => {
// Why: `orca serve --mobile-pairing` registers no renderer, so files.openDiff
// answers renderer_unavailable. The claim is parity with native on this host.
mocks.waitForDocument.mockReset()
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes }) => {
if (expectedHrefIncludes === '/session/') {
return Promise.reject(new Error('Session diff route did not open'))
}
return Promise.resolve({
href: `orca-mobile-web://build/h/host${expectedHrefIncludes ?? '/review/'}workspace`
})
})
mocks.readBridgeErrors.mockResolvedValue([
{ capability: 'sourceControl', operation: 'reviewOpen', code: 'host_error', retryable: true }
])
mocks.readState.mockReset()
mocks.readState
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control Changes Pull Request Commits Create pull request 128 on branch',
labels: ['Refresh source control', 'Open changed file mobile/app/index.tsx']
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/source-control/workspace',
bodyText: 'Source Control\nSource control action failed\nStage All'
})
.mockResolvedValueOnce({
href: 'orca-mobile-web://build/h/host/review/workspace',
bodyText: 'reviewed',
labels: ['Back', 'Open review actions']
})
const result = await verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
nativeReviewOpen: { headless: 'renderer_unavailable' },
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
timeoutMs: 30_000
})
expect(result.sessionDiffRoute).toBeNull()
expect(result.reviewOpen).toEqual({
headless: 'renderer_unavailable',
native: 'renderer_unavailable',
bridgeError: {
capability: 'sourceControl',
operation: 'reviewOpen',
code: 'host_error',
retryable: true
},
visibleMessage: 'Source control action failed'
})
// The standalone Review route still opens, from the Source Control document.
expect(result.reviewRoute).toBe('orca-mobile-web://build/h/host/review/workspace')
})
it('fails when the hybrid outcome diverges from what native did on the same host', async () => {
mocks.waitForDocument.mockReset()
mocks.waitForDocument.mockImplementation(({ expectedHrefIncludes }) => {
if (expectedHrefIncludes === '/session/') {
return Promise.reject(new Error('Session diff route did not open'))
}
return Promise.resolve({
href: `orca-mobile-web://build/h/host${expectedHrefIncludes ?? '/review/'}workspace`
})
})
mocks.readBridgeErrors.mockResolvedValue([
{ capability: 'sourceControl', operation: 'reviewOpen', code: 'host_error', retryable: true }
])
await expect(
verifyHostedSourceControlReviewJourney({
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
nativeReviewOpen: { headless: false },
sessionDocument: { href: 'orca-mobile-web://build/h/host/session/workspace' },
timeoutMs: 30_000
})
).rejects.toThrow('Session-origin reviewOpen diverged')
})
})
@@ -1,203 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
captureScreenshot: vi.fn(),
compareScreenshots: vi.fn(),
dismissDeveloperMenu: vi.fn(),
longPressControlByPrefix: vi.fn(),
readLabels: vi.fn(),
readTextPoint: vi.fn(),
tapControl: vi.fn(),
tapControlByPrefix: vi.fn(),
waitForControl: vi.fn(),
waitForControlByPrefix: vi.fn(),
waitForControlEndingWith: vi.fn(),
waitForControlMatching: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: mocks.captureScreenshot
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: mocks.dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
readHostedIosAccessibilityLabels: mocks.readLabels,
tapHostedIosAccessibilityControl: mocks.tapControl,
tapHostedIosAccessibilityControlByLabelPrefix: mocks.tapControlByPrefix,
waitForHostedIosAccessibilityControl: mocks.waitForControl,
waitForHostedIosAccessibilityControlByLabelPrefix: mocks.waitForControlByPrefix,
waitForHostedIosAccessibilityControlEndingWith: mocks.waitForControlEndingWith,
waitForHostedIosAccessibilityControlMatching: mocks.waitForControlMatching
}))
vi.mock('../../scripts/hosted-ios-emulator-long-press.mjs', () => ({
longPressHostedIosAccessibilityControlByLabelPrefix: mocks.longPressControlByPrefix
}))
vi.mock('../../scripts/hosted-ios-screenshot-parity.mjs', () => ({
assertHostedIosScreenshotParity: mocks.compareScreenshots
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
readHostedWebViewTextPoint: mocks.readTextPoint
}))
import {
captureHostedSourceControlReviewScreen,
captureNativeSourceControlReviewBaselines,
HEADLESS_REVIEW_OPEN_ERROR
} from '../../scripts/hosted-ios-source-control-review-parity.mjs'
describe('hosted iOS Source Control and Review parity', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.captureScreenshot.mockImplementation((_command, _args, callback) =>
callback(null, '', '')
)
mocks.compareScreenshots.mockResolvedValue({ changedPixelRatio: 0.01 })
mocks.longPressControlByPrefix.mockResolvedValue({ x: 0.5, y: 0.4 })
mocks.readLabels.mockResolvedValue([
'Open source control',
'Source Control',
'Back to session',
HEADLESS_REVIEW_OPEN_ERROR
])
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.tapControl.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.tapControlByPrefix.mockResolvedValue({ x: 0.5, y: 0.4 })
mocks.waitForControl.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControlByPrefix.mockResolvedValue({ x: 0.5, y: 0.4 })
mocks.waitForControlEndingWith.mockResolvedValue({ x: 0.5, y: 0.4 })
mocks.waitForControlMatching
.mockResolvedValueOnce({
label: 'Open changed file mobile/app/index.tsx',
value: '',
x: 0.5,
y: 0.5
})
.mockResolvedValueOnce({
label: 'Pull request #13386, Draft, No checks. Open pull request.',
value: '',
x: 0.5,
y: 0.3
})
.mockResolvedValue({
label: 'Open changed file mobile/app/index.tsx',
value: '',
x: 0.5,
y: 0.5
})
})
it('captures the real host-origin Source Control and standalone Review path', async () => {
const baselines = await captureNativeSourceControlReviewBaselines({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.longPressControlByPrefix).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'mobile-rearch',
30_000,
undefined,
'Source Control'
)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Source Control',
30_000
)
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Open changed file mobile/app/index.tsx',
30_000
)
expect(baselines).toEqual({
review: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-review-portrait.png'
},
// Why: the session-origin probe records what the paired host answers, so the
// hosted journey can assert parity instead of asserting a failure.
sessionOriginReviewOpen: {
changedFileLabel: 'Open changed file mobile/app/index.tsx',
headless: HEADLESS_REVIEW_OPEN_ERROR
},
sourceControl: {
changedFileLabel: 'Open changed file mobile/app/index.tsx',
pullRequestState: {
kind: 'ready',
label: 'Pull request #13386, Draft, No checks. Open pull request.',
number: '13386'
},
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-source-control-portrait.png'
}
})
})
it('uses value-backed accessibility text for the changed file and pull request', async () => {
mocks.waitForControlMatching
.mockReset()
.mockResolvedValue({
label: 'Changed file',
value: 'Open changed file mobile/src/mobile-web/bridge.ts',
x: 0.5,
y: 0.5
})
.mockResolvedValueOnce({
label: 'Changed file',
value: 'Open changed file mobile/src/mobile-web/bridge.ts',
x: 0.5,
y: 0.5
})
.mockResolvedValueOnce({
label: '',
value: 'Pull request #13386, Draft, No checks. Open pull request.',
x: 0.5,
y: 0.3
})
const baselines = await captureNativeSourceControlReviewBaselines({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(baselines.sourceControl).toMatchObject({
changedFileLabel: 'Open changed file mobile/src/mobile-web/bridge.ts',
pullRequestState: { kind: 'ready', number: '13386' }
})
expect(mocks.tapControl).toHaveBeenCalledWith(
{ deviceUdid: 'simulator' },
'Open changed file mobile/src/mobile-web/bridge.ts',
30_000
)
})
it('compares a hosted route against its native screenshot and title landmark', async () => {
const capture = await captureHostedSourceControlReviewScreen({
deviceUdid: 'simulator',
document: { href: 'orca-mobile-web://build/h/host/review/worktree' },
nativeBaseline: {
screenTitlePoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-review-portrait.png'
},
runtimeDirectory: '/tmp/parity',
screenshotName: 'hosted-review-portrait.png',
title: 'Changes',
timeoutMs: 30_000
})
expect(capture.screenshotParity).toEqual({ changedPixelRatio: 0.01 })
expect(mocks.compareScreenshots).toHaveBeenCalledWith({
hostedLandmark: { x: 0.2, y: 0.1 },
hostedScreenshot: '/tmp/parity/hosted-review-portrait.png',
nativeLandmark: { x: 0.2, y: 0.1 },
nativeScreenshot: '/tmp/parity/native-review-portrait.png'
})
})
})
@@ -1,247 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { PNG } from 'pngjs'
import {
copyHostedIosPhotoFixtureToClipboard,
stageFreshHostedIosPhotoFixture,
pngPixelIdentity,
verifyHostedIosTerminalClipboardImagePaste
} from '../../scripts/hosted-ios-terminal-clipboard-image-paste.mjs'
const uploadedPath =
'/var/folders/x/y/T/orca-paste-1780000000000-00000000-0000-4000-8000-000000000000.png'
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
const args = {
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
sessionDocument,
terminalHandle: 'terminal-handle',
timeoutMs: 30_000,
worktree: '/repo/mobile-rearch'
}
const fixtureBytes = createPng([18, 22, 41, 255])
const fixturePixelIdentity = pngPixelIdentity(fixtureBytes)
describe('hosted iOS terminal clipboard image paste', () => {
it('normalizes Photos to its library and copies the latest visible photo', async () => {
const runCommand = vi.fn().mockResolvedValue({ stdout: '', stderr: '' })
const tapControl = vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 })
const tapLastControl = vi.fn().mockResolvedValue({ x: 0.16, y: 0.52 })
const tapPoint = vi.fn()
const waitForControl = vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 })
const waitForMatch = vi.fn().mockResolvedValue({ label: 'Share', x: 0.1, y: 0.9 })
await expect(
copyHostedIosPhotoFixtureToClipboard(
{
deviceUdid: 'simulator',
emulator: args.emulator,
fixturePath: '/repo/mobile/assets/favicon.png',
timeoutMs: args.timeoutMs
},
{
readFixture: vi.fn().mockResolvedValue(fixtureBytes),
runCommand,
stageFixture: vi.fn().mockResolvedValue('/repo/mobile/assets/favicon.png'),
tapControl,
tapLastControl,
tapPoint,
waitForControl,
waitForMatch
}
)
).resolves.toMatchObject({
fixtureName: 'favicon.png',
fixturePixelIdentity,
photoPoint: { x: 0.16, y: 0.52 }
})
expect(runCommand).toHaveBeenNthCalledWith(1, 'xcrun', [
'simctl',
'addmedia',
'simulator',
'/repo/mobile/assets/favicon.png'
])
expect(runCommand).toHaveBeenNthCalledWith(2, 'xcrun', [
'simctl',
'launch',
'simulator',
'com.apple.mobileslideshow'
])
expect(tapControl.mock.calls.map(([, label]) => label)).toEqual([
'Back',
'Share',
'Copy Photo',
'Return to Orca'
])
expect(tapLastControl).toHaveBeenCalledWith(args.emulator, 'Photo', args.timeoutMs)
expect(tapPoint).not.toHaveBeenCalled()
})
it('stages a freshly written copy so the fixture is the newest library photo', async () => {
const writeFixture = vi.fn().mockResolvedValue(undefined)
await expect(
stageFreshHostedIosPhotoFixture('/repo/mobile/assets/favicon.png', {
createDirectory: vi.fn().mockResolvedValue('/tmp/staged'),
readFixture: vi.fn().mockResolvedValue(fixtureBytes),
writeFixture
})
).resolves.toBe('/tmp/staged/favicon.png')
expect(writeFixture).toHaveBeenCalledWith('/tmp/staged/favicon.png', fixtureBytes)
})
it('dismisses first-launch Photos surfaces before copying', async () => {
const waitForMatch = vi
.fn()
.mockResolvedValueOnce({ label: 'Continue', x: 0.5, y: 0.9 })
.mockResolvedValueOnce({ label: 'Don’t Allow', x: 0.3, y: 0.6 })
.mockResolvedValueOnce({ label: 'Continue', x: 0.5, y: 0.9 })
.mockResolvedValueOnce({ label: 'Select', x: 0.8, y: 0.1 })
const tapPoint = vi.fn().mockResolvedValue(undefined)
await copyHostedIosPhotoFixtureToClipboard(
{
deviceUdid: 'simulator',
emulator: args.emulator,
fixturePath: '/repo/mobile/assets/favicon.png',
timeoutMs: args.timeoutMs
},
{
readFixture: vi.fn().mockResolvedValue(fixtureBytes),
runCommand: vi.fn().mockResolvedValue({ stdout: '', stderr: '' }),
stageFixture: vi.fn().mockResolvedValue('/repo/mobile/assets/favicon.png'),
tapControl: vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 }),
tapLastControl: vi.fn().mockResolvedValue({ x: 0.16, y: 0.52 }),
tapPoint,
waitForControl: vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 }),
waitForMatch
}
)
expect(tapPoint).toHaveBeenNthCalledWith(1, args.emulator, {
label: 'Continue',
x: 0.5,
y: 0.9
})
expect(tapPoint).toHaveBeenNthCalledWith(2, args.emulator, {
label: 'Don’t Allow',
x: 0.3,
y: 0.6
})
expect(tapPoint).toHaveBeenNthCalledWith(3, args.emulator, {
label: 'Continue',
x: 0.5,
y: 0.9
})
})
it('retries when Photos onboarding appears after the first photo tap', async () => {
const waitForMatch = vi
.fn()
.mockResolvedValueOnce({ label: 'Select', x: 0.8, y: 0.1 })
.mockResolvedValueOnce({ label: 'Continue', x: 0.5, y: 0.9 })
.mockResolvedValueOnce({ label: 'Select', x: 0.8, y: 0.1 })
const tapLastControl = vi.fn().mockResolvedValue({ x: 0.16, y: 0.52 })
const tapPoint = vi.fn().mockResolvedValue(undefined)
const waitForControl = vi
.fn()
.mockRejectedValueOnce(new Error('lazy onboarding'))
.mockResolvedValue({ x: 0.5, y: 0.5 })
await copyHostedIosPhotoFixtureToClipboard(
{
deviceUdid: 'simulator',
emulator: args.emulator,
fixturePath: '/repo/mobile/assets/favicon.png',
timeoutMs: args.timeoutMs
},
{
readFixture: vi.fn().mockResolvedValue(fixtureBytes),
runCommand: vi.fn().mockResolvedValue({ stdout: '', stderr: '' }),
stageFixture: vi.fn().mockResolvedValue('/repo/mobile/assets/favicon.png'),
tapControl: vi.fn().mockResolvedValue({ x: 0.5, y: 0.5 }),
tapLastControl,
tapPoint,
waitForControl,
waitForMatch
}
)
expect(tapLastControl).toHaveBeenCalledTimes(2)
expect(tapPoint).toHaveBeenCalledWith(args.emulator, {
label: 'Continue',
x: 0.5,
y: 0.9
})
})
it('requires pixel-identical shell upload and terminal-only path exposure', async () => {
const beforeTerminal = ['ORCA_HOSTED_CLIPBOARD_TEXT_PASTE']
const afterTerminal = [...beforeTerminal, uploadedPath]
const operations = {
allowPaste: vi.fn().mockResolvedValue('allowed'),
copyFixture: vi.fn().mockResolvedValue({
copyPoint: { x: 0.15, y: 0.66 },
fixtureName: 'favicon.png',
fixturePixelIdentity
}),
readState: vi.fn().mockResolvedValue({ bodyText: 'Mobile Emulator' }),
readTerminal: vi
.fn()
.mockResolvedValueOnce(beforeTerminal)
.mockResolvedValueOnce(afterTerminal),
readUploadedFile: vi.fn().mockResolvedValue(fixtureBytes),
tapControl: vi.fn().mockResolvedValue({ x: 0.3, y: 0.9 }),
waitForDocument: vi.fn().mockResolvedValue(sessionDocument)
}
await expect(verifyHostedIosTerminalClipboardImagePaste(args, operations)).resolves.toEqual({
evidence: {
copyPoint: { x: 0.15, y: 0.66 },
fixtureName: 'favicon.png',
height: 1,
pasteControlPoint: { x: 0.3, y: 0.9 },
pastePermissionPrompt: 'allowed',
pixelSha256: fixturePixelIdentity.sha256,
privilegedPageMarkers: 'absent',
route: sessionDocument.href,
size: fixtureBytes.byteLength,
terminalPathInjected: true,
width: 1
},
sessionDocument
})
expect(operations.readUploadedFile).toHaveBeenCalledWith(uploadedPath)
})
it('rejects a clipboard upload with different decoded pixels', async () => {
const operations = {
allowPaste: vi.fn().mockResolvedValue('not-shown'),
copyFixture: vi.fn().mockResolvedValue({
copyPoint: { x: 0.15, y: 0.66 },
fixtureName: 'favicon.png',
fixturePixelIdentity
}),
readState: vi.fn().mockResolvedValue({ bodyText: 'Mobile Emulator' }),
readTerminal: vi.fn().mockResolvedValueOnce([]).mockResolvedValueOnce([uploadedPath]),
readUploadedFile: vi.fn().mockResolvedValue(createPng([255, 0, 0, 255])),
tapControl: vi.fn().mockResolvedValue({ x: 0.3, y: 0.9 }),
waitForDocument: vi.fn().mockResolvedValue(sessionDocument)
}
await expect(verifyHostedIosTerminalClipboardImagePaste(args, operations)).rejects.toThrow(
'did not preserve the fixture pixels'
)
})
})
function createPng(rgba: [number, number, number, number]): Buffer {
const png = new PNG({ height: 1, width: 1 })
png.data.set(rgba)
return PNG.sync.write(png)
}
@@ -1,162 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
activateControl: vi.fn()
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
activateHostedWebViewControl: mocks.activateControl,
waitForVisibleHostedWebView: vi.fn()
}))
import {
verifyHostedIosTerminalClipboardPaste,
writeHostedIosSimulatorPasteboard
} from '../../scripts/hosted-ios-terminal-clipboard-paste.mjs'
describe('hosted iOS terminal clipboard paste', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('uses the unchanged Paste control and requires the marker in the Desktop terminal', async () => {
const workspaceDocument = { href: 'orca-mobile-web://build/h/host' }
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
const writePasteboard = vi.fn().mockResolvedValue(undefined)
const activateWorkspace = vi
.fn()
.mockImplementation(async (_document, _workspace, _activate, _timeout, waitForWorkspace) => {
await waitForWorkspace()
})
const waitForDocument = vi
.fn()
.mockResolvedValueOnce(workspaceDocument)
.mockResolvedValueOnce(sessionDocument)
const tapControl = vi
.fn()
.mockResolvedValueOnce({ x: 0.75, y: 0.9 })
.mockResolvedValueOnce({ x: 0.5, y: 0.6 })
const readTerminal = vi.fn().mockResolvedValue('terminal-handle')
await expect(
verifyHostedIosTerminalClipboardPaste(
{
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
timeoutMs: 30_000,
workspaceDocument,
worktree: '/repo/mobile-rearch'
},
{
activateWorkspace,
readTerminal,
tapControl,
waitForDocument,
writePasteboard
}
)
).resolves.toEqual({
evidence: {
activationAttempts: 1,
marker: 'ORCA_HOSTED_CLIPBOARD_TEXT_PASTE',
pasteControlPoint: { x: 0.75, y: 0.9 },
pastePermissionPrompt: 'allowed',
route: sessionDocument.href,
terminalHandle: 'terminal-handle'
},
sessionDocument
})
expect(writePasteboard).toHaveBeenCalledWith('simulator', 'ORCA_HOSTED_CLIPBOARD_TEXT_PASTE')
expect(activateWorkspace).toHaveBeenCalledWith(
workspaceDocument,
'mobile-rearch',
mocks.activateControl,
30_000,
expect.any(Function)
)
expect(tapControl).toHaveBeenNthCalledWith(1, { deviceUdid: 'simulator' }, 'Paste', 30_000)
expect(tapControl).toHaveBeenNthCalledWith(2, { deviceUdid: 'simulator' }, 'Allow Paste', 3_000)
expect(readTerminal).toHaveBeenCalledWith({
marker: 'ORCA_HOSTED_CLIPBOARD_TEXT_PASTE',
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
timeoutMs: 10_000,
worktree: '/repo/mobile-rearch'
})
})
it('writes text to the selected simulator pasteboard', async () => {
const stdin = { end: vi.fn() }
const child = {
once: vi.fn((event, listener) => {
if (event === 'exit') {
listener(0)
}
}),
stderr: { on: vi.fn(), setEncoding: vi.fn() },
stdin
}
const spawnProcess = vi.fn().mockReturnValue(child)
await expect(
writeHostedIosSimulatorPasteboard('simulator', 'marker', spawnProcess)
).resolves.toBeUndefined()
expect(spawnProcess).toHaveBeenCalledWith('xcrun', ['simctl', 'pbcopy', 'simulator'], {
stdio: ['pipe', 'ignore', 'pipe']
})
expect(stdin.end).toHaveBeenCalledWith('marker')
})
it('retries a silent native tap before accepting terminal evidence', async () => {
const sessionDocument = {
href: 'orca-mobile-web://build/h/host/session/workspace'
}
const tapControl = vi
.fn()
.mockResolvedValueOnce({ x: 0.25, y: 0.9 })
.mockRejectedValueOnce(new Error('prompt not shown'))
.mockResolvedValueOnce({ x: 0.25, y: 0.9 })
.mockRejectedValueOnce(new Error('prompt not shown'))
const readTerminal = vi
.fn()
.mockRejectedValueOnce(new Error('marker missing'))
.mockResolvedValueOnce('terminal-handle')
const result = await verifyHostedIosTerminalClipboardPaste(
{
deviceUdid: 'simulator',
discoveryUrl: 'http://127.0.0.1:9222',
emulator: { deviceUdid: 'simulator' },
expectedWorkspace: 'mobile-rearch',
orcaCli: './config/scripts/orca-dev.mjs',
pairingRuntimeUserDataPath: '/tmp/pairing/userData',
timeoutMs: 30_000,
workspaceDocument: { href: 'orca-mobile-web://build/h/host' },
worktree: '/repo/mobile-rearch'
},
{
activateWorkspace: vi.fn().mockResolvedValue(undefined),
readTerminal,
tapControl,
waitForDocument: vi.fn().mockResolvedValue(sessionDocument),
writePasteboard: vi.fn().mockResolvedValue(undefined)
}
)
expect(result.evidence).toMatchObject({
activationAttempts: 2,
pastePermissionPrompt: 'not-shown',
terminalHandle: 'terminal-handle'
})
expect(readTerminal).toHaveBeenCalledTimes(2)
expect(tapControl).toHaveBeenCalledTimes(4)
})
})
@@ -1,183 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
verifyClipboardImagePaste: vi.fn(),
verifyDocumentUpload: vi.fn(),
verifyClipboardPaste: vi.fn(),
verifyPhotoDenial: vi.fn(),
verifyPhotoRevocation: vi.fn()
}))
vi.mock('../../scripts/hosted-ios-document-upload.mjs', () => ({
verifyHostedIosDocumentUpload: mocks.verifyDocumentUpload
}))
vi.mock('../../scripts/hosted-ios-terminal-clipboard-paste.mjs', () => ({
verifyHostedIosTerminalClipboardPaste: mocks.verifyClipboardPaste
}))
vi.mock('../../scripts/hosted-ios-terminal-clipboard-image-paste.mjs', () => ({
verifyHostedIosTerminalClipboardImagePaste: mocks.verifyClipboardImagePaste
}))
vi.mock('../../scripts/hosted-ios-photo-permission-denial.mjs', () => ({
verifyHostedIosPhotoPermissionDenial: mocks.verifyPhotoDenial
}))
vi.mock('../../scripts/hosted-ios-photo-permission-revocation.mjs', () => ({
verifyHostedIosPhotoPermissionRevocation: mocks.verifyPhotoRevocation
}))
import {
verifyHostedIosTerminalDeviceInputJourney,
verifyHostedIosTerminalInputJourney
} from '../../scripts/hosted-ios-terminal-device-input-journey.mjs'
describe('hosted iOS terminal device-input journey', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('carries the exact session and terminal through every device-input operation', async () => {
const args = {
deviceUdid: 'simulator',
emulator: {},
worktree: '/repo/mobile-rearch',
workspaceDocument: { href: 'workspace' }
}
const documentFixture = {
destinationPath: '/simulator/fixture.png',
fixtureName: 'fixture.png'
}
const seedFixture = vi.fn().mockResolvedValue(documentFixture)
const removeFixture = vi.fn().mockResolvedValue(undefined)
const terminalClipboardPaste = {
evidence: { terminalHandle: 'terminal-handle' },
sessionDocument: { href: 'session' }
}
const photoPermissionDenial = {
evidence: { toast: 'Photo permission denied' },
sessionDocument: { href: 'session-after-photo-denial' }
}
const photoPermissionRevocation = {
evidence: { permissionState: 'revoked-after-grant' },
sessionDocument: { href: 'session-after-photo-revocation' }
}
const documentUpload = {
evidence: { terminalPathInjected: true },
sessionDocument: { href: 'session-after-document-upload' }
}
const terminalClipboardImagePaste = {
evidence: { terminalPathInjected: true },
sessionDocument: { href: 'session-after-clipboard-image-paste' }
}
mocks.verifyClipboardPaste.mockResolvedValue(terminalClipboardPaste)
mocks.verifyClipboardImagePaste.mockResolvedValue(terminalClipboardImagePaste)
mocks.verifyPhotoDenial.mockResolvedValue(photoPermissionDenial)
mocks.verifyPhotoRevocation.mockResolvedValue(photoPermissionRevocation)
mocks.verifyDocumentUpload.mockResolvedValue(documentUpload)
await expect(
verifyHostedIosTerminalDeviceInputJourney(args, {
removeFixture,
seedFixture
})
).resolves.toEqual({
documentUpload,
photoPermissionDenial,
photoPermissionRevocation,
terminalClipboardImagePaste,
terminalClipboardPaste
})
expect(seedFixture).toHaveBeenCalledWith({
deviceUdid: 'simulator',
fixturePath: '/repo/mobile-rearch/mobile/assets/favicon.png'
})
expect(mocks.verifyPhotoDenial).toHaveBeenCalledWith({
...args,
sessionDocument: documentUpload.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(mocks.verifyDocumentUpload).toHaveBeenCalledWith({
...args,
documentFixture,
sessionDocument: terminalClipboardPaste.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(mocks.verifyPhotoRevocation).toHaveBeenCalledWith({
...args,
sessionDocument: photoPermissionDenial.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(mocks.verifyClipboardImagePaste).toHaveBeenCalledWith({
...args,
sessionDocument: photoPermissionRevocation.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(removeFixture).toHaveBeenCalledWith(documentFixture.destinationPath)
})
it('runs clipboard-image verification without unrelated picker stages', async () => {
const args = {
deviceUdid: 'simulator',
emulator: {},
worktree: '/repo/mobile-rearch',
workspaceDocument: { href: 'workspace' }
}
const terminalClipboardPaste = {
evidence: { terminalHandle: 'terminal-handle' },
sessionDocument: { href: 'session' }
}
const terminalClipboardImagePaste = {
evidence: { terminalPathInjected: true },
sessionDocument: { href: 'session-after-image-paste' }
}
mocks.verifyClipboardPaste.mockResolvedValue(terminalClipboardPaste)
mocks.verifyClipboardImagePaste.mockResolvedValue(terminalClipboardImagePaste)
await expect(
verifyHostedIosTerminalInputJourney(args, { clipboardImageOnly: true })
).resolves.toEqual({
terminalClipboardImagePaste,
terminalClipboardPaste
})
expect(mocks.verifyClipboardImagePaste).toHaveBeenCalledWith({
...args,
sessionDocument: terminalClipboardPaste.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(mocks.verifyDocumentUpload).not.toHaveBeenCalled()
expect(mocks.verifyPhotoDenial).not.toHaveBeenCalled()
expect(mocks.verifyPhotoRevocation).not.toHaveBeenCalled()
})
it('runs Photos revocation without unrelated picker stages', async () => {
const args = {
deviceUdid: 'simulator',
emulator: {},
worktree: '/repo/mobile-rearch',
workspaceDocument: { href: 'workspace' }
}
const terminalClipboardPaste = {
evidence: { terminalHandle: 'terminal-handle' },
sessionDocument: { href: 'session' }
}
const photoPermissionRevocation = {
evidence: { permissionState: 'revoked-after-grant' },
sessionDocument: { href: 'session-after-photo-revocation' }
}
mocks.verifyClipboardPaste.mockResolvedValue(terminalClipboardPaste)
mocks.verifyPhotoRevocation.mockResolvedValue(photoPermissionRevocation)
await expect(
verifyHostedIosTerminalInputJourney(args, { photosRevocationOnly: true })
).resolves.toEqual({
photoPermissionRevocation,
terminalClipboardPaste
})
expect(mocks.verifyPhotoRevocation).toHaveBeenCalledWith({
...args,
sessionDocument: terminalClipboardPaste.sessionDocument,
terminalHandle: 'terminal-handle'
})
expect(mocks.verifyDocumentUpload).not.toHaveBeenCalled()
expect(mocks.verifyPhotoDenial).not.toHaveBeenCalled()
expect(mocks.verifyClipboardImagePaste).not.toHaveBeenCalled()
})
})
@@ -1,48 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
alignHostedIosSessionPoint,
alignHostedIosTerminalLinkPoints
} from '../../scripts/hosted-ios-adversarial-terminal-links.mjs'
describe('hosted iOS terminal link point alignment', () => {
it('removes the transient viewport displacement before a native tap', () => {
expect(
alignHostedIosTerminalLinkPoints(
{
file: { x: 0.07, y: 0.72 },
http: { x: 0.07, y: 0.79 },
javascript: { x: 0.07, y: 0.65 }
},
{ x: 0.4, y: 0.5 },
{ x: 0.4, y: 0.14 }
)
).toEqual({
points: {
file: { x: 0.07, y: 0.36 },
http: { x: 0.07, y: 0.43000000000000005 },
javascript: { x: 0.07, y: 0.29000000000000004 }
},
yOffset: -0.36
})
})
it('rejects an alignment that leaves the native screen', () => {
expect(() =>
alignHostedIosTerminalLinkPoints(
{ file: { x: 0.2, y: 0.1 } },
{ x: 0.2, y: 0.8 },
{ x: 0.2, y: 0.1 }
)
).toThrow('alignment is invalid')
})
it('applies the measured offset only to Session-origin taps', () => {
const point = { x: 0.8, y: 0.7 }
expect(
alignHostedIosSessionPoint(point, -0.36, { href: 'orca://host/session/opaque' })
).toEqual({ x: 0.8, y: 0.33999999999999997 })
expect(
alignHostedIosSessionPoint(point, -0.36, { href: 'orca://host/source-control/opaque' })
).toBe(point)
})
})
@@ -1,80 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
captureScreenshot: vi.fn(),
compareScreenshots: vi.fn(),
dismissDeveloperMenu: vi.fn(),
readTextPoint: vi.fn(),
waitForControl: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: mocks.captureScreenshot
}))
vi.mock('../../scripts/emulator-developer-menu-dismissal.mjs', () => ({
dismissEmulatorDeveloperMenuIfPresent: mocks.dismissDeveloperMenu
}))
vi.mock('../../scripts/hosted-ios-emulator-accessibility.mjs', () => ({
waitForHostedIosAccessibilityControl: mocks.waitForControl
}))
vi.mock('../../scripts/hosted-ios-screenshot-parity.mjs', () => ({
assertHostedIosScreenshotParity: mocks.compareScreenshots
}))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
readHostedWebViewTextPoint: mocks.readTextPoint
}))
import {
captureHostedWorkspaceParity,
captureNativeWorkspaceBaseline
} from '../../scripts/hosted-ios-workspace-parity.mjs'
describe('hosted iOS workspace parity', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.captureScreenshot.mockImplementation((_command, _args, callback) =>
callback(null, '', '')
)
mocks.compareScreenshots.mockResolvedValue({ changedPixelRatio: 0.01 })
mocks.readTextPoint.mockResolvedValue({ x: 0.2, y: 0.1 })
mocks.waitForControl.mockResolvedValue({ x: 0.2, y: 0.1 })
})
it('captures the unchanged native workspace screen', async () => {
const baseline = await captureNativeWorkspaceBaseline({
deviceUdid: 'simulator',
emulator: { deviceUdid: 'simulator' },
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.waitForControl).toHaveBeenCalledWith({ deviceUdid: 'simulator' }, 'Filter', 30_000)
expect(baseline).toEqual({
filterPoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-workspace-portrait.png'
})
})
it('compares the unchanged hosted workspace screen', async () => {
const evidence = await captureHostedWorkspaceParity({
deviceUdid: 'simulator',
document: { href: 'orca-mobile-web://build/h/host' },
nativeBaseline: {
filterPoint: { x: 0.2, y: 0.1 },
screenshot: '/tmp/parity/native-workspace-portrait.png'
},
runtimeDirectory: '/tmp/parity',
timeoutMs: 30_000
})
expect(mocks.readTextPoint).toHaveBeenCalledWith(expect.anything(), 'Filter')
expect(mocks.compareScreenshots).toHaveBeenCalledOnce()
expect(evidence).toEqual({
nativeScreenshot: 'native-workspace-portrait.png',
hostedScreenshot: 'hosted-workspace-portrait.png',
nativeFilterPoint: { x: 0.2, y: 0.1 },
hostedFilterPoint: { x: 0.2, y: 0.1 },
screenshotParity: { changedPixelRatio: 0.01 }
})
})
})
@@ -1,23 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
HOSTED_MOBILE_APP_ROUTE_URL,
hostedMobileMetroArguments
} from '../../scripts/hosted-mobile-e2e-launch.mjs'
describe('hosted mobile E2E launch', () => {
it('uses an Expo Router path URL', () => {
expect(HOSTED_MOBILE_APP_ROUTE_URL).toBe('orca:///hybrid')
})
it('invalidates Metro transforms when the public host identity changes', () => {
expect(hostedMobileMetroArguments(8081, true)).toEqual([
'start',
'--host',
'lan',
'--port',
'8081',
'--clear'
])
expect(hostedMobileMetroArguments(8081, false)).not.toContain('--clear')
})
})
@@ -1,55 +0,0 @@
import { readFileSync } from 'node:fs'
import { join, relative } from 'node:path'
import { describe, expect, it } from 'vitest'
import { hostedModuleGraph, hostedRoot, mobileRoot } from './hosted-module-graph.test-support'
const HOSTED_SESSION_ROUTE = join(hostedRoot, 'h', '[hostId]', 'session', '[worktreeId].tsx')
// Native-only entry points: on the hosted page these either throw or silently do nothing.
const BANNED_IN_HOSTED_BUNDLE = ['Clipboard.setStringAsync']
const BANNED_IN_SESSION_ROUTE = [
...BANNED_IN_HOSTED_BUNDLE,
'Linking.openURL',
"/transport/host-store'"
]
describe('hosted route native API census', () => {
it('keeps native-only APIs out of every module the hosted session route reaches', () => {
const graph = hostedModuleGraph([HOSTED_SESSION_ROUTE])
// Presence precondition: the walk must actually reach the session screen's own modules.
expect(graph.length).toBeGreaterThan(500)
expect(graph.map((path) => relative(mobileRoot, path))).toEqual(
expect.arrayContaining([
join('app', 'h', '[hostId]', 'session', '[worktreeId].tsx'),
join('src', 'session', 'MobileTerminalInputActions.tsx')
])
)
expect(offenders(graph, BANNED_IN_SESSION_ROUTE)).toEqual([])
})
it('keeps native-only APIs out of every module any hosted route reaches', () => {
const graph = hostedModuleGraph()
expect(graph.length).toBeGreaterThan(500)
expect(graph).toContain(join(hostedRoot, 'terminal-settings.tsx'))
// Terminal settings now has a real hosted route; it is no longer a native-only destination.
// AsyncStorage is deliberately not censused: it is reachable today through the shared
// storage and transport modules, and on web it resolves to localStorage.
expect(offenders(graph, BANNED_IN_HOSTED_BUNDLE)).toEqual([])
})
})
function offenders(graph: string[], banned: string[]): string[] {
const found: string[] = []
for (const modulePath of graph) {
readFileSync(modulePath, 'utf8')
.split('\n')
.forEach((line, index) => {
if (banned.some((pattern) => line.includes(pattern))) {
found.push(`${relative(mobileRoot, modulePath)}:${index + 1}`)
}
})
}
return found
}
@@ -1,116 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
describeHostedTerminalLinkPoint,
hostedTerminalLinkPointsFromGeometry,
readHostedTerminalLinkPoints
} from '../../scripts/hosted-terminal-link-locator.mjs'
const geometry = {
cellHeight: 20,
cellWidth: 10,
cursorTop: 460,
innerHeight: 740,
screenRect: {
height: 600,
width: 400
},
terminalRect: {
bottom: 700,
height: 610,
left: 0,
top: 90,
width: 400
},
screenHeight: 800,
screenWidth: 400
}
describe('hosted terminal link locator', () => {
it('maps fixed cursor-relative rows through the resized xterm grid', () => {
expect(hostedTerminalLinkPointsFromGeometry(geometry)).toEqual({
javascript: { x: 0.0875, y: 0.530625 },
file: { x: 0.0875, y: 0.606875 },
fileAlternate: { x: 0.0875, y: 0.6322916666666667 },
http: { x: 0.0875, y: 0.683125 }
})
})
it('uses rendered link rows when the cursor moved after staging', () => {
expect(
hostedTerminalLinkPointsFromGeometry({
...geometry,
cursorTop: 20,
linkRows: { javascript: 4, file: 7, fileAlternate: 8, http: 10 }
})
).toEqual({
javascript: { x: 0.0875, y: 0.301875 },
file: { x: 0.0875, y: 0.378125 },
fileAlternate: { x: 0.0875, y: 0.40354166666666663 },
http: { x: 0.0875, y: 0.454375 }
})
})
it('anchors the staged corpus to the viewport bottom when xterm is blurred', () => {
expect(hostedTerminalLinkPointsFromGeometry({ ...geometry, cursorTop: 0 })).toEqual({
javascript: { x: 0.0875, y: 0.683125 },
file: { x: 0.0875, y: 0.759375 },
fileAlternate: { x: 0.0875, y: 0.7847916666666666 },
http: { x: 0.0875, y: 0.835625 }
})
})
it('rejects an empty discovered xterm instead of tapping a guessed row', () => {
expect(() =>
hostedTerminalLinkPointsFromGeometry({
...geometry,
cursorTop: 0,
linkRows: { javascript: null, file: null, fileAlternate: null, http: null },
xtermFound: true
})
).toThrow('corpus is not present')
})
it('fails when the rendered cell geometry cannot describe the screen', () => {
expect(() =>
hostedTerminalLinkPointsFromGeometry({
...geometry,
cellHeight: 70
})
).toThrow('grid is invalid')
})
it('reports the DOM target under a normalized emulator point', async () => {
const evaluate = async (_document: unknown, expression: string) => {
expect(expression).toContain('"x":0.25')
expect(expression).toContain('document.elementFromPoint')
return JSON.stringify({ clientX: 100, clientY: 200, terminalHit: true })
}
await expect(
describeHostedTerminalLinkPoint({}, { x: 0.25, y: 0.5 }, { evaluate })
).resolves.toEqual({
clientX: 100,
clientY: 200,
terminalHit: true
})
})
it('reads rendered link rows from the visible xterm buffer', async () => {
const evaluate = async (_document: unknown, expression: string) => {
expect(expression).toContain('xterm.buffer.active')
return JSON.stringify({
...geometry,
cursorTop: 0,
linkRows: { javascript: 4, file: 7, fileAlternate: 8, http: 10 }
})
}
await expect(readHostedTerminalLinkPoints({}, { evaluate })).resolves.toEqual(
hostedTerminalLinkPointsFromGeometry({
...geometry,
cursorTop: 0,
linkRows: { javascript: 4, file: 7, fileAlternate: 8, http: 10 }
})
)
})
})
@@ -1,266 +0,0 @@
import { execFile } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { promisify } from 'node:util'
import { afterEach, describe, expect, it } from 'vitest'
import {
createHostedAdversarialRepositoryFixture,
HOSTED_ADVERSARIAL_CONTENT,
HOSTED_ADVERSARIAL_CONTENT_MARKER,
HOSTED_ADVERSARIAL_FILENAME,
HOSTED_ADVERSARIAL_FILENAME_MARKER,
HOSTED_ADVERSARIAL_HTML_FILENAME,
HOSTED_ADVERSARIAL_HTML_MARKER,
HOSTED_ADVERSARIAL_IMAGE_FILENAME,
HOSTED_ADVERSARIAL_IMAGE_MARKER,
HOSTED_ADVERSARIAL_MARKDOWN_FILENAME,
HOSTED_ADVERSARIAL_MARKDOWN_MARKER,
HOSTED_ADVERSARIAL_SVG_FILENAME,
HOSTED_ADVERSARIAL_SVG_MARKER,
HOSTED_ADVERSARIAL_WORKSPACE_ROW,
readHostedAdversarialRepositoryContent,
removeHostedAdversarialRepositoryFixture
} from '../../scripts/hosted-adversarial-repository-fixture.mjs'
import {
hostedWebViewAdversarialContentEvidence,
hostedWebViewAdversarialContentObservations,
verifyHostedWebViewAdversarialContent
} from '../../scripts/hosted-webview-adversarial-content.mjs'
import { hostedAdversarialFileExecutionEvidence } from '../../scripts/hosted-webview-adversarial-files.mjs'
const execFileAsync = promisify(execFile)
const fixtures: Awaited<ReturnType<typeof createHostedAdversarialRepositoryFixture>>[] = []
const androidHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-source-control-review-e2e.mjs', import.meta.url),
'utf8'
)
const androidHarnessPath = fileURLToPath(
new URL('../../scripts/run-hosted-android-source-control-review-e2e.mjs', import.meta.url)
)
const iosHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-webview-simulator-e2e.mjs', import.meta.url),
'utf8'
)
afterEach(async () => {
await Promise.all(fixtures.splice(0).map(removeHostedAdversarialRepositoryFixture))
})
describe('hosted WebView adversarial content', () => {
it('creates one disposable hostile filename and diff', async () => {
const fixture = await createHostedAdversarialRepositoryFixture({
probePort: 54321
})
fixtures.push(fixture)
const status = await execFileAsync('git', ['status', '--short'], {
cwd: fixture.root,
encoding: 'utf8'
})
expect(status.stdout).toContain(HOSTED_ADVERSARIAL_FILENAME_MARKER)
const diff = await execFileAsync('git', ['diff', '--'], {
cwd: fixture.root,
encoding: 'utf8'
})
expect(diff.stdout).toContain(HOSTED_ADVERSARIAL_CONTENT_MARKER)
expect(await readHostedAdversarialRepositoryContent(fixture)).toBe(
`${HOSTED_ADVERSARIAL_CONTENT}\n`
)
const branch = await execFileAsync('git', ['symbolic-ref', '--short', 'HEAD'], {
cwd: fixture.root,
encoding: 'utf8'
})
expect(branch.stdout.trim()).toBe(HOSTED_ADVERSARIAL_WORKSPACE_ROW)
expect(fixture.workspaceRowName).toBe(HOSTED_ADVERSARIAL_WORKSPACE_ROW)
expect(fixture.repositoryFiles.map(({ filename }) => filename)).toEqual([
HOSTED_ADVERSARIAL_MARKDOWN_FILENAME,
HOSTED_ADVERSARIAL_HTML_FILENAME,
HOSTED_ADVERSARIAL_SVG_FILENAME,
HOSTED_ADVERSARIAL_IMAGE_FILENAME
])
expect(fixture.repositoryFiles.map(({ marker }) => marker)).toEqual([
HOSTED_ADVERSARIAL_MARKDOWN_MARKER,
HOSTED_ADVERSARIAL_HTML_MARKER,
HOSTED_ADVERSARIAL_SVG_MARKER,
HOSTED_ADVERSARIAL_IMAGE_MARKER
])
for (const file of fixture.repositoryFiles) {
const content = await readFile(path.join(fixture.root, file.filename))
expect(content).toEqual(Buffer.from(file.content))
expect(content.includes(file.marker)).toBe(true)
expect(content.includes('http://127.0.0.1:54321/')).toBe(true)
}
})
it('accepts literal markers without created elements or execution', () => {
expect(
hostedWebViewAdversarialContentEvidence({
text: `${HOSTED_ADVERSARIAL_FILENAME}\n${HOSTED_ADVERSARIAL_CONTENT}`,
execution: {
filenameExecuted: false,
contentExecuted: false,
injectedImageCount: 0
}
})
).toEqual({
filenameRenderedAsText: true,
diffRenderedAsText: true,
injectedImageCount: 0,
scriptMarkersExecuted: false
})
})
it('aggregates literal markers across route documents', async () => {
const values = [
JSON.stringify({
href: 'https://orca-mobile-web.invalid/source-control',
bodyText: HOSTED_ADVERSARIAL_FILENAME,
labels: [],
placeholders: []
}),
JSON.stringify({
filenameExecuted: false,
contentExecuted: false,
injectedImageCount: 0
}),
JSON.stringify({
href: 'https://orca-mobile-web.invalid/session',
bodyText: HOSTED_ADVERSARIAL_CONTENT,
labels: [],
placeholders: []
}),
JSON.stringify({
filenameExecuted: false,
contentExecuted: false,
injectedImageCount: 0
})
]
class FakeWebSocket {
private message: ((data: Buffer) => void) | undefined
once(event: string, listener: () => void) {
if (event === 'open') {
queueMicrotask(listener)
}
}
on(event: string, listener: (data: Buffer) => void) {
if (event === 'message') {
this.message = listener
}
}
send(payload: string) {
const id = JSON.parse(payload).id
const value = values.shift()
queueMicrotask(() => {
this.message?.(
Buffer.from(JSON.stringify({ id, result: { result: { value: value ?? '' } } }))
)
})
}
close() {}
}
await expect(
verifyHostedWebViewAdversarialContent({
documents: [
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/source' },
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/session' }
],
WebSocketCtor: FakeWebSocket as never
})
).resolves.toMatchObject({
filenameRenderedAsText: true,
diffRenderedAsText: true,
scriptMarkersExecuted: false
})
})
it('rejects missing literal markers and executed content', () => {
expect(() =>
hostedWebViewAdversarialContentEvidence({
text: HOSTED_ADVERSARIAL_FILENAME,
execution: {
filenameExecuted: false,
contentExecuted: false,
injectedImageCount: 0
}
})
).toThrow('was not rendered')
expect(() =>
hostedWebViewAdversarialContentEvidence({
text: `${HOSTED_ADVERSARIAL_FILENAME}\n${HOSTED_ADVERSARIAL_CONTENT}`,
execution: {
filenameExecuted: false,
contentExecuted: true,
injectedImageCount: 1
}
})
).toThrow('content executed')
})
it('rejects malformed execution observations without coercion', () => {
expect(() =>
hostedWebViewAdversarialContentObservations([
{
state: {
bodyText: `${HOSTED_ADVERSARIAL_FILENAME}\n${HOSTED_ADVERSARIAL_CONTENT}`,
labels: []
},
execution: {
filenameExecuted: 'false',
contentExecuted: false,
injectedImageCount: 0
}
}
])
).toThrow('content executed')
})
it('rejects malformed repository-file execution evidence', () => {
expect(
hostedAdversarialFileExecutionEvidence({
markers: [false, false, false, false],
injectedElementCount: 0
})
).toEqual({
injectedElementCount: 0,
repositoryFileScriptMarkersExecuted: false
})
expect(() =>
hostedAdversarialFileExecutionEvidence({
markers: [false, 'false', false, false],
injectedElementCount: 0
})
).toThrow('repository file executed')
})
it('keeps the disposable corpus wired into the exact Android route gate', () => {
expect(androidHarnessSource).toContain('createHostedAdversarialRuntimeFixture')
expect(androidHarnessSource).toContain('captureHostedWebViewAdversarialObservation')
expect(androidHarnessSource).toContain('hostedWebViewAdversarialContentObservations')
expect(androidHarnessSource).toContain('removeHostedAdversarialRuntimeFixture')
})
it('rejects conflicting Android security journeys before device access', async () => {
await expect(
execFileAsync(process.execPath, [
androidHarnessPath,
'--adversarial-content',
'--security-only'
])
).rejects.toMatchObject({
stderr: expect.stringContaining('mutually exclusive')
})
})
it('keeps the same disposable corpus wired into the exact iOS route gate', () => {
expect(iosHarnessSource).toContain('registerHostedIosAdversarialRepository')
expect(iosHarnessSource).toContain('createHostedIosAdversarialContentInspector')
expect(iosHarnessSource).toContain('removeHostedAdversarialRuntimeFixture')
})
})
@@ -1,804 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import {
activateHostedWebViewControl,
assertNoHostedMobileWebCdpTarget,
isHostedMobileWebUrl,
readHostedWebViewState,
readHostedWebViewTextPoint,
selectVisibleHostedWebView,
setHostedWebViewInput,
startHostedWebViewConnectionObservation,
terminateHostedWebViewProcess,
waitForHostedWebViewConnectionSequence,
verifyHostedWebViewNavigationIsolation,
verifyHostedWebViewNetworkIsolation
} from '../../scripts/hosted-webview-cdp-session.mjs'
import { createHostedIosNativeBaselineStep } from '../../scripts/hosted-ios-native-baseline-step.mjs'
import { verifyHostedWebViewExecutableIsolation } from '../../scripts/hosted-webview-executable-isolation.mjs'
import {
FakeCdpSocket,
FakeProcessTerminationSocket,
fakeCdpConstructor
} from './hosted-webview-cdp-test-fakes'
const iosShellSource = readFileSync(
new URL('../../packages/expo-mobile-web-shell/ios/MobileWebShellView.swift', import.meta.url),
'utf8'
)
const androidShellSource = readFileSync(
new URL(
'../../packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebShellView.kt',
import.meta.url
),
'utf8'
)
const androidProbeSource = readFileSync(
new URL(
'../../packages/expo-mobile-web-shell/android/src/main/java/expo/modules/mobilewebshell/MobileWebDebugIsolationProbe.kt',
import.meta.url
),
'utf8'
)
// Read patches through pnpm-workspace.yaml so a dependency bump can't leave a patch undeclared.
const workspaceManifest = readFileSync(
new URL('../../pnpm-workspace.yaml', import.meta.url),
'utf8'
)
function readPatchedDependencySource(packageName: string): string {
const entry = workspaceManifest.match(
new RegExp(`^\\s+'?${packageName.replace(/[/@.]/g, '\\$&')}@[^':]+'?:\\s*(\\S+)$`, 'm')
)
if (!entry) {
throw new Error(`no patchedDependencies entry for ${packageName}`)
}
return readFileSync(new URL(`../../${entry[1]}`, import.meta.url), 'utf8')
}
const expoLogBoxPatch = readPatchedDependencySource('@expo/log-box')
const expoDomWebViewPatch = readPatchedDependencySource('@expo/dom-webview')
const reactNativeWebViewPatch = readPatchedDependencySource('react-native-webview')
const simulatorHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-webview-simulator-e2e.mjs', import.meta.url),
'utf8'
)
const simulatorSecurityEvidenceSource = readFileSync(
new URL('../../scripts/hosted-webview-security-evidence.mjs', import.meta.url),
'utf8'
)
const simulatorReportSource = readFileSync(
new URL('../../scripts/hosted-webview-e2e-report.mjs', import.meta.url),
'utf8'
)
const simulatorAppBuildSource = readFileSync(
new URL('../../scripts/hosted-ios-simulator-app-build.mjs', import.meta.url),
'utf8'
)
const simulatorAppPreparationSource = readFileSync(
new URL('../../scripts/hosted-ios-simulator-app-preparation.mjs', import.meta.url),
'utf8'
)
const simulatorLauncherSource = readFileSync(
new URL('../../scripts/hosted-ios-mobile-launcher.mjs', import.meta.url),
'utf8'
)
const androidSecurityHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-webview-security-e2e.mjs', import.meta.url),
'utf8'
)
const androidReleaseHarnessSource = readFileSync(
new URL('../../scripts/verify-hosted-android-release-webview.mjs', import.meta.url),
'utf8'
)
const androidCrashLoopHarnessSource = readFileSync(
new URL('../../scripts/run-hosted-android-webview-crash-loop.mjs', import.meta.url),
'utf8'
)
function probe(overrides: Record<string, unknown> = {}) {
return {
targetId: 'target-a',
href: 'orca-mobile-web://session-a/',
visibility: 'visible',
focused: true,
bridgeListening: true,
bodyText: 'mobile-rearch',
buttonCount: 3,
...overrides
}
}
describe('hosted WebView CDP target selection', () => {
it('waits for the workspace route when the departing session has the same text', () => {
const session = probe({ href: 'orca-mobile-web://session-a/h/host/session/workspace' })
const workspace = probe({ href: 'orca-mobile-web://session-a/h/host', focused: false })
expect(
selectVisibleHostedWebView([session], 'mobile-rearch', undefined, true, '/h/host')
).toBeNull()
expect(
selectVisibleHostedWebView([session, workspace], 'mobile-rearch', undefined, true, '/h/host')
).toBe(workspace)
})
it('recognizes only the platform private asset origins', () => {
expect(isHostedMobileWebUrl('orca-mobile-web://session-a/')).toBe(true)
expect(isHostedMobileWebUrl('https://session-a.orca-mobile-web.invalid/#session-a')).toBe(true)
expect(isHostedMobileWebUrl('https://orca-mobile-web.invalid/#session-a')).toBe(true)
expect(isHostedMobileWebUrl('https://orca-mobile-web.invalid.evil.test/')).toBe(false)
expect(isHostedMobileWebUrl('https://session-a.orca-mobile-web.invalid.evil.test/')).toBe(false)
expect(isHostedMobileWebUrl('http://orca-mobile-web.invalid/')).toBe(false)
expect(isHostedMobileWebUrl('https://session-a.orca-mobile-web.invalid.evil/')).toBe(false)
})
it('proves native baselines have no hosted private-origin CDP target', async () => {
const target = (url: string) => ({
id: 'target',
type: 'page',
url,
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/target'
})
const fetchTargets = (urls: string[]) => async () =>
new Response(JSON.stringify(urls.map(target)))
await expect(
assertNoHostedMobileWebCdpTarget({
discoveryUrl: 'http://127.0.0.1:9222',
fetchImpl: fetchTargets(['about:blank', 'file:///native-terminal.html'])
})
).resolves.toBeUndefined()
await expect(
assertNoHostedMobileWebCdpTarget({
discoveryUrl: 'http://127.0.0.1:9222',
fetchImpl: fetchTargets(Array.from({ length: 17 }, () => 'about:blank'))
})
).rejects.toThrow('Native baseline CDP target count exceeded its inspection limit')
for (const url of [
'orca-mobile-web://session-a/',
'https://orca-mobile-web.invalid/#session-a'
]) {
await expect(
assertNoHostedMobileWebCdpTarget({
discoveryUrl: 'http://127.0.0.1:9222',
fetchImpl: fetchTargets([url])
})
).rejects.toThrow('Native baseline has a hosted mobile WebView CDP target')
}
await expect(
assertNoHostedMobileWebCdpTarget({
discoveryUrl: 'http://127.0.0.1:9222',
fetchImpl: async () =>
new Response(
JSON.stringify([
{
id: 'uninspectable-hosted-target',
type: 'page',
url: 'orca-mobile-web://session-a/'
}
])
)
})
).rejects.toThrow('Native baseline has a hosted mobile WebView CDP target')
const staleDiscoverySocket = new FakeCdpSocket([JSON.stringify(probe())])
await expect(
assertNoHostedMobileWebCdpTarget({
discoveryUrl: 'http://127.0.0.1:9222',
fetchImpl: fetchTargets(['about:blank']),
WebSocketCtor: fakeCdpConstructor(staleDiscoverySocket)
})
).rejects.toThrow('Native baseline has a hosted mobile WebView CDP target')
})
it('asserts target exclusion around every native baseline capture', async () => {
const events: string[] = []
const nativeBaselineStep = createHostedIosNativeBaselineStep({
assertNoHostedMobileWebCdpTarget: async () => {
events.push('assert')
},
discoveryUrl: 'http://127.0.0.1:9222',
evidenceStep: async (label: string, run: () => Promise<string>) => {
events.push(label)
return run()
}
})
await expect(
nativeBaselineStep('native workspace baseline', async () => {
events.push('capture')
return 'captured'
})
).resolves.toBe('captured')
expect(events).toEqual([
'native workspace baseline hosted target exclusion',
'assert',
'native workspace baseline',
'capture',
'native workspace baseline hosted target exclusion after capture',
'assert'
])
})
it('selects only a visible interactive hosted document with expected UI text', () => {
expect(
selectVisibleHostedWebView(
[
probe({ href: 'https://untrusted.example/' }),
probe({ visibility: 'hidden' }),
probe({ bridgeListening: false }),
probe({ bodyText: 'another workspace' }),
probe()
],
'mobile-rearch'
)
).toMatchObject({ targetId: 'target-a' })
})
it('prefers the focused generation when WebKit briefly reports two visible documents', () => {
expect(
selectVisibleHostedWebView(
[probe({ targetId: 'old', focused: false }), probe({ targetId: 'current', focused: true })],
'mobile-rearch'
)
).toMatchObject({ targetId: 'current' })
})
it('rejects a document without user-observable controls', () => {
expect(selectVisibleHostedWebView([probe({ buttonCount: 0 })], 'mobile-rearch')).toBeNull()
})
it('can select a ready terminal document without raw DOM buttons', () => {
expect(
selectVisibleHostedWebView(
[probe({ buttonCount: 0, href: 'orca-mobile-web://session-a/h/host/session/worktree' })],
'mobile-rearch',
'/session/',
false
)
).toMatchObject({ targetId: 'target-a' })
})
it('can require the visible document to be on the expected route', () => {
expect(
selectVisibleHostedWebView(
[
probe({
href: 'orca-mobile-web://session-a/h/host/agent-history/worktree'
}),
probe({
href: 'orca-mobile-web://session-a/h/host/session/worktree'
})
],
'mobile-rearch',
'/session/'
)
).toMatchObject({
href: 'orca-mobile-web://session-a/h/host/session/worktree'
})
})
it('starts the document probe and requires its completion marker', async () => {
const socket = new FakeCdpSocket(['probe-a', 'probe-a'])
const result = await verifyHostedWebViewNetworkIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
}
})
expect(result).toEqual({
fetch: 'attempted',
xhr: 'attempted',
webSocket: 'attempted',
image: 'attempted'
})
expect(socket.evaluations).toHaveLength(2)
const expressions = socket.evaluations
.map((evaluation) => evaluation.params.expression)
.join('\n')
expect(expressions).toContain('__orcaRunSecurityProbe')
expect(expressions).toContain('__orcaDebugNetworkProbeCompletion')
})
it('requires an exact probe token', async () => {
await expect(
verifyHostedWebViewNetworkIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: ''
})
).rejects.toThrow('token is unavailable')
})
it('requires all adversarial navigation attempts to fail closed', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
token: 'probe-a',
documentRetained: true,
popupBlocked: true,
serviceWorkerBlocked: true,
redirectFrameAttempted: true,
downloadAttempted: true,
externalSchemeAttempted: true
})
])
const result = await verifyHostedWebViewNavigationIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
}
})
expect(result).toEqual({
documentRetained: true,
popupBlocked: true,
serviceWorkerBlocked: true,
redirectFrameAttempted: true,
downloadAttempted: true,
externalSchemeAttempted: true
})
expect(socket.evaluations[0]?.params.expression).toContain(
'__orcaDebugNavigationProbeCompletion'
)
})
it('requires the active script and blocks an undeclared executable asset', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
token: 'probe-a',
activeDeclaredScriptLoaded: true,
undeclaredScriptBlocked: true,
documentRetained: true,
bridgeListening: true,
scriptPaths: ['/h/host/review/assets/bundle.js']
})
])
await expect(
verifyHostedWebViewExecutableIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
}
})
).resolves.toEqual({
activeDeclaredScriptLoaded: true,
undeclaredScriptBlocked: true,
documentRetained: true
})
expect(socket.evaluations[0]?.params.expression).toContain(
'__orcaDebugExecutableProbeCompletion'
)
})
it('rejects incomplete executable isolation evidence', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
token: 'probe-a',
activeDeclaredScriptLoaded: true,
undeclaredScriptBlocked: false,
documentRetained: true
})
])
await expect(
verifyHostedWebViewExecutableIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
}
})
).rejects.toThrow('executable isolation failed')
})
it('reads a normalized hosted text landmark', async () => {
const socket = new FakeCdpSocket([JSON.stringify({ x: 0.25, y: 0.125 })])
await expect(
readHostedWebViewTextPoint(
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current' },
'Agent Session History',
fakeCdpConstructor(socket),
{ horizontalPosition: 0.15, ignoreCase: true, occurrence: 1, reveal: true }
)
).resolves.toEqual({ x: 0.25, y: 0.125 })
expect(socket.evaluations[0]?.params.expression).toContain('getBoundingClientRect')
expect(socket.evaluations[0]?.params.expression).toContain("style.visibility !== 'hidden'")
expect(socket.evaluations[0]?.params.expression).toContain('toLocaleLowerCase')
expect(socket.evaluations[0]?.params.expression).toContain('matches[1]')
expect(socket.evaluations[0]?.params.expression).toContain('rect.width * 0.15')
expect(socket.evaluations[0]?.params.expression).toContain(
"scrollIntoView({ block: 'nearest', inline: 'nearest' })"
)
})
it('rejects incomplete adversarial navigation evidence', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
token: 'probe-a',
documentRetained: true,
popupBlocked: false
})
])
await expect(
verifyHostedWebViewNavigationIsolation({
document: {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
}
})
).rejects.toThrow('navigation isolation failed')
})
it('accepts an Android popup proxy only without a second page target', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
token: 'probe-a',
documentRetained: true,
popupBlocked: false,
serviceWorkerBlocked: true,
redirectFrameAttempted: true,
downloadAttempted: true,
externalSchemeAttempted: true
})
])
const fetchImpl = async () =>
new Response(
JSON.stringify([
{
id: 'current',
type: 'page',
url: 'https://orca-mobile-web.invalid/#session',
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
}
])
)
await expect(
verifyHostedWebViewNavigationIsolation({
document: {
targetId: 'current',
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
},
discoveryUrl: 'http://127.0.0.1:9222',
probeId: 'probe-a',
settleDelayMs: 0,
WebSocketCtor: class {
constructor() {
return socket
}
},
fetchImpl
})
).resolves.toMatchObject({ popupBlocked: true })
})
it('reads bounded UI facts and activates controls without page-owned selectors', async () => {
const socket = new FakeCdpSocket([
JSON.stringify({
href: 'orca-mobile-web://session-a/',
bodyText: 'Agent Session History',
labels: ['Back', 'Refresh agent sessions'],
placeholders: ['Search sessions, repo:, path:']
}),
JSON.stringify({ found: true }),
JSON.stringify({ activated: true }),
JSON.stringify({ updated: true }),
JSON.stringify({ found: true }),
JSON.stringify({ activated: true })
])
const document = {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
}
await expect(
readHostedWebViewState(document, fakeCdpConstructor(socket))
).resolves.toMatchObject({
bodyText: 'Agent Session History',
labels: ['Back', 'Refresh agent sessions']
})
await expect(
activateHostedWebViewControl(
document,
{ kind: 'label', value: 'More session actions', reveal: true },
fakeCdpConstructor(socket)
)
).resolves.toBeUndefined()
await expect(
setHostedWebViewInput(
document,
{ placeholder: 'Search sessions, repo:, path:', value: 'fixture' },
fakeCdpConstructor(socket)
)
).resolves.toBeUndefined()
await expect(
activateHostedWebViewControl(
document,
{ kind: 'text', value: 'mobile-rearch' },
fakeCdpConstructor(socket)
)
).resolves.toBeUndefined()
const expressions = socket.evaluations
.map((evaluation) => evaluation.params.expression)
.join('\n')
expect(expressions).toContain('input[placeholder],textarea[placeholder]')
expect(expressions).toContain('element.getBoundingClientRect()')
expect(expressions).toContain('rect.top < innerHeight')
expect(expressions).toContain('More session actions')
expect(expressions).toContain('closest(\'button,[role="button"],a,[tabindex]\')')
expect(expressions).toContain('getBoundingClientRect()')
expect(expressions).toContain("scrollIntoView({ block: 'nearest', inline: 'nearest' })")
expect(expressions).toContain('data-orca-cdp-activation')
expect(expressions).toContain('__orcaCdpActivationLedger')
expect(expressions).toContain("Object.getOwnPropertyDescriptor(prototype, 'value')")
expect(expressions).toContain("new InputEvent('input'")
expect(expressions).toContain('element.click()')
})
it('requires renderer-loss evidence after requesting a debug process crash', async () => {
const socket = new FakeProcessTerminationSocket()
await expect(
terminateHostedWebViewProcess(
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current' },
fakeCdpConstructor(socket)
)
).resolves.toBeUndefined()
expect(socket.command).toEqual({ id: 1, method: 'Page.crash' })
})
it('observes an ordered reconnect while recording retained route evidence', async () => {
const entries = [
{
state: 'recovering',
href: 'orca-mobile-web://session-a/h/host/agent-history/worktree',
retainedExpectedText: true,
retainedExpectedRoute: true
},
{
state: 'connected',
href: 'orca-mobile-web://session-a/h/host/agent-history/worktree',
retainedExpectedText: true,
retainedExpectedRoute: true
}
]
const socket = new FakeCdpSocket([JSON.stringify({ started: true }), JSON.stringify(entries)])
const document = {
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/current'
}
const WebSocketCtor = fakeCdpConstructor(socket)
await startHostedWebViewConnectionObservation(
document,
{
expectedText: 'Hybrid Agent History Fixture',
expectedHrefIncludes: '/agent-history/'
},
WebSocketCtor
)
await expect(
waitForHostedWebViewConnectionSequence(
document,
['recovering', 'connected'],
1_000,
WebSocketCtor
)
).resolves.toEqual(entries)
const expressions = socket.evaluations
.map((evaluation) => evaluation.params.expression)
.join('\n')
expect(expressions).toContain("addEventListener('message'")
expect(expressions).toContain("message?.type !== 'connection'")
expect(expressions).toContain('requestAnimationFrame')
expect(expressions).toContain('retainedExpectedText')
expect(expressions).toContain('retainedExpectedRoute')
})
it('keeps the native probe DEBUG-only, loopback-only, and completion-marked', () => {
const probeStart = iosShellSource.indexOf(
'private func mobileWebNetworkIsolationProbeUserScript()'
)
const debugStart = iosShellSource.lastIndexOf('#if DEBUG', probeStart)
const debugEnd = iosShellSource.indexOf('#endif', probeStart)
const probeSource = iosShellSource.slice(debugStart, debugEnd)
expect(debugStart).toBeGreaterThanOrEqual(0)
expect(debugEnd).toBeGreaterThan(probeStart)
expect(probeSource).toContain('ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_PORT')
expect(probeSource).toContain('ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_TOKEN')
expect(probeSource).toContain('http://127.0.0.1:')
expect(probeSource).toContain('ws://127.0.0.1:')
expect(probeSource).toContain('completed===4')
expect(probeSource).toContain('__orcaDebugNavigationProbeCompletion')
expect(probeSource).toContain('__orcaDebugExecutableProbeCompletion')
expect(probeSource).toContain('activeDeclaredScriptLoaded')
expect(probeSource).toContain('undeclaredScriptBlocked')
expect(probeSource).toContain('[a-f0-9]{64}\\\\.js$')
expect(probeSource).toContain("script.getAttribute('src')")
expect(probeSource).toContain('new URL(declaredScriptPath(activeScript),location.origin)')
expect(probeSource).toContain("window.open(probeBase+'/popup-probe','_blank')")
expect(probeSource).toContain("frame.src=probeBase+'/redirect-probe'")
expect(probeSource).toContain("download.href=probeBase+'/download-probe'")
expect(probeSource).toContain("navigator.serviceWorker.register(probeBase+'/worker-probe')")
expect(probeSource).toContain("location.assign('orca-security-probe://blocked')")
expect(probeSource).toContain('forMainFrameOnly: true')
expect(iosShellSource).toContain(
'#if DEBUG\n if let networkProbe = mobileWebNetworkIsolationProbeUserScript()'
)
})
it('keeps the Android probe debuggable-only and installs it at document start', () => {
expect(androidProbeSource).toContain('BuildConfig.DEBUG')
expect(androidProbeSource).toContain('ApplicationInfo.FLAG_DEBUGGABLE')
expect(androidProbeSource).toContain(
'WebView.setWebContentsDebuggingEnabled(isMobileWebInspectionEnabled(applicationFlags))'
)
expect(androidProbeSource).toContain('if (!BuildConfig.DEBUG || !isDebuggable) return null')
expect(androidProbeSource).toContain('ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_PORT')
expect(androidProbeSource).toContain('ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_TOKEN')
expect(androidProbeSource).toContain('http://127.0.0.1:')
expect(androidProbeSource).toContain('ws://127.0.0.1:')
expect(androidProbeSource).toContain('globalThis.__orcaRunSecurityProbe=function()')
expect(androidProbeSource).toContain('globalThis.__orcaMobileWebShellListening!==true')
expect(androidProbeSource).toContain('completed===4')
expect(androidProbeSource).toContain('__orcaDebugNavigationProbeCompletion')
expect(androidProbeSource).toContain('__orcaDebugExecutableProbeCompletion')
expect(androidProbeSource).toContain('activeDeclaredScriptLoaded')
expect(androidProbeSource).toContain('undeclaredScriptBlocked')
expect(androidProbeSource).toContain('[a-f0-9]{64}\\.js')
expect(androidProbeSource).toContain("script.getAttribute('src')")
expect(androidProbeSource).toContain(
'new URL(declaredScriptPath(activeScript),location.origin)'
)
expect(androidProbeSource).toContain("window.open(probeBase+'/popup-probe','_blank')")
expect(androidProbeSource).toContain("frame.src=probeBase+'/redirect-probe'")
expect(androidProbeSource).toContain("download.href=probeBase+'/download-probe'")
expect(androidProbeSource).toContain(
"navigator.serviceWorker.register(probeBase+'/worker-probe')"
)
expect(androidProbeSource).toContain("location.assign('orca-security-probe://blocked')")
expect(androidProbeSource).toContain('WebViewFeature.DOCUMENT_START_SCRIPT')
expect(androidProbeSource).toContain('WebViewCompat.addDocumentStartJavaScript')
expect(androidProbeSource).toContain('allowedOrigin: String')
expect(androidProbeSource).toContain('setOf(allowedOrigin)')
expect(androidProbeSource).not.toContain('setOf(MOBILE_WEB_ORIGIN)')
expect(androidShellSource).toContain(
'installMobileWebDebugIsolationProbe(\n webView,\n appContext,\n mobileWebOriginForSession(sessionId)'
)
expect(androidShellSource).toContain('override fun onAttachedToWindow()')
expect(androidShellSource).toContain('addBridgeMessageListener(sessionId)')
expect(androidShellSource).toContain(
'!documentLoaded || currentUrl == null || !isAllowedDocumentUrl(currentUrl)'
)
expect(expoLogBoxPatch).toContain(
'context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0'
)
expect(expoLogBoxPatch).toContain('- setWebContentsDebuggingEnabled(true)')
expect(expoLogBoxPatch).toContain('+ setWebContentsDebuggingEnabled(isDebuggable)')
expect(expoDomWebViewPatch).toContain(
'+ WebView.setWebContentsDebuggingEnabled(webviewDebuggingEnabled && isDebuggable)'
)
expect(reactNativeWebViewPatch).toContain(
'+ RNCWebView.setWebContentsDebuggingEnabled(enabled && isDebuggable)'
)
expect(reactNativeWebViewPatch).toContain(
'+ if (ReactBuildConfig.DEBUG && isDebuggable) {'
)
})
it('requires a production Android image for the Release inspector gate', () => {
expect(androidReleaseHarnessSource).toContain("buildType !== 'user'")
expect(androidReleaseHarnessSource).toContain("debuggable !== '0'")
expect(androidReleaseHarnessSource).toContain("!fingerprint.includes(':user/')")
expect(androidReleaseHarnessSource).toContain("packageFlags.includes('DEBUGGABLE')")
expect(androidReleaseHarnessSource).toContain("'am', 'force-stop'")
expect(androidReleaseHarnessSource).toContain("'uiautomator', 'dump', '/dev/tty'")
expect(androidReleaseHarnessSource).toContain('assertNoInspectorSocket(options, pid)')
expect(androidReleaseHarnessSource).toContain(
'Android Release DevTools discovery endpoint is accessible'
)
})
it('builds and installs the exact native shell before the isolated live gate', () => {
expect(simulatorHarnessSource).toContain('hostedIosSimulatorAppPreparation')
expect(simulatorHarnessSource).toContain(
'const appPreparation = hostedIosSimulatorAppPreparation'
)
expect(simulatorHarnessSource).toContain(
'nativeAppPath = await evidenceStep(appPreparation.label, appPreparation.run)'
)
expect(simulatorAppPreparationSource).toContain("label: 'existing native simulator app'")
expect(simulatorAppPreparationSource).toContain("label: 'cached native simulator app install'")
expect(simulatorAppPreparationSource).toContain("label: 'native simulator app build'")
expect(simulatorHarnessSource).toContain('options.securityOnly')
expect(simulatorHarnessSource).toContain(
"expectedText: options.sourceControlOnly ? '1 tab' : '2 tabs'"
)
expect(simulatorHarnessSource).toContain(
"await evidenceStep('hosted terminal device input journey'"
)
expect(simulatorHarnessSource).toContain(
'terminalDeviceInput.photoPermissionRevocation?.sessionDocument'
)
expect(simulatorReportSource).toContain(
'terminalDeviceInput?.terminalClipboardImagePaste?.evidence'
)
expect(simulatorHarnessSource).toContain('captureHostedWebViewSecurityEvidence')
expect(simulatorSecurityEvidenceSource).toContain('verifyHostedWebViewExecutableIsolation')
expect(simulatorSecurityEvidenceSource).toContain('verifyHostedWebViewPrivacyIsolation')
expect(simulatorHarnessSource).toContain("await evidenceStep('Photos permission reset'")
expect(simulatorHarnessSource).toContain('await clearHostedIosWebViewSecurityProbe(deviceUdid)')
expect(simulatorAppBuildSource).toContain("'xcodebuild'")
expect(simulatorAppBuildSource).toContain("'simctl', 'install', deviceUdid, appPath")
expect(simulatorAppBuildSource).not.toContain('CODE_SIGNING_ALLOWED=NO')
expect(simulatorLauncherSource).toContain("EXPO_PUBLIC_ORCA_E2E_MOBILE_NATIVE_BASELINE: '1'")
const inspectorStart = simulatorHarnessSource.indexOf('inspector = await startCdpServer')
const nativeCapture = simulatorHarnessSource.indexOf("'native workspace baseline'")
const exclusion = simulatorHarnessSource.indexOf(
'assertNoHostedMobileWebCdpTarget',
inspectorStart
)
const handoff = simulatorHarnessSource.indexOf("'native hybrid route handoff'")
const hostedWait = simulatorHarnessSource.indexOf('let workspaceDocument = await')
expect(inspectorStart).toBeGreaterThanOrEqual(0)
expect(exclusion).toBeGreaterThan(inspectorStart)
expect(nativeCapture).toBeGreaterThan(exclusion)
expect(handoff).toBeGreaterThan(nativeCapture)
expect(hostedWait).toBeGreaterThan(handoff)
})
it('installs and launches the exact Android shell with a proven sentinel', () => {
expect(androidSecurityHarnessSource).toContain("['install', '-r', '-t', options.apk]")
expect(androidSecurityHarnessSource).toContain(
"['reverse', `tcp:${probe.port}`, `tcp:${probe.port}`]"
)
expect(androidSecurityHarnessSource).toContain("'nc', '-z', '-w', '5', '127.0.0.1'")
expect(androidSecurityHarnessSource).toContain("includes('tcp:connection')")
expect(androidSecurityHarnessSource).toContain('probe.reset()')
expect(androidSecurityHarnessSource).toContain("'ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_PORT'")
expect(androidSecurityHarnessSource).toContain("'ORCA_E2E_MOBILE_WEB_NETWORK_PROBE_TOKEN'")
expect(androidSecurityHarnessSource).toContain('waitForVisibleHostedWebView')
expect(androidSecurityHarnessSource).toContain('verifyHostedWebViewNetworkIsolation')
expect(androidSecurityHarnessSource).toContain('verifyHostedWebViewNavigationIsolation')
expect(androidSecurityHarnessSource).toContain('verifyHostedWebViewExecutableIsolation')
expect(androidSecurityHarnessSource).toContain('verifyHostedWebViewPrivacyIsolation')
expect(androidSecurityHarnessSource).toContain('probe.observations.length > 0')
})
it('crashes three Android renderers and requires native activation rollback', () => {
expect(androidCrashLoopHarnessSource).toContain('const failureCount = 3')
expect(androidCrashLoopHarnessSource).toContain('terminateHostedWebViewProcess(document)')
expect(androidCrashLoopHarnessSource).toContain('initial.previous')
expect(androidCrashLoopHarnessSource).toContain('waitForAndroidActivation(')
expect(androidCrashLoopHarnessSource).toContain('>= 60_000')
expect(androidCrashLoopHarnessSource).toContain('documents.at(-1)?.href === documents[0]?.href')
})
})
@@ -1,80 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { waitForHostedWebViewConnectionSequence } from '../../scripts/hosted-webview-cdp-session.mjs'
describe('hosted WebView CDP target replacement', () => {
it('reacquires the document while observing reconnect', async () => {
const recovering = connectionEntry('recovering')
const connected = connectionEntry('connected')
const stale = new TargetSocket([
JSON.stringify([recovering]),
new Error('target replaced'),
new Error('target replaced'),
new Error('target replaced')
])
const replacement = new TargetSocket([JSON.stringify([connected])])
const WebSocketCtor = class {
constructor(endpoint: string) {
return endpoint.endsWith('/stale') ? stale : replacement
}
}
const reacquireDocument = vi.fn().mockResolvedValue({
webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/replacement'
})
await expect(
waitForHostedWebViewConnectionSequence(
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/stale' },
['recovering', 'connected'],
5_000,
{ WebSocketCtor, reacquireDocument }
)
).resolves.toEqual([recovering, connected])
expect(reacquireDocument).toHaveBeenCalledOnce()
})
})
function connectionEntry(state: string) {
return {
state,
href: 'orca-mobile-web://session-a/h/host/agent-history/worktree',
retainedExpectedText: true,
retainedExpectedRoute: true
}
}
class TargetSocket {
private readonly values: (string | Error)[]
private errorListener: ((error: Error) => void) | undefined
private messageListener: ((data: Buffer) => void) | undefined
constructor(values: (string | Error)[]) {
this.values = [...values]
}
once(event: string, listener: (value?: unknown) => void): void {
if (event === 'open') {
queueMicrotask(listener)
} else if (event === 'error') {
this.errorListener = listener as (error: Error) => void
}
}
on(event: string, listener: (data: Buffer) => void): void {
if (event === 'message') {
this.messageListener = listener
}
}
send(): void {
const value = this.values.shift() ?? ''
queueMicrotask(() => {
if (value instanceof Error) {
this.errorListener?.(value)
return
}
this.messageListener?.(Buffer.from(JSON.stringify({ id: 1, result: { result: { value } } })))
})
}
close(): void {}
}
@@ -1,75 +0,0 @@
export class FakeCdpSocket {
evaluations: {
id: number
params: { expression: string; returnByValue: boolean }
}[] = []
private readonly values: string[]
private messageListener: ((data: Buffer) => void) | undefined
constructor(values: string[]) {
this.values = [...values]
}
once(event: string, listener: (value?: unknown) => void): void {
if (event === 'open') {
queueMicrotask(listener)
}
}
on(event: string, listener: (data: Buffer) => void): void {
if (event === 'message') {
this.messageListener = listener
}
}
send(payload: string): void {
this.evaluations.push(JSON.parse(payload))
const value = this.values.shift() ?? ''
queueMicrotask(() => {
this.messageListener?.(Buffer.from(JSON.stringify({ id: 1, result: { result: { value } } })))
})
}
close(): void {}
}
export class FakeProcessTerminationSocket {
command: unknown
private messageListener: ((data: Buffer) => void) | undefined
once(event: string, listener: (value?: unknown) => void): void {
if (event === 'open') {
queueMicrotask(listener)
}
}
on(event: string, listener: (data: Buffer) => void): void {
if (event === 'message') {
this.messageListener = listener
}
}
send(payload: string): void {
this.command = JSON.parse(payload)
queueMicrotask(() => {
this.messageListener?.(
Buffer.from(
JSON.stringify({
method: 'Inspector.detached',
params: { reason: 'Render process gone.' }
})
)
)
})
}
close(): void {}
}
export function fakeCdpConstructor(socket: FakeCdpSocket | FakeProcessTerminationSocket) {
return class {
constructor() {
return socket
}
}
}
@@ -1,35 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { dispatchHostedWebViewLongPress } from '../../scripts/hosted-webview-long-press.mjs'
describe('hosted WebView long press', () => {
it('holds the unchanged RNW control after native touch grants shell authority', async () => {
const evaluate = vi
.fn()
.mockResolvedValueOnce('hosted-long-press-123')
.mockResolvedValueOnce('hosted-long-press-123')
const wait = vi.fn().mockResolvedValue(undefined)
await dispatchHostedWebViewLongPress(
{ href: 'orca-mobile-web://build/session/workspace' },
'Attach a photo',
{ evaluate, now: () => 123, wait }
)
expect(evaluate.mock.calls[0]?.[1]).toContain(
"element.dispatchEvent(pointerEvent(element, 'mousedown', 1))"
)
expect(evaluate.mock.calls[0]?.[1]).toContain('document.getSelection()?.removeAllRanges()')
expect(wait).toHaveBeenCalledWith(600)
expect(evaluate.mock.calls[1]?.[1]).toContain("new MouseEvent('mouseup'")
})
it('fails closed when the hosted control cannot be located', async () => {
await expect(
dispatchHostedWebViewLongPress({}, 'Attach a photo', {
evaluate: vi.fn().mockResolvedValue(''),
now: () => 123,
wait: vi.fn()
})
).rejects.toThrow('Hosted WebView control was not found')
})
})
@@ -1,100 +0,0 @@
import { describe, expect, it } from 'vitest'
import { verifyHostedWebViewPrivacyIsolation } from '../../scripts/hosted-webview-privacy-isolation.mjs'
describe('hosted WebView privacy isolation', () => {
it('accepts an empty private-origin page state', async () => {
const socket = new FakeCdpSocket({
href: 'orca-mobile-web://session-a/h/paired-orca-desktop',
htmlBytes: 4096,
domMarkers: [],
historyMarkers: [],
localStorageEntries: 0,
sessionStorageEntries: 0,
cookieBytes: 0
})
await expect(
verifyHostedWebViewPrivacyIsolation({
document: { webSocketDebuggerUrl: 'ws://localhost/page' },
WebSocketCtor: fakeCdpConstructor(socket)
})
).resolves.toMatchObject({
privateOrigin: true,
credentialedUrl: false,
domCredentialMarkers: 0
})
expect(socket.expression).toContain('document.documentElement?.outerHTML')
})
it.each([
{ domMarkers: ['devicetoken'] },
{ historyMarkers: ['publickeyb64'] },
{ localStorageEntries: 1 },
{ sessionStorageEntries: 1 },
{ cookieBytes: 1 },
{ href: 'https://user:secret@orca-mobile-web.invalid/' },
{ href: 'https://orca-mobile-web.invalid/?credential=secret' }
])('rejects privileged page state: %j', async (overrides) => {
const socket = new FakeCdpSocket({
href: 'https://orca-mobile-web.invalid/#session-a',
htmlBytes: 4096,
domMarkers: [],
historyMarkers: [],
localStorageEntries: 0,
sessionStorageEntries: 0,
cookieBytes: 0,
...overrides
})
await expect(
verifyHostedWebViewPrivacyIsolation({
document: { webSocketDebuggerUrl: 'ws://localhost/page' },
WebSocketCtor: fakeCdpConstructor(socket)
})
).rejects.toThrow('privacy isolation failed')
})
})
class FakeCdpSocket {
expression = ''
private messageListener: ((data: Buffer) => void) | undefined
constructor(private readonly value: unknown) {}
once(event: string, listener: () => void): void {
if (event === 'open') {
queueMicrotask(listener)
}
}
on(event: string, listener: (data: Buffer) => void): void {
if (event === 'message') {
this.messageListener = listener
}
}
send(payload: string): void {
const request = JSON.parse(payload)
this.expression = request.params.expression
queueMicrotask(() => {
this.messageListener?.(
Buffer.from(
JSON.stringify({
id: 1,
result: { result: { value: JSON.stringify(this.value) } }
})
)
)
})
}
close(): void {}
}
function fakeCdpConstructor(socket: FakeCdpSocket) {
return class {
constructor() {
return socket
}
}
}
@@ -1,48 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { evaluate } = vi.hoisted(() => ({ evaluate: vi.fn() }))
vi.mock('../../scripts/hosted-webview-cdp-session.mjs', () => ({
evaluateHostedDocumentWithRetry: evaluate
}))
import {
installHostedWebViewRouteExceptionCapture,
readHostedWebViewRouteExceptionEvidence
} from '../../scripts/hosted-webview-route-exception-evidence.mjs'
describe('hosted WebView route exception evidence', () => {
beforeEach(() => {
evaluate.mockReset()
})
it('installs a bounded error-only capture in the hosted document', async () => {
evaluate.mockResolvedValue('installed')
const WebSocketCtor = class {}
await installHostedWebViewRouteExceptionCapture({ targetId: 'hosted' }, WebSocketCtor)
expect(evaluate).toHaveBeenCalledWith(
{ targetId: 'hosted' },
expect.stringContaining("append('unhandled-rejection'"),
WebSocketCtor
)
expect(evaluate.mock.calls[0][1]).toContain('entries.length > 24')
expect(evaluate.mock.calls[0][1]).toContain('.slice(0, 4096)')
})
it('returns only bounded exception evidence entries', async () => {
evaluate.mockResolvedValue(
JSON.stringify([
{ kind: 'console-error', text: 'TypeError: failed' },
{ kind: 'other', text: 'ignored' },
{ kind: 'window-error', text: 'x'.repeat(4_097) }
])
)
const WebSocketCtor = class {}
await expect(
readHostedWebViewRouteExceptionEvidence({ targetId: 'hosted' }, WebSocketCtor)
).resolves.toEqual([{ kind: 'console-error', text: 'TypeError: failed' }])
})
})
@@ -1,65 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { navigateHostedWebViewRoute } from '../../scripts/hosted-webview-route-navigation.mjs'
describe('hosted WebView route navigation', () => {
it('moves within the authenticated hosted SPA without a document navigation', async () => {
const socket = cdpSocket('/h/host/review/workspace')
await expect(
navigateHostedWebViewRoute(
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/1' },
'/h/host/review/workspace?scope=all',
socket.WebSocket
)
).resolves.toBeUndefined()
expect(socket.expression()).toContain('history.pushState')
expect(socket.expression()).toContain("dispatchEvent(new PopStateEvent('popstate'")
})
it('rejects routes outside the hosted app graph', async () => {
const socket = cdpSocket('')
await expect(
navigateHostedWebViewRoute(
{ webSocketDebuggerUrl: 'ws://127.0.0.1/devtools/page/1' },
'https://example.test/',
socket.WebSocket
)
).rejects.toThrow('route is invalid')
expect(socket.expression()).toBe('')
})
})
function cdpSocket(value: string) {
let expression = ''
class FakeWebSocket {
private message: ((data: Buffer) => void) | undefined
once(event: string, listener: (error?: Error) => void) {
if (event === 'open') {
queueMicrotask(listener)
}
}
on(event: string, listener: (data: Buffer) => void) {
if (event === 'message') {
this.message = listener
}
}
send(payload: string) {
expression = JSON.parse(payload).params.expression
queueMicrotask(() => {
this.message?.(Buffer.from(JSON.stringify({ id: 1, result: { result: { value } } })))
})
}
close() {}
}
return {
WebSocket: FakeWebSocket as never,
expression: vi.fn(() => expression)
}
}
@@ -1,32 +0,0 @@
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { resolveHostedWebViewRuntimeDirectory } from '../../scripts/hosted-webview-runtime-directory.mjs'
describe('hosted WebView runtime directory', () => {
it('isolates each run while keeping the socket path short', () => {
const first = resolveHostedWebViewRuntimeDirectory({
worktree: '/repo/mobile-rearch',
runNonce: 'run-one'
})
const second = resolveHostedWebViewRuntimeDirectory({
worktree: '/repo/mobile-rearch',
runNonce: 'run-two'
})
expect(first).not.toBe(second)
expect(first).toMatch(/^\/tmp\/orca-mw-[a-f0-9]{8}-run-one$/)
expect(
path.join(first, 'emulator-control/userData/daemon/daemon-v27.sock').length
).toBeLessThan(104)
})
it('honors an explicit evidence directory', () => {
expect(
resolveHostedWebViewRuntimeDirectory({
worktree: '/repo/mobile-rearch',
override: './evidence',
runNonce: 'ignored'
})
).toBe(path.resolve('./evidence'))
})
})
@@ -1,124 +0,0 @@
import { describe, expect, it } from 'vitest'
import { parseHostedWebViewSimulatorE2eOptions } from '../../scripts/hosted-webview-simulator-e2e-options.mjs'
describe('hosted WebView simulator E2E options', () => {
it('retains bounded defaults', () => {
expect(parseHostedWebViewSimulatorE2eOptions([])).toEqual({
accountsOnly: false,
adversarialContent: false,
clipboardImageOnly: false,
device: 'iPhone 17 Pro',
expectedBuild: undefined,
filesPreviewOnly: false,
isolationOnly: false,
nativeSettingsOnly: false,
photosRevocationOnly: false,
reuseNativeInstall: false,
securityOnly: false,
skipNativeBuild: false,
sourceControlOnly: false,
timeoutMs: 180_000
})
})
it('reuses an installed app without selecting the cached reinstall path', () => {
expect(parseHostedWebViewSimulatorE2eOptions(['--reuse-native-install'])).toMatchObject({
reuseNativeInstall: true,
skipNativeBuild: false
})
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--reuse-native-install', '--skip-native-build'])
).toThrow('mutually exclusive')
})
it('accepts only canonical expected build identities', () => {
const buildId = 'a'.repeat(64)
expect(parseHostedWebViewSimulatorE2eOptions(['--expected-build', buildId])).toMatchObject({
expectedBuild: buildId
})
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--expected-build', 'A'.repeat(64)])
).toThrow('lowercase SHA-256')
})
it('parses the focused cached-app journey', () => {
expect(
parseHostedWebViewSimulatorE2eOptions([
'--',
'--device',
'simulator-id',
'--timeout-ms',
'60000',
'--native-settings-only',
'--skip-native-build'
])
).toMatchObject({
device: 'simulator-id',
nativeSettingsOnly: true,
skipNativeBuild: true,
timeoutMs: 60_000
})
})
it('maps the clipboard-image journey onto focused security setup', () => {
expect(parseHostedWebViewSimulatorE2eOptions(['--clipboard-image-only'])).toMatchObject({
clipboardImageOnly: true,
securityOnly: true
})
})
it('maps the Photos revocation journey onto focused security setup', () => {
expect(parseHostedWebViewSimulatorE2eOptions(['--photos-revocation-only'])).toMatchObject({
photosRevocationOnly: true,
securityOnly: true
})
})
it('maps WebView isolation onto focused security setup', () => {
expect(parseHostedWebViewSimulatorE2eOptions(['--isolation-only'])).toMatchObject({
isolationOnly: true,
securityOnly: true
})
})
it('maps adversarial content onto the focused Source Control journey', () => {
expect(parseHostedWebViewSimulatorE2eOptions(['--adversarial-content'])).toMatchObject({
adversarialContent: true,
sourceControlOnly: true
})
})
it('rejects mutually exclusive focused journeys', () => {
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--accounts-only', '--source-control-only'])
).toThrow('mutually exclusive')
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--clipboard-image-only', '--security-only'])
).toThrow('mutually exclusive')
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--clipboard-image-only', '--photos-revocation-only'])
).toThrow('mutually exclusive')
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--isolation-only', '--security-only'])
).toThrow('mutually exclusive')
})
it('keeps the real chat/session journey exclusive from OTA and adversarial runs', () => {
expect(
parseHostedWebViewSimulatorE2eOptions(['--chat-only', '--reuse-native-install'])
).toMatchObject({ chatOnly: true, reuseNativeInstall: true })
expect(() => parseHostedWebViewSimulatorE2eOptions(['--chat-only', '--ota-only'])).toThrow(
'mutually exclusive'
)
expect(() =>
parseHostedWebViewSimulatorE2eOptions(['--chat-only', '--adversarial-content'])
).toThrow('mutually exclusive')
})
it('rejects invalid timeouts and unknown arguments', () => {
expect(() => parseHostedWebViewSimulatorE2eOptions(['--timeout-ms', '9999'])).toThrow(
'at least 10000'
)
expect(() => parseHostedWebViewSimulatorE2eOptions(['--unknown'])).toThrow('Unknown argument')
})
})
@@ -1,162 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { activateHostedWorkspaceRow } from '../../scripts/hosted-webview-workspace-activation.mjs'
describe('hosted WebView workspace activation', () => {
it('prefers the existing accessible workspace-row label', async () => {
const activate = vi.fn().mockResolvedValue(undefined)
const document = { href: 'orca-mobile-web://build/' }
await activateHostedWorkspaceRow(document, 'mobile-rearch', activate)
expect(activate).toHaveBeenCalledOnce()
expect(activate).toHaveBeenCalledWith(document, {
kind: 'label',
value: 'Open mobile-rearch',
reveal: true
})
})
it('reveals a grouped text row when accessible row labels are absent', async () => {
const missing = new Error('Hosted WebView control was not found: mobile-rearch')
const activate = vi
.fn()
.mockRejectedValueOnce(missing)
.mockRejectedValueOnce(missing)
.mockResolvedValueOnce(undefined)
.mockResolvedValueOnce(undefined)
await activateHostedWorkspaceRow({}, 'mobile-rearch', activate)
expect(activate).toHaveBeenNthCalledWith(
2,
{},
{
kind: 'text',
value: 'mobile-rearch',
ignoreCase: true,
occurrence: 1,
reveal: true
}
)
expect(activate).toHaveBeenNthCalledWith(
3,
{},
{
kind: 'text',
value: 'mobile-rearch',
ignoreCase: true,
reveal: true
}
)
expect(activate).toHaveBeenNthCalledWith(
4,
{},
{
kind: 'text',
value: 'mobile-rearch',
ignoreCase: true,
occurrence: 1,
reveal: true
}
)
})
it('reacquires a replaced WebKit document before retrying', async () => {
const stale = new Error('WebKit CDP connection closed')
const activate = vi.fn().mockRejectedValueOnce(stale).mockResolvedValueOnce(undefined)
const replacement = { href: 'orca-mobile-web://replacement/' }
const resolveDocument = vi.fn().mockResolvedValue(replacement)
await activateHostedWorkspaceRow(
{ href: 'orca-mobile-web://stale/' },
'mobile-rearch',
activate,
1_000,
resolveDocument
)
expect(resolveDocument).toHaveBeenCalledOnce()
expect(activate).toHaveBeenLastCalledWith(replacement, {
kind: 'label',
value: 'Open mobile-rearch',
reveal: true
})
})
it('reacquires an Android target replaced during its inspector handshake', async () => {
const stale = new Error('Unexpected server response: 500')
const activate = vi.fn().mockRejectedValueOnce(stale).mockResolvedValueOnce(undefined)
const replacement = { href: 'https://orca-mobile-web.invalid/' }
const resolveDocument = vi.fn().mockResolvedValue(replacement)
await activateHostedWorkspaceRow(
{ href: 'https://orca-mobile-web.invalid/' },
'mobile-rearch',
activate,
1_000,
resolveDocument
)
expect(resolveDocument).toHaveBeenCalledOnce()
expect(activate).toHaveBeenLastCalledWith(replacement, {
kind: 'label',
value: 'Open mobile-rearch',
reveal: true
})
})
it('retries after a grouped row is not visible immediately', async () => {
const missing = new Error('Hosted WebView control was not found: mobile-rearch')
const activate = vi
.fn()
.mockRejectedValueOnce(missing)
.mockRejectedValueOnce(missing)
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(missing)
.mockRejectedValueOnce(missing)
.mockResolvedValueOnce(undefined)
await activateHostedWorkspaceRow({}, 'mobile-rearch', activate, 1_000)
expect(activate).toHaveBeenCalledTimes(6)
expect(activate).toHaveBeenLastCalledWith({}, expect.objectContaining({ reveal: true }))
})
it('accepts activation of the only matching text row', async () => {
const missing = new Error('Hosted WebView control was not found: mobile-rearch')
const stale = new Error('WebKit CDP connection closed')
const activate = vi
.fn()
.mockRejectedValueOnce(missing)
.mockRejectedValueOnce(missing)
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(stale)
await activateHostedWorkspaceRow({}, 'mobile-rearch', activate)
expect(activate).toHaveBeenCalledTimes(4)
})
it('accepts an SPA route transition after the activated row disappears', async () => {
const missing = new Error('Hosted WebView control was not found: mobile-rearch')
const activate = vi
.fn()
.mockRejectedValueOnce(missing)
.mockRejectedValueOnce(missing)
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(missing)
const resolveDocument = vi.fn().mockResolvedValue({
href: 'https://orca-mobile-web.invalid/h/host/session/workspace'
})
await activateHostedWorkspaceRow(
{ href: 'https://orca-mobile-web.invalid/h/host' },
'mobile-rearch',
activate,
1_000,
resolveDocument
)
expect(resolveDocument).toHaveBeenCalledOnce()
})
})
@@ -1,248 +0,0 @@
/// <reference types="vite/client" />
import { describe, expect, it, vi } from 'vitest'
import { z, ZodType } from 'zod'
import {
MOBILE_WEB_BRIDGE_OPERATIONS,
MOBILE_WEB_BRIDGE_PROTOCOL_VERSION,
parseMobileWebBridgeShellMessage,
type MobileWebBridgePageMessage,
type MobileWebBridgeShellMessage
} from '../../../src/shared/mobile-web/bridge-contract'
import { MobileWebBridgeSubscriptionClient } from '../../../src/mobile-web/src/mobile-web-bridge-subscription-client'
import type { MobileWebBridgeSubscriptionSetup } from '../../../src/mobile-web/src/mobile-web-bridge-subscription-setup'
import { MobileWebOneShotRequestClient } from '../../../src/mobile-web/src/mobile-web-one-shot-request-client'
import { MobileWebHostResultSchema } from '../../../src/shared/mobile-web/host-rpc-contract'
import { MOBILE_WEB_PRODUCTION_GRANTS } from './mobile-web-production-grants'
const CONTEXT = {
shellSessionId: 'S'.repeat(43),
buildId: 'a'.repeat(64)
}
const REQUEST_ID = 'R'.repeat(22)
const SUBSCRIPTION_ID = 'U'.repeat(22)
const RESPONSE_PAYLOAD_CORPUS: unknown[] = [
null,
true,
false,
0,
1.5,
'',
'payload',
[],
{},
{ unexpected: true }
]
const contractModules = import.meta.glob(
[
'../../../src/shared/mobile-web/*-contract.ts',
'!../../../src/shared/mobile-web/bridge-operation-contract.ts'
],
{ eager: true }
)
type NamedSchema = { name: string; schema: ZodType }
describe('mobile web shell response schema corpus', () => {
const resultSchemas = namedSchemas('ResultSchema')
const eventSchemas = namedSchemas('EventSchema')
it('keeps the result and event schema corpus aligned with production grant kinds', () => {
const registeredOperations = Object.values(MOBILE_WEB_BRIDGE_OPERATIONS).flatMap((operations) =>
Object.keys(operations)
)
const oneShotGrants = MOBILE_WEB_PRODUCTION_GRANTS.filter(
(grant) => grant.operation !== 'subscribe'
)
const subscriptionGrants = MOBILE_WEB_PRODUCTION_GRANTS.filter(
(grant) => grant.operation === 'subscribe'
)
expect(resultSchemas.length).toBeGreaterThanOrEqual(150)
expect(oneShotGrants.length).toBeGreaterThan(100)
expect(eventSchemas.length).toBeGreaterThanOrEqual(subscriptionGrants.length)
expect(MOBILE_WEB_PRODUCTION_GRANTS).toHaveLength(registeredOperations.length)
expect(new Set(resultSchemas.map(({ name }) => name))).toHaveLength(resultSchemas.length)
expect(new Set(eventSchemas.map(({ name }) => name))).toHaveLength(eventSchemas.length)
})
it('rejects invalid success payloads through every one-shot result schema', async () => {
let caseCount = 0
for (const { name, schema } of resultSchemas) {
// Domain payloads on the generic lane are interpreted by the matching hosted page.
if (schema === MobileWebHostResultSchema) {
continue
}
const rejected = RESPONSE_PAYLOAD_CORPUS.filter((payload) => pageRejects(schema, payload))
expect(rejected.length, name).toBeGreaterThanOrEqual(8)
for (const payload of rejected) {
await expectOneShotRejection(schema, payload, name)
caseCount += 1
}
}
expect(caseCount).toBeGreaterThan(1_200)
})
it('keeps generic host domain results opaque to the shell contract', () => {
expect(resultSchemas.filter(({ schema }) => schema === MobileWebHostResultSchema)).toHaveLength(
1
)
for (const payload of RESPONSE_PAYLOAD_CORPUS) {
expect(MobileWebHostResultSchema.parse(payload)).toEqual(payload)
}
})
it('retires every subscription after an invalid event payload', async () => {
let caseCount = 0
for (const { name, schema } of eventSchemas) {
const rejected = RESPONSE_PAYLOAD_CORPUS.filter((payload) => pageRejects(schema, payload))
expect(rejected.length, name).toBeGreaterThanOrEqual(8)
for (const payload of rejected) {
await expectSubscriptionRejection(schema, payload, name)
caseCount += 1
}
}
expect(caseCount).toBeGreaterThanOrEqual(64)
})
})
function pageRejects(schema: ZodType, payload: unknown): boolean {
return !schema.safeParse(payload).success
}
function namedSchemas(suffix: 'ResultSchema' | 'EventSchema'): NamedSchema[] {
const schemas: NamedSchema[] = []
for (const [path, module] of Object.entries(contractModules)) {
for (const [name, schema] of Object.entries(module as Record<string, unknown>)) {
if (name.endsWith(suffix) && schema instanceof ZodType) {
schemas.push({ name: `${path}:${name}`, schema })
}
}
}
return schemas.sort((left, right) => left.name.localeCompare(right.name))
}
async function expectOneShotRejection(
resultSchema: ZodType,
payload: unknown,
label: string
): Promise<void> {
const client = new MobileWebOneShotRequestClient({
getGrant: () => grant('snapshot'),
postMessage: () => true,
envelope: () => ({ version: MOBILE_WEB_BRIDGE_PROTOCOL_VERSION, ...CONTEXT }),
createRequestId: () => REQUEST_ID,
otherPendingCount: () => 0
})
const result = client.request('workspace', 'snapshot', {}, z.object({}).strict(), resultSchema)
const rejection = expect(result, `${label}: ${JSON.stringify(payload)}`).rejects.toMatchObject({
code: 'invalid_message',
retryable: false
})
expect(client.receive(parsedShellMessage(successResponse(payload)))).toBe(true)
await rejection
expect(client.pendingCount()).toBe(0)
}
async function expectSubscriptionRejection(
eventSchema: ZodType,
payload: unknown,
label: string
): Promise<void> {
const messages: MobileWebBridgePageMessage[] = []
const onEvent = vi.fn()
const onError = vi.fn()
const ids = [REQUEST_ID, SUBSCRIPTION_ID]
const client = new MobileWebBridgeSubscriptionClient({
getGrant: () => grant('subscribe'),
postMessage: (message) => {
messages.push(message)
return true
},
envelope: () => ({ version: MOBILE_WEB_BRIDGE_PROTOCOL_VERSION, ...CONTEXT }),
createMessageId: () => ids.shift() ?? 'Z'.repeat(22),
otherPendingCount: () => 0
})
const subscription = exposeSubscribeWith(client)({
capability: 'workspace',
payload: {},
payloadSchema: z.object({}).strict(),
eventSchema,
onEvent,
onError
})
client.receive(parsedShellMessage(successResponse(null)))
await subscription.ready
expect(client.receive(parsedShellMessage(eventMessage(payload)))).toBe(true)
expect(onEvent, label).not.toHaveBeenCalled()
expect(onError, label).toHaveBeenCalledWith(
expect.objectContaining({ code: 'invalid_message', retryable: false })
)
expect(messages.at(-1)).toMatchObject({
type: 'cancel',
target: 'subscription',
id: SUBSCRIPTION_ID
})
}
function exposeSubscribeWith(client: MobileWebBridgeSubscriptionClient): (
setup: MobileWebBridgeSubscriptionSetup
) => {
ready: Promise<void>
unsubscribe: () => void
subscriptionId: string
} {
return (
client as unknown as {
subscribeWith: ReturnType<typeof exposeSubscribeWith>
}
).subscribeWith.bind(client)
}
function grant(operation: 'snapshot' | 'subscribe') {
return {
capability: 'workspace',
operation,
limits: {
maxRequestBytes: 1024,
maxResponseBytes: 1024,
maxConcurrent: 2,
rateCapacity: 4,
rateRefillPerSecond: 1
}
} as const
}
function successResponse(
payload: unknown
): Extract<MobileWebBridgeShellMessage, { type: 'response' }> {
return {
version: MOBILE_WEB_BRIDGE_PROTOCOL_VERSION,
...CONTEXT,
type: 'response',
requestId: REQUEST_ID,
status: 'success',
payload
}
}
function eventMessage(payload: unknown): Extract<MobileWebBridgeShellMessage, { type: 'event' }> {
return {
version: MOBILE_WEB_BRIDGE_PROTOCOL_VERSION,
...CONTEXT,
type: 'event',
subscriptionId: SUBSCRIPTION_ID,
sequence: 0,
payload
}
}
function parsedShellMessage(message: MobileWebBridgeShellMessage): MobileWebBridgeShellMessage {
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
if (!parsed.ok) {
throw new Error(`Corpus produced ${parsed.error}`)
}
return parsed.value
}
@@ -43,8 +43,6 @@ describe('mobile web session screen binding', () => {
expect(hostedSessionRoute).toContain('lastConnectedAt={shell.lastConnectedAt}')
expect(hostedSessionRoute).toContain('<SessionScreen')
expect(nativeSessionRoute).toContain('export function SessionScreen(')
// The native-only API bans moved to hosted-route-native-api-census.test.ts, which walks
// every module the hosted session route reaches rather than this fixed file list.
expect(nativeSessionRoute).toContain('sessionDeviceOperations?.openTerminalSettings()')
expect(nativeSessionRoute).toContain(
'sessionDeviceOperations?.loadTerminalAccessoryPreferences()'
@@ -1,309 +0,0 @@
import { readFileSync, readdirSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { MOBILE_WEB_BRIDGE_OPERATIONS } from './bridge-operation-registry'
const PAGE_DIR = resolve(__dirname, '..', '..', 'mobile-web', 'src')
/** Result fields each page request client compares against its own request before resolving. A
* shell that answers one workspace's request with another workspace's payload is caught here, so
* losing a field silently re-opens that swap. */
const EXPECTED_ECHO_FIELDS: Record<string, readonly string[]> = {
'account.consumeResetCredit': ['scope'],
'file.markdownDraftRead': ['relativePath', 'tabId', 'workspaceId'],
'file.markdownRead': ['relativePath', 'tabId', 'workspaceId'],
'file.markdownSave': ['relativePath', 'tabId', 'workspaceId'],
'file.readTerminalArtifactChunk': ['offset', 'tabId', 'token', 'workspaceId'],
'file.resolveTerminalPath': ['workspaceId'],
'file.write': ['byteLength', 'relativePath', 'revision', 'workspaceId'],
'provider.manageReview': ['action', 'provider', 'reviewNumber', 'workspaceId'],
'provider.mutateReview': ['provider', 'reviewNumber', 'workspaceId'],
'provider.review': ['branch', 'observedHead', 'workspaceId'],
'provider.reviewCreate': ['provider', 'workspaceId'],
'provider.reviewCreationEligibility': ['branch', 'observedHead', 'workspaceId'],
'provider.reviewDiff': [
'branch',
'observedHead',
'path',
'provider',
'reviewHead',
'reviewNumber',
'workspaceId'
],
'provider.reviewGenerateFields': ['workspaceId'],
'provider.reviewQuery': ['provider', 'query', 'reviewNumber', 'workspaceId'],
'provider.submitReview': [
'action',
'expectedReviewHead',
'provider',
'reviewNumber',
'submissionId',
'submittedCommentIds',
'workspaceId'
],
'sourceControl.abort': ['operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.branch': ['branch', 'operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.branchCompare': ['baseRef', 'offset', 'revision', 'workspaceId'],
'sourceControl.branches': ['workspaceId'],
'sourceControl.cancelCommitMessageGeneration': ['workspaceId'],
'sourceControl.commit': ['previousHead', 'workspaceId'],
'sourceControl.commitCompare': ['commitId', 'workspaceId'],
'sourceControl.discard': ['operation', 'relativePaths.length', 'workspaceId'],
'sourceControl.fetch': ['operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.generateCommitMessage': ['previousHead', 'workspaceId'],
'sourceControl.history': ['limit', 'workspaceId'],
'sourceControl.pull': ['operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.push': ['operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.rebase': ['operation', 'previousBranch', 'previousHead', 'workspaceId'],
'sourceControl.reviewDiff': ['relativePath', 'scope', 'workspaceId'],
'sourceControl.reviewLink': ['workspaceId'],
'sourceControl.reviewLinkUpdate': ['workspaceId'],
'sourceControl.reviewMetadata': ['workspaceId'],
'sourceControl.reviewMetadataUpdate': ['workspaceId'],
'sourceControl.stage': ['operation', 'relativePaths.length', 'workspaceId'],
'sourceControl.unstage': ['operation', 'relativePaths.length', 'workspaceId'],
'sourceControl.upstream': ['workspaceId'],
'task.loadLinearDetail': ['issue.targetId'],
'task.loadLinearIssue': ['issue.targetId'],
'task.projectTable': ['project', 'selectedView.id'],
'task.resolveProjectRef': ['host'],
'workspace.activate': ['workspaceId'],
'workspace.creationSaveSparsePreset': ['directories', 'id', 'name', 'repoId'],
'workspace.creationSparsePresets': ['repoId'],
'workspace.creationSshConnect': ['targetId'],
'workspace.creationSshState': ['targetId'],
'workspace.remove': ['workspaceId'],
'workspace.update': ['workspaceId']
}
/** Echo helpers shared across request clients, and the result fields each one compares. Resolved
* by name because the derivation below only walks helpers defined in the same file. */
const SHARED_ECHO_HELPERS: Record<string, readonly string[]> = {
requireEchoedWorkspaceId: ['workspaceId']
}
function balancedBody(text: string, from: number): string | null {
let depth = 0
let index = from
let opened = false
for (; index < text.length; index++) {
const character = text[index]
if (character === '(') {
depth++
opened = true
} else if (character === ')') {
depth--
if (opened && depth === 0) {
index++
break
}
}
}
const start = text.indexOf('{', index)
if (start === -1) {
return null
}
let braces = 0
for (let cursor = start; cursor < text.length; cursor++) {
if (text[cursor] === '{') {
braces++
} else if (text[cursor] === '}') {
braces--
if (braces === 0) {
return text.slice(start, cursor + 1)
}
}
}
return null
}
type Method = { name: string; body: string }
function readClient(name: string): { text: string; methods: Method[]; helpers: Method[] } {
const text = readFileSync(join(PAGE_DIR, name), 'utf8')
const lines = text.split('\n')
const offsets: number[] = []
let offset = 0
for (const line of lines) {
offsets.push(offset)
offset += line.length + 1
}
const methods: Method[] = []
for (let index = 0; index < lines.length; index++) {
const match =
/^ {2}(?:(?:private|protected|public|async)\s+)*([A-Za-z][A-Za-z0-9]*)\s*[(<]/.exec(
lines[index]!
)
if (!match || /^\s*(?:if|for|while|switch|catch|return)\b/.test(lines[index]!)) {
continue
}
const body = balancedBody(text, offsets[index]!)
if (!body) {
continue
}
methods.push({ name: match[1]!, body })
index += body.split('\n').length - 1
}
const helpers: Method[] = []
for (const match of text.matchAll(/\nfunction ([A-Za-z0-9_]+)\s*[<(]/g)) {
const body = balancedBody(text, match.index + match[0].length - 1)
if (body) {
helpers.push({ name: match[1]!, body })
}
}
return { text, methods, helpers }
}
/** Echo comparisons today, plus any `echo: [...]` declaration a factory-built method carries, so a
* declarative rewrite that drops a field still shrinks this set. */
function echoFields(body: string, helpers: Method[], seen: Set<string>): Set<string> {
const fields = new Set<string>()
for (const match of body.matchAll(
/\b(?:result|preset|value)\.((?:[A-Za-z0-9_]+\.)*[A-Za-z0-9_]+)\s*!==/g
)) {
fields.add(match[1]!)
}
for (const match of body.matchAll(
/!same[A-Za-z0-9_]*\(\s*(?:result|preset|value)\.([A-Za-z0-9_]+)/g
)) {
fields.add(match[1]!)
}
for (const match of body.matchAll(/\becho:\s*\[([^\]]*)\]/g)) {
for (const field of match[1]!.matchAll(/'([A-Za-z0-9_.]+)'/g)) {
fields.add(field[1]!)
}
}
for (const [helper, declared] of Object.entries(SHARED_ECHO_HELPERS)) {
if (new RegExp(`\\b${helper}\\(`).test(body)) {
for (const field of declared) {
fields.add(field)
}
}
}
for (const match of body.matchAll(/\b((?:matching|same)[A-Za-z0-9_]*)\s*\(/g)) {
const helper = helpers.find((candidate) => candidate.name === match[1])
if (!helper || seen.has(helper.name)) {
continue
}
seen.add(helper.name)
for (const field of echoFields(helper.body, helpers, seen)) {
fields.add(field)
}
}
return fields
}
/** Operations a method names directly in a `request` call. */
function directOperations(body: string): string[] {
return [...body.matchAll(/\.request\(\s*'([A-Za-z]+)',\s*'([A-Za-z0-9]+)'/g)].map(
(match) => `${match[1]}.${match[2]}`
)
}
/** Operations a method hands to a private helper that forwards a variable operation on. */
function forwardedOperations(body: string, forwarders: Map<string, string>): string[] {
const operations: string[] = []
for (const [helper, capability] of forwarders) {
for (const match of body.matchAll(
new RegExp(`\\bthis\\.${helper}(?:<[^(]*>)?\\(\\s*'([A-Za-z0-9]+)'`, 'g')
)) {
operations.push(`${capability}.${match[1]}`)
}
}
return operations
}
function derivedEchoCensus(): Record<string, string[]> {
const census: Record<string, Set<string>> = {}
const files = readdirSync(PAGE_DIR).filter(
(name) => name.endsWith('-request-client.ts') && !name.includes('.test.')
)
for (const name of files) {
const { methods, helpers } = readClient(name)
const forwarders = new Map<string, string>()
for (const method of methods) {
const match = /\.request\(\s*'([A-Za-z]+)',\s*(?!')[A-Za-z]/.exec(method.body)
if (match) {
forwarders.set(method.name, match[1]!)
}
}
const forwarderFields = new Map<string, Set<string>>()
for (const method of methods) {
const fields = echoFields(method.body, helpers, new Set())
if (forwarders.has(method.name)) {
forwarderFields.set(method.name, fields)
continue
}
if (fields.size === 0) {
continue
}
for (const operation of [
...directOperations(method.body),
...forwardedOperations(method.body, forwarders)
]) {
census[operation] = new Set([...(census[operation] ?? []), ...fields])
}
}
for (const [helper, fields] of forwarderFields) {
if (fields.size === 0) {
continue
}
for (const method of methods) {
if (method.name === helper) {
continue
}
for (const operation of forwardedOperations(
method.body,
new Map([[helper, forwarders.get(helper)!]])
)) {
census[operation] = new Set([...(census[operation] ?? []), ...fields])
}
}
}
}
return Object.fromEntries(
Object.entries(census).map(([operation, fields]) => [operation, [...fields].sort()])
)
}
describe('mobile web bridge operation echo census', () => {
const derived = derivedEchoCensus()
it('checks the recorded echo fields on every operation that guards its own identity', () => {
expect(derived).toEqual(
Object.fromEntries(
Object.entries(EXPECTED_ECHO_FIELDS).map(([operation, fields]) => [operation, [...fields]])
)
)
})
it('keeps every shared echo helper checking the fields it is credited with', () => {
const text = readFileSync(join(PAGE_DIR, 'mobile-web-result-echo.ts'), 'utf8')
for (const [helper, declared] of Object.entries(SHARED_ECHO_HELPERS)) {
expect(text).toContain(`export function ${helper}`)
for (const field of declared) {
expect(text).toContain(`result.${field} !==`)
}
}
})
it('records echo fields only for registered operations', () => {
const registered = new Set(
Object.entries(MOBILE_WEB_BRIDGE_OPERATIONS).flatMap(([capability, operations]) =>
Object.keys(operations).map((operation) => `${capability}.${operation}`)
)
)
expect(Object.keys(EXPECTED_ECHO_FIELDS).filter((key) => !registered.has(key))).toEqual([])
expect(Object.keys(EXPECTED_ECHO_FIELDS).length).toBeGreaterThanOrEqual(49)
})
it('guards the page workspace handle on every workspace-scoped echo it records', () => {
const workspaceScoped = Object.entries(EXPECTED_ECHO_FIELDS).filter(([, fields]) =>
fields.some((field) => field === 'workspaceId')
)
expect(workspaceScoped.length).toBeGreaterThanOrEqual(40)
})
})
@@ -1,95 +0,0 @@
import { readFileSync, readdirSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { MOBILE_WEB_BRIDGE_OPERATIONS } from './bridge-operation-registry'
const REPO_ROOT = resolve(__dirname, '..', '..', '..')
const PAGE_DIR = join(REPO_ROOT, 'src', 'mobile-web', 'src')
const SHELL_DIR = join(REPO_ROOT, 'mobile', 'src', 'mobile-web')
const registered = new Set(
Object.entries(MOBILE_WEB_BRIDGE_OPERATIONS).flatMap(([capability, operations]) =>
Object.keys(operations).map((operation) => `${capability}.${operation}`)
)
)
function sources(dir: string, keep: (name: string) => boolean): { name: string; text: string }[] {
return readdirSync(dir)
.filter((name) => name.endsWith('.ts') && !name.includes('.test.') && keep(name))
.map((name) => ({ name, text: readFileSync(join(dir, name), 'utf8') }))
}
describe('mobile web bridge operation registry census', () => {
it('registers every capability and operation pair a page request client names', () => {
const files = sources(PAGE_DIR, () => true)
const named = new Set<string>()
for (const { text } of files) {
for (const match of text.matchAll(/\.request\(\s*'([A-Za-z]+)',\s*'([A-Za-z]+)'/g)) {
named.add(`${match[1]}.${match[2]}`)
}
}
expect(files.length).toBeGreaterThanOrEqual(40)
expect(named.size).toBeGreaterThanOrEqual(126)
expect([...named].filter((pair) => !registered.has(pair))).toEqual([])
})
// A bare `string` here is how `workspace.creationRetiredNames` shipped unregistered: the helper
// erased the operation name before the compiler could check it against the registry.
it('types every page request client operation parameter against the registry', () => {
const files = sources(PAGE_DIR, (name) => name.endsWith('-request-client.ts'))
const untyped = files
.filter(({ text }) => /\boperation\??:\s*string\b/.test(text))
.map(({ name }) => name)
expect(files.map(({ name }) => name)).toContain('mobile-web-one-shot-request-client.ts')
expect(files.length).toBeGreaterThanOrEqual(20)
expect(untyped).toEqual([])
})
// A page client that reaches one operation through two different schema pairs has an arm nobody
// reviews; a factory rewrite that swaps a schema shows up here as a second pair.
it('binds every directly named operation to exactly one payload and result schema', () => {
const pairs = new Map<string, Set<string>>()
for (const { text } of sources(PAGE_DIR, () => true)) {
for (const match of text.matchAll(
/\.request(?:<[^(]*>)?\(\s*'([A-Za-z]+)',\s*'([A-Za-z0-9]+)',([\s\S]{0,400}?)\n\s*\)/g
)) {
const schemas = [...match[3]!.matchAll(/\b([A-Za-z0-9_]*Schema)\b/g)].map((name) => name[1])
const key = `${match[1]}.${match[2]}`
pairs.set(key, (pairs.get(key) ?? new Set()).add(schemas.join('|')))
}
}
expect(pairs.size).toBeGreaterThanOrEqual(126)
expect([...pairs].filter(([, schemas]) => schemas.size !== 1).map(([key]) => key)).toEqual([])
expect([...pairs.keys()].filter((key) => !registered.has(key))).toEqual([])
expect(
[...pairs]
.filter(([, schemas]) => [...schemas].some((pair) => pair.split('|').length !== 2))
.map(([key]) => key)
).toEqual([])
})
// Capability-level routing is proven against the real table in the shell's dispatch census. This
// covers the step inside an arm: the operation name has to be matched somewhere, not merely
// quoted, which a stray comment or an unrelated string used to satisfy.
it('matches every registered operation at a shell dispatch position', () => {
const shell = sources(SHELL_DIR, (name) => !name.startsWith('mobile-web-production-'))
const undispatched = [...registered].filter((pair) => {
const operation = pair.slice(pair.indexOf('.') + 1)
const patterns = [
new RegExp(`operation === '${operation}'`),
new RegExp(`case '${operation}':`),
new RegExp(`'${operation}'(?=[,\\]])`),
new RegExp(`^\\s*'${operation}',?$`, 'm'),
new RegExp(`\\bstartsWith\\('${operation}'\\)`),
new RegExp(`\\b${operation}:\\s`)
]
return !shell.some(({ text }) => patterns.some((pattern) => pattern.test(text)))
})
expect(shell.length).toBeGreaterThanOrEqual(100)
expect(undispatched).toEqual([])
})
})