mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
test(mobile): extend Android hosted route parity
This commit is contained in:
@@ -27,8 +27,8 @@
|
||||
now pass; iOS Simulator and Android emulator manual previous-generation
|
||||
recovery, cache clear/redownload, and corrupt-active cold fallback now pass
|
||||
through the native recovery surface; the exact iOS Simulator and Android API
|
||||
36 Source Control and Review journeys and the full post-rebase validation
|
||||
matrix now pass
|
||||
36 Agent History, Source Control, and Review journeys and the full
|
||||
post-rebase validation matrix now pass
|
||||
- **Last updated:** July 28, 2026
|
||||
- **Design:**
|
||||
[`2026-07-22-mobile-hybrid-webview-single-pr-migration.md`](./2026-07-22-mobile-hybrid-webview-single-pr-migration.md)
|
||||
@@ -857,7 +857,12 @@ recovery, or physical-device gates.
|
||||
threshold (observed 2.2677%), a mean-channel-difference budget of 4
|
||||
(observed 2.5663), and a vertical header-landmark budget of 0.005
|
||||
(observed 0.00075). The status bar alone is masked. The full live
|
||||
accessibility and remaining screen/device/state matrix remain open.
|
||||
accessibility and remaining screen/device/state matrix remain open. The
|
||||
Pixel 9 Pro API 36 emulator now also passes the same existing route's three
|
||||
scopes, lazy preview, search/no-match/clear flow, synthetic Resume rejection,
|
||||
and native-touch Resume into a second Session tab. That fresh paired-app run
|
||||
continues through Source Control, changed-file handoff, standalone Review,
|
||||
both isolation probes, and the Android bridge log audit.
|
||||
- [x] Implement approvals and elicitation/question flows. The existing prompt
|
||||
cards call a named response operation that freshly verifies the exact
|
||||
workspace, tab, terminal, agent, provider session, and transcript before
|
||||
@@ -2436,5 +2441,7 @@ copy.
|
||||
| 2026-07-28 | Complete | The exact Pixel 9 Pro API 36 arm64 Debug APK was rebuilt, installed, paired to a disposable Desktop runtime, entered the unchanged workspace and Session UI, opened Source Control, opened a changed file as a second Session diff tab, and verified standalone Review. Deliberate-red network/navigation isolation ended with zero sentinel observations. |
|
||||
| 2026-07-28 | Complete | The Android route harness now clears and audits fresh logcat. The final journey contains no Expo bridge rejection, Kotlin conversion/cast failure, or fatal process error. Focused coverage passes 6 files / 41 tests, Android native module tests pass, mobile and RNW typechecks/lints/formatting/max-lines pass, and the final rebased RNW verifier passes build `f852d8525d2b0e20d79262d74ce3ef74bfa73c3e55b95176bfb1b467beafae61`. |
|
||||
| 2026-07-28 | Complete | Rebased the single migration commit onto `origin/main` at `1fa9ffb5e`; the sole root-package conflict retained upstream's current code-quality script shape and the migration's dedicated mobile-web lint. The branch remains one commit ahead and zero behind. |
|
||||
| 2026-07-28 | Complete | Final post-rebase validation passes 550 mobile files / 3,285 tests with 2 expected skips and 3,769 root files / 39,207 tests with 62 expected skips. Root/mobile/RNW typechecks, lint, formatting, reliability gates, localization, max-lines, the verified 49-asset RNW package, and the rebuilt Android journey all pass. |
|
||||
| 2026-07-28 | Complete | The Pixel 9 Pro API 36 route gate now seeds a disposable Agent History fixture and passes Workspace/Project/All scopes, lazy preview, search/no-match/clear, synthetic privileged-resume rejection, and native-touch resume into a second Session tab. The same app session then opens Source Control, a third changed-file Session tab, standalone Review, both isolation probes, and a clean bridge log audit. |
|
||||
| 2026-07-28 | Complete | Rebased both migration commits without conflict onto current `origin/main` at `0404f27b3`. The branch is two commits ahead and zero behind. |
|
||||
| 2026-07-28 | Complete | Final validation passes 551 mobile files / 3,286 tests with 2 expected skips and 3,770 root files / 39,212 tests with 62 expected skips. Root/mobile/RNW typechecks, lint, formatting, reliability gates, localization, max-lines, the verified 49-asset RNW package, and the rebuilt Android journey all pass. |
|
||||
| 2026-07-28 | Next | Complete the remaining parity inventory and cutover cleanup, then execute the physical-device, topology, security, performance, packaged-release, and App Store gates. |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
- **Status:** In progress; route, terminal, native, persisted-state, UX-state,
|
||||
accessibility/input, notification, native-chat, and UI-preservation
|
||||
boundaries frozen
|
||||
- **Last updated:** July 27, 2026
|
||||
- **Last updated:** July 28, 2026
|
||||
- **Design:**
|
||||
[`2026-07-22-mobile-hybrid-webview-single-pr-migration.md`](./2026-07-22-mobile-hybrid-webview-single-pr-migration.md)
|
||||
- **Checklist:**
|
||||
@@ -66,11 +66,11 @@ but is a component, not a route. It migrates with the session UI.
|
||||
| `mobile/app/h/[hostId]/accounts.tsx` | Host agent-account usage and selection | Mobile web app | Complete: the same screen uses typed native/web host-account adapters |
|
||||
| `mobile/app/h/[hostId]/tasks.tsx` | Host task providers, task details, mutations, workspace creation | Mobile web app | Complete on iOS Simulator: same route/presentation with strict native/web operations |
|
||||
| `mobile/app/h/[hostId]/session/[worktreeId].tsx` | Sessions, tabs, terminal, browser, native chat, files, attachments, dictation | Mobile web app | Reuse current session presentation; native-chat/agent-state slice is adapter-complete and awaiting live parity evidence |
|
||||
| `mobile/app/h/[hostId]/agent-history/[worktreeId].tsx` | Agent session history and resume | Mobile web app | Same panel/list presentation now mounted with bounded opaque snapshot/preview/resume operations; live hosted parity remains |
|
||||
| `mobile/app/h/[hostId]/agent-history/[worktreeId].tsx` | Agent session history and resume | Mobile web app | Complete on iOS and Android emulators: same panel/list presentation, bounded opaque snapshot/preview/resume operations, scopes, search, and trusted-resume mediation |
|
||||
| `mobile/app/h/[hostId]/files/[worktreeId].tsx` | File explorer | Mobile web app | Complete at adapter level: the same `MobileFileExplorerPanel` is mounted by a thin hosted route with bounded opaque directory reads and native-shell reconnect |
|
||||
| `mobile/app/h/[hostId]/files/preview/[worktreeId].tsx` | File, Markdown, image, editable, HTML, and Mermaid previews | Mobile web app | Reuse current preview UI; HTML is sanitized in a hash-bound inert frame, while native/hosted Mermaid use the locally bundled hash-authorized engine and unchanged fallback presentation |
|
||||
| `mobile/app/h/[hostId]/source-control/[worktreeId].tsx` | Source-control hub | Mobile web app | Complete on iOS Simulator: same `MobileSourceControlPanel`, including Session-origin changed-file handoff; Android route evidence remains |
|
||||
| `mobile/app/h/[hostId]/review/[worktreeId].tsx` | Diff review and comments | Mobile web app | Complete on iOS Simulator: same review presentation and virtualization; standalone controls verified independently |
|
||||
| `mobile/app/h/[hostId]/source-control/[worktreeId].tsx` | Source-control hub | Mobile web app | Complete on iOS and Android emulators: same `MobileSourceControlPanel`, including Session-origin changed-file handoff |
|
||||
| `mobile/app/h/[hostId]/review/[worktreeId].tsx` | Diff review and comments | Mobile web app | Complete on iOS and Android emulators: same review presentation and virtualization; standalone controls verified independently |
|
||||
| `mobile/app/h/[hostId]/history/[worktreeId].tsx` | Legacy source-control history redirect | Mobile web app | Complete: provider-neutral compatibility redirect preserves the history segment during rollout |
|
||||
| `mobile/app/h/[hostId]/pr/[worktreeId].tsx` | Legacy pull-request redirect | Mobile web app | Complete: provider-neutral compatibility redirect preserves the pull-request segment during rollout |
|
||||
| `mobile/app/hybrid-prototype.tsx` | Experimental single-document Option B prototype | Remove | Replace with production host route after all gates pass |
|
||||
@@ -472,13 +472,14 @@ IDs, and unrelated provider state never enter the page result.
|
||||
|
||||
## Next Inventory Action
|
||||
|
||||
The dedicated hosted Files, Preview, Source Control, and Review routes now
|
||||
mount the unchanged presentations through native/web adapters. The iPhone 17
|
||||
Pro Simulator verifies Session-origin changed-file handoff, standalone Review,
|
||||
and both isolation probes. Provider-neutral History and PR compatibility
|
||||
redirects are present. Run the same Source Control/Review route evidence on
|
||||
Android after restoring the Android SDK/AVD, then close the remaining
|
||||
native-versus-hosted interaction matrix.
|
||||
The dedicated hosted Files, Preview, Source Control, Review, and Agent History
|
||||
routes mount the unchanged presentations through native/web adapters. The
|
||||
iPhone 17 Pro Simulator verifies Session-origin changed-file handoff,
|
||||
standalone Review, and both isolation probes. The Pixel 9 Pro API 36 emulator
|
||||
now verifies those paths in the same fresh app session as Agent History scopes,
|
||||
preview, filtering, synthetic-resume rejection, and native-touch resume.
|
||||
Provider-neutral History and PR compatibility redirects are present. Close the
|
||||
remaining native-versus-hosted interaction matrix.
|
||||
|
||||
The interrupted-transcript versus hook-status mismatch and a real structured
|
||||
prompt response pass Host 37 Simulator replay. Current package `4b7df7d4…`
|
||||
|
||||
@@ -2092,6 +2092,14 @@ accessibility finding remains open. This is not completion of the phone/tablet,
|
||||
shell-state, route,
|
||||
screen-reader, or physical-device matrix.
|
||||
|
||||
The Pixel 9 Pro API 36 emulator now exercises that same hosted Agent History
|
||||
presentation in a freshly installed paired app. Workspace/Project/All scopes,
|
||||
lazy preview, search/no-match/clear, synthetic privileged-resume rejection, and
|
||||
native-touch resume into a second Session tab pass. The same authenticated
|
||||
WebView then opens Source Control, a changed file as a third Session tab, and
|
||||
standalone Review before passing the network/navigation isolation probes and
|
||||
fresh Android bridge-log audit.
|
||||
|
||||
Native route ownership now has exact-app lifecycle evidence too. A
|
||||
gesture-gated request from the unchanged hosted Session records only a
|
||||
request-scoped Terminal Settings intent, posts the broker success response, and
|
||||
|
||||
@@ -37,9 +37,15 @@ verifies standalone Review. The deliberate-red network/navigation isolation
|
||||
probe records zero escaped traffic, and the embedded log audit records no Expo
|
||||
bridge rejection, Kotlin conversion/cast error, or fatal process error.
|
||||
|
||||
The migration is rebased onto `origin/main` at `1fa9ffb5e`. Post-rebase
|
||||
validation passes 550 mobile files / 3,285 tests with 2 expected skips and
|
||||
3,769 root files / 39,207 tests with 62 expected skips. All project typechecks,
|
||||
The Android route gate now also seeds a disposable Agent History fixture and
|
||||
verifies the unchanged screen's Workspace/Project/All scopes, lazy preview,
|
||||
search/no-match/clear flow, rejection of synthetic privileged activation, and
|
||||
native-touch resume into a second Session tab before continuing through Source
|
||||
Control and Review.
|
||||
|
||||
The migration is rebased onto `origin/main` at `0404f27b3`. Post-rebase
|
||||
validation passes 551 mobile files / 3,286 tests with 2 expected skips and
|
||||
3,770 root files / 39,212 tests with 62 expected skips. All project typechecks,
|
||||
root/mobile/mobile-web lint, reliability gates, and the max-lines ratchet pass.
|
||||
The independently verified React Native Web package is
|
||||
`f852d8525d2b0e20d79262d74ce3ef74bfa73c3e55b95176bfb1b467beafae61`:
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import {
|
||||
activateHostedWebViewControl,
|
||||
readHostedWebViewState,
|
||||
readHostedWebViewTextPoint,
|
||||
setHostedWebViewInput,
|
||||
waitForVisibleHostedWebView
|
||||
} from './hosted-webview-cdp-session.mjs'
|
||||
import {
|
||||
EMULATOR_AGENT_HISTORY_PREVIEW_MARKER,
|
||||
EMULATOR_AGENT_HISTORY_TITLE
|
||||
} from './emulator-agent-history-fixture.mjs'
|
||||
import { tapHostedAndroidPoint } from './hosted-android-emulator-accessibility.mjs'
|
||||
import { readHostedWebViewControlPoint } from './hosted-webview-control-point.mjs'
|
||||
|
||||
const SEARCH_PLACEHOLDER = 'Search sessions, repo:, path:'
|
||||
|
||||
export async function verifyHostedAndroidAgentHistoryJourney({
|
||||
discoveryUrl,
|
||||
emulator,
|
||||
sessionDocument,
|
||||
timeoutMs
|
||||
}) {
|
||||
if (!sessionDocument.href.includes('/session/')) {
|
||||
throw new Error('Agent History journey requires an active Session route.')
|
||||
}
|
||||
const actionsDocument = await evidenceStep('open Session actions', () =>
|
||||
retryAndroidRouteActivation({
|
||||
activate: () => tapAndroidLabel(emulator, sessionDocument, 'More session actions'),
|
||||
wait: () =>
|
||||
waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: 'Agent History',
|
||||
expectedHrefIncludes: '/session/',
|
||||
timeoutMs: Math.min(timeoutMs, 3_000)
|
||||
})
|
||||
})
|
||||
)
|
||||
await evidenceStep('open Agent History', () =>
|
||||
retryAndroidRouteActivation({
|
||||
activate: () =>
|
||||
tapAndroidText(emulator, actionsDocument, 'Agent History', Math.min(timeoutMs, 5_000)),
|
||||
wait: () =>
|
||||
waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: 'Agent Session History',
|
||||
expectedHrefIncludes: '/agent-history/',
|
||||
timeoutMs: Math.min(timeoutMs, 3_000)
|
||||
})
|
||||
})
|
||||
)
|
||||
let historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: EMULATOR_AGENT_HISTORY_TITLE,
|
||||
expectedHrefIncludes: '/agent-history/',
|
||||
timeoutMs
|
||||
})
|
||||
const state = await evidenceStep('read Agent History state', () =>
|
||||
readHostedWebViewState(historyDocument)
|
||||
)
|
||||
assertAgentHistoryState(state)
|
||||
await evidenceStep('expand Agent History row', () =>
|
||||
activateHostedWebViewControl(historyDocument, {
|
||||
kind: 'text',
|
||||
value: EMULATOR_AGENT_HISTORY_TITLE
|
||||
})
|
||||
)
|
||||
historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: EMULATOR_AGENT_HISTORY_PREVIEW_MARKER,
|
||||
timeoutMs
|
||||
})
|
||||
await evidenceStep('filter Agent History', () =>
|
||||
setHostedWebViewInput(historyDocument, {
|
||||
placeholder: SEARCH_PLACEHOLDER,
|
||||
value: 'NO_MATCHING_AGENT_HISTORY_SESSION'
|
||||
})
|
||||
)
|
||||
historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: 'No sessions match your search.',
|
||||
timeoutMs
|
||||
})
|
||||
await evidenceStep('clear Agent History filter', () =>
|
||||
setHostedWebViewInput(historyDocument, {
|
||||
placeholder: SEARCH_PLACEHOLDER,
|
||||
value: ''
|
||||
})
|
||||
)
|
||||
historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: EMULATOR_AGENT_HISTORY_TITLE,
|
||||
timeoutMs
|
||||
})
|
||||
for (const scope of ['Project', 'All']) {
|
||||
await evidenceStep(`select ${scope} scope`, () =>
|
||||
activateHostedWebViewControl(historyDocument, {
|
||||
kind: 'text',
|
||||
value: scope
|
||||
})
|
||||
)
|
||||
historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: EMULATOR_AGENT_HISTORY_TITLE,
|
||||
timeoutMs
|
||||
})
|
||||
}
|
||||
await evidenceStep('reject untrusted synthetic resume', () =>
|
||||
activateHostedWebViewControl(historyDocument, {
|
||||
kind: 'label',
|
||||
value: 'Resume agent session'
|
||||
})
|
||||
)
|
||||
historyDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: 'Failed to resume session.',
|
||||
timeoutMs
|
||||
})
|
||||
const nativeTouchPoint = await evidenceStep('resume Agent History from native touch', () =>
|
||||
tapAndroidLabel(emulator, historyDocument, 'Resume agent session')
|
||||
)
|
||||
const resumedSessionDocument = await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: '2 tabs',
|
||||
expectedHrefIncludes: '/session/',
|
||||
timeoutMs
|
||||
})
|
||||
return {
|
||||
route: state.href,
|
||||
scopes: ['Workspace', 'Project', 'All'],
|
||||
search: 'present',
|
||||
row: EMULATOR_AGENT_HISTORY_TITLE,
|
||||
preview: EMULATOR_AGENT_HISTORY_PREVIEW_MARKER,
|
||||
resume: {
|
||||
synthetic: 'gesture rejected',
|
||||
native: 'queued',
|
||||
nativeTouchPoint,
|
||||
resumedRoute: resumedSessionDocument.href
|
||||
},
|
||||
headerControls: ['Back', 'Refresh agent sessions'],
|
||||
returnedSessionDocument: resumedSessionDocument
|
||||
}
|
||||
}
|
||||
|
||||
async function tapAndroidLabel(emulator, document, label) {
|
||||
const point = await readHostedWebViewControlPoint(document, label)
|
||||
return tapHostedAndroidPoint(emulator, point)
|
||||
}
|
||||
|
||||
async function tapAndroidText(emulator, document, text, timeoutMs) {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let lastError = new Error(`Hosted Android text was not visible: ${text}`)
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const point = await readHostedWebViewTextPoint(document, text)
|
||||
return await tapHostedAndroidPoint(emulator, point)
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
await delay(100)
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
|
||||
async function retryAndroidRouteActivation({ activate, wait }) {
|
||||
let lastError = new Error('Hosted Android route activation failed')
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
await activate()
|
||||
return await wait()
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
|
||||
function assertAgentHistoryState(state) {
|
||||
for (const expected of ['Workspace', 'Project', 'All']) {
|
||||
if (!state.bodyText.includes(expected)) {
|
||||
throw new Error(`Agent History is missing its ${expected} scope.`)
|
||||
}
|
||||
}
|
||||
if (!state.labels.includes('Back') || !state.labels.includes('Refresh agent sessions')) {
|
||||
throw new Error('Agent History is missing its existing header controls.')
|
||||
}
|
||||
if (!state.placeholders.includes(SEARCH_PLACEHOLDER)) {
|
||||
throw new Error('Agent History is missing its existing search input.')
|
||||
}
|
||||
}
|
||||
|
||||
async function evidenceStep(label, run) {
|
||||
try {
|
||||
return await run()
|
||||
} catch (error) {
|
||||
throw new Error(`${label} failed: ${error instanceof Error ? error.message : String(error)}`, {
|
||||
cause: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -11,20 +11,16 @@ export async function verifyHostedSourceControlReviewJourney({
|
||||
emulator,
|
||||
sessionDocument,
|
||||
timeoutMs,
|
||||
expectedSessionDiffText = '2 tabs',
|
||||
tapPoint = tapHostedIosPoint
|
||||
}) {
|
||||
const sourceControlPoint = await journeyStep('measure Open source control', () =>
|
||||
readHostedWebViewControlPoint(sessionDocument, 'Open source control')
|
||||
)
|
||||
await journeyStep('tap Open source control', () =>
|
||||
tapPoint(emulator, sourceControlPoint, 'Open source control')
|
||||
)
|
||||
const sourceControl = await journeyStep('wait for Source Control route', () =>
|
||||
waitForVisibleHostedWebView({
|
||||
openSourceControlRoute({
|
||||
discoveryUrl,
|
||||
expectedText: 'Source Control',
|
||||
expectedHrefIncludes: '/source-control/',
|
||||
timeoutMs
|
||||
emulator,
|
||||
sessionDocument,
|
||||
timeoutMs,
|
||||
tapPoint
|
||||
})
|
||||
)
|
||||
const sourceState = await journeyStep('read populated Source Control state', () =>
|
||||
@@ -51,7 +47,7 @@ export async function verifyHostedSourceControlReviewJourney({
|
||||
const sessionDiff = await journeyStep('wait for Session diff route', () =>
|
||||
waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: '2 tabs',
|
||||
expectedText: expectedSessionDiffText,
|
||||
expectedHrefIncludes: '/session/',
|
||||
requireInteractiveControls: false,
|
||||
timeoutMs
|
||||
@@ -84,6 +80,31 @@ export async function verifyHostedSourceControlReviewJourney({
|
||||
}
|
||||
}
|
||||
|
||||
async function openSourceControlRoute({
|
||||
discoveryUrl,
|
||||
emulator,
|
||||
sessionDocument,
|
||||
timeoutMs,
|
||||
tapPoint
|
||||
}) {
|
||||
let lastError = new Error('Source Control route did not open')
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
const point = await readHostedWebViewControlPoint(sessionDocument, 'Open source control')
|
||||
await tapPoint(emulator, point, 'Open source control')
|
||||
return await waitForVisibleHostedWebView({
|
||||
discoveryUrl,
|
||||
expectedText: 'Source Control',
|
||||
expectedHrefIncludes: '/source-control/',
|
||||
timeoutMs: Math.min(timeoutMs, 3_000)
|
||||
})
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
|
||||
async function waitForChangedFileState(document, timeoutMs) {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let state
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { promisify } from 'node:util'
|
||||
import { resolveEmulatorOrcaCli } from './emulator-orca-cli-selection.mjs'
|
||||
import { verifyHostedAndroidAgentHistoryJourney } from './hosted-android-agent-history-journey.mjs'
|
||||
import {
|
||||
tapHostedAndroidAccessibilityControl,
|
||||
tapHostedAndroidPoint,
|
||||
@@ -83,6 +84,7 @@ async function main() {
|
||||
logSuccess: () => {}
|
||||
})
|
||||
)
|
||||
runtime.env.ORCA_E2E_MOBILE_AGENT_HISTORY_FIXTURE = '1'
|
||||
await stage('test workspace registration', () =>
|
||||
registerWorktreeForPairingRuntime(runtime, worktree, {
|
||||
orca: runOrca,
|
||||
@@ -175,11 +177,21 @@ async function main() {
|
||||
timeoutMs: options.timeoutMs
|
||||
})
|
||||
)
|
||||
const agentHistoryResult = await stage('Agent History journey', () =>
|
||||
verifyHostedAndroidAgentHistoryJourney({
|
||||
discoveryUrl,
|
||||
emulator,
|
||||
sessionDocument,
|
||||
timeoutMs: options.timeoutMs
|
||||
})
|
||||
)
|
||||
const { returnedSessionDocument, ...agentHistory } = agentHistoryResult
|
||||
const sourceControlReview = await stage('Source Control and Review journey', () =>
|
||||
verifyHostedSourceControlReviewJourney({
|
||||
discoveryUrl,
|
||||
emulator,
|
||||
sessionDocument,
|
||||
sessionDocument: returnedSessionDocument,
|
||||
expectedSessionDiffText: '3 tabs',
|
||||
timeoutMs: options.timeoutMs,
|
||||
tapPoint: tapHostedAndroidJourneyControl
|
||||
})
|
||||
@@ -217,6 +229,7 @@ async function main() {
|
||||
device: await runAndroidAdb(adb, ['shell', 'getprop', 'ro.product.model']),
|
||||
pid: inspector.pid,
|
||||
workspace: expectedWorkspace,
|
||||
agentHistory,
|
||||
sourceControlReview,
|
||||
networkIsolation,
|
||||
navigationIsolation,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
activateControl: vi.fn(),
|
||||
readControlPoint: vi.fn(),
|
||||
readState: 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-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.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, trusted 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: {
|
||||
synthetic: 'gesture rejected',
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -112,6 +112,7 @@ describe('hosted iOS Source Control and Review journey', () => {
|
||||
sessionDocument: {
|
||||
href: 'orca-mobile-web://build/h/host/session/workspace'
|
||||
},
|
||||
expectedSessionDiffText: '3 tabs',
|
||||
timeoutMs: 30_000,
|
||||
tapPoint
|
||||
})
|
||||
@@ -121,5 +122,9 @@ describe('hosted iOS Source Control and Review journey', () => {
|
||||
'Open source control',
|
||||
'Open changed file mobile/app/index.tsx'
|
||||
])
|
||||
expect(mocks.waitForDocument).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ expectedText: '3 tabs' })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user