test(mobile): extend Android hosted route parity

This commit is contained in:
OrcaWin
2026-08-09 18:34:57 -04:00
committed by Jinwoo-H
parent 9834f65552
commit 3feb8bf332
9 changed files with 392 additions and 30 deletions
@@ -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' })
)
})
})