Keep managed file drags out of desktop browser paths (#26698)

This commit is contained in:
Neil
2026-10-09 05:09:00 -07:00
committed by GitHub
parent 6c20143807
commit 93a6b1abff
10 changed files with 390 additions and 24 deletions
+4 -3
View File
@@ -361,7 +361,8 @@ jobs:
. != "tests/e2e/ssh-orcad-open-in-owner.spec.ts" and
. != "tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts" and
. != "tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts" and
. != "tests/e2e/ssh-orcad-explorer-selected-host.spec.ts"
. != "tests/e2e/ssh-orcad-explorer-selected-host.spec.ts" and
. != "tests/e2e/ssh-orcad-browser-drop-owner.spec.ts"
)' <<<"$TEST_FILES_JSON" > "$RUNNER_TEMP/general-e2e-specs"
fi
mapfile -t TEST_FILES < "$RUNNER_TEMP/general-e2e-specs"
@@ -595,7 +596,7 @@ jobs:
orcad-auto-convert-docker:
name: ssh host auto-converts to managed orcad (Docker)
needs: [build, prepare-native-cache]
if: inputs.test_files == '' || contains(inputs.test_files, 'tests/e2e/ssh-orcad-auto-convert.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-idle-exit.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-capabilities.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-service-status.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-routing.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-editor-ownership.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-conversion.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-live-documents.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-open-in-owner.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-explorer-selected-host.spec.ts')
if: inputs.test_files == '' || contains(inputs.test_files, 'tests/e2e/ssh-orcad-auto-convert.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-idle-exit.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-capabilities.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-service-status.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-routing.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-editor-ownership.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-conversion.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-markdown-live-documents.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-open-in-owner.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-explorer-selected-host.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-orcad-browser-drop-owner.spec.ts')
runs-on: ubuntu-latest
timeout-minutes: 45
env:
@@ -631,7 +632,7 @@ jobs:
ORCA_RELAY_PATH: ${{ github.workspace }}/out/relay
run: |
export ORCA_E2E_ORCAD_CONVERT_TEMPLATE="$RUNNER_TEMP/orcad-convert-template"
xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh pnpm exec playwright test --config tests/playwright.config.ts tests/e2e/ssh-orcad-auto-convert.spec.ts tests/e2e/ssh-orcad-idle-exit.spec.ts tests/e2e/ssh-orcad-browser-capabilities.spec.ts tests/e2e/ssh-orcad-browser-service-status.spec.ts tests/e2e/ssh-orcad-browser-routing.spec.ts tests/e2e/ssh-orcad-editor-ownership.spec.ts tests/e2e/ssh-orcad-markdown-conversion.spec.ts tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts tests/e2e/ssh-orcad-markdown-live-documents.spec.ts tests/e2e/ssh-orcad-open-in-owner.spec.ts tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts tests/e2e/ssh-orcad-explorer-selected-host.spec.ts --project=electron-headless --workers=1
xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh pnpm exec playwright test --config tests/playwright.config.ts tests/e2e/ssh-orcad-auto-convert.spec.ts tests/e2e/ssh-orcad-idle-exit.spec.ts tests/e2e/ssh-orcad-browser-capabilities.spec.ts tests/e2e/ssh-orcad-browser-service-status.spec.ts tests/e2e/ssh-orcad-browser-routing.spec.ts tests/e2e/ssh-orcad-editor-ownership.spec.ts tests/e2e/ssh-orcad-markdown-conversion.spec.ts tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts tests/e2e/ssh-orcad-markdown-live-documents.spec.ts tests/e2e/ssh-orcad-open-in-owner.spec.ts tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts tests/e2e/ssh-orcad-explorer-selected-host.spec.ts tests/e2e/ssh-orcad-browser-drop-owner.spec.ts --project=electron-headless --workers=1
- uses: actions/upload-artifact@v7
if: failure()
with:
+5 -2
View File
@@ -62,6 +62,7 @@ export const ORCAD_MARKDOWN_LINK_REFRESH_E2E_SPEC =
'tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts'
export const ORCAD_MARKDOWN_LIVE_DOCUMENTS_E2E_SPEC =
'tests/e2e/ssh-orcad-markdown-live-documents.spec.ts'
export const ORCAD_BROWSER_DROP_OWNER_E2E_SPEC = 'tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'
export const ORCAD_OPEN_IN_OWNER_E2E_SPEC = 'tests/e2e/ssh-orcad-open-in-owner.spec.ts'
export const ORCAD_EDITOR_WATCH_RECOVERY_E2E_SPEC =
'tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts'
@@ -89,7 +90,8 @@ export const DEDICATED_E2E_SPECS = [
ORCAD_OPEN_IN_OWNER_E2E_SPEC,
ORCAD_EDITOR_WATCH_RECOVERY_E2E_SPEC,
ORCAD_EXPLORER_WATCH_RECOVERY_E2E_SPEC,
ORCAD_EXPLORER_SELECTED_HOST_E2E_SPEC
ORCAD_EXPLORER_SELECTED_HOST_E2E_SPEC,
ORCAD_BROWSER_DROP_OWNER_E2E_SPEC
]
const dedicatedSpecs = new Set(DEDICATED_E2E_SPECS)
const dockerSpecs = new Set(DOCKER_SSH_E2E_SPECS)
@@ -142,7 +144,8 @@ export function classifyE2eJobs(input, sshSourceChanged = 'false') {
spec === ORCAD_OPEN_IN_OWNER_E2E_SPEC ||
spec === ORCAD_EDITOR_WATCH_RECOVERY_E2E_SPEC ||
spec === ORCAD_EXPLORER_WATCH_RECOVERY_E2E_SPEC ||
spec === ORCAD_EXPLORER_SELECTED_HOST_E2E_SPEC
spec === ORCAD_EXPLORER_SELECTED_HOST_E2E_SPEC ||
spec === ORCAD_BROWSER_DROP_OWNER_E2E_SPEC
)
}
}
@@ -47,6 +47,25 @@ const workflow = parse(readFileSync('.github/workflows/e2e.yml', 'utf8'))
const prWorkflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
const classify = (specs, ssh = 'false') => classifyE2eJobs(JSON.stringify(specs), ssh)
it('runs real browser file-drop ownership in the template-building Docker job', () => {
const spec = 'tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'
expect(classify([spec])).toEqual({ e2e_run_changed: false, e2e_needs_build: true })
const job = workflow.jobs['orcad-auto-convert-docker']
expect(job.if).toContain(spec)
expect(
job.steps.some((step) => step.run?.includes(spec) && step.env?.ORCA_E2E_SSH_DOCKER === '1')
).toBe(true)
for (const source of [
'src/renderer/src/lib/file-preview.ts',
'src/renderer/src/lib/workspace-file-drag.ts',
'src/renderer/src/components/browser-pane/navigate/use-browser-page-navigation-downloads.ts',
'src/renderer/src/components/right-sidebar/FileExplorerRow.tsx',
'tests/e2e/helpers/browser-split-guest-probes.ts'
]) {
expect(selectPrE2eSpecs([source]), source).toContain(spec)
}
})
it('skips the general consumer only when every requested spec has a dedicated owner', () => {
for (const spec of DEDICATED_E2E_SPECS) {
expect(classify([spec]).e2e_run_changed, spec).toBe(false)
+12
View File
@@ -134,6 +134,18 @@ export const PR_E2E_SOURCE_ROUTES = [
file
)
},
{
id: 'ssh.orcad-browser-drop-owner',
specs: ['tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'],
matches: (file) =>
/^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-relay-target|browser-split-(?:guest-probes|page-server))\.ts$/.test(
file
) ||
(isProductSource(file) &&
/^src\/renderer\/src\/(?:lib\/(?:file-preview|workspace-file-drag|worktree-runtime-owner|connection-context)\.ts|components\/(?:right-sidebar\/FileExplorer(?:Row|VirtualRows)\.tsx|browser-pane\/(?:navigate\/use-browser-page-navigation-downloads\.ts|host-guest\/attach-browser-page-webview\.ts)))$/.test(
file
))
},
{
id: 'ssh.orcad-open-in-owner',
specs: ['tests/e2e/ssh-orcad-open-in-owner.spec.ts'],
@@ -0,0 +1,108 @@
// @vitest-environment happy-dom
import { cleanup, fireEvent, render } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useBrowserPageNavigationDownloads } from './use-browser-page-navigation-downloads'
import {
WORKSPACE_FILE_DRAG_SOURCE_MIME,
WORKSPACE_FILE_PATH_MIME
} from '@/lib/workspace-file-drag'
const mocks = vi.hoisted(() => ({ notice: vi.fn(), target: vi.fn(), navigate: vi.fn() }))
vi.mock('@/lib/file-preview', () => ({
REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE:
'Open in Orca Browser is only available for local files.',
getWorkspaceFileBrowserOpenTarget: mocks.target
}))
vi.mock('./navigate-browser-page-url', () => ({ navigateBrowserPageToUrl: mocks.navigate }))
vi.mock('./workspace-doc-address-submission', () => ({
routeWorkspaceDocAddressSubmission: vi.fn()
}))
vi.mock('./use-browser-page-download-events', () => ({
useBrowserPageDownloadEvents: () => ({ downloadStates: [], setDownloadStates: vi.fn() })
}))
function DropSurface() {
const nav = useBrowserPageNavigationDownloads({
browserTabId: 'page-local',
worktreeId: 'global-floating-terminal',
webviewRef: { current: null },
activeLoadFailureRef: { current: null },
lastKnownWebviewUrlRef: { current: null },
trackNextLoadingEventRef: { current: false },
recoveryNavigationValidationRef: { current: null },
onSetUrlRef: { current: vi.fn() },
onUpdatePageStateRef: { current: vi.fn() },
keepAddressBarFocusRef: { current: false },
focusWebviewNow: vi.fn(() => false),
setResourceNotice: mocks.notice,
addressBarValueRef: { current: 'about:blank' },
addressBarInputRef: { current: null },
browserTabUrl: 'about:blank'
})
return <div data-testid="drop" onDrop={(event) => nav.handleInternalFileDropRef.current(event)} />
}
function dropPayload(source: string | null) {
const values = new Map([[WORKSPACE_FILE_PATH_MIME, '/same/path/report.html']])
if (source !== null) {
values.set(WORKSPACE_FILE_DRAG_SOURCE_MIME, source)
}
return { types: [...values.keys()], getData: (type: string) => values.get(type) ?? '' }
}
describe('browser file drops preserve source ownership', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.target.mockReturnValue({ status: 'ready', url: 'file:///same/path/report.html' })
})
afterEach(cleanup)
it.each(['runtime:host-a', 'ssh:host-a', 'runtime:unresolved-owner'])(
'rejects a %s source before local URL resolution',
(executionHostId) => {
const view = render(<DropSurface />)
fireEvent.drop(view.getByTestId('drop'), {
dataTransfer: dropPayload(
JSON.stringify({
version: 1,
executionHostId,
workspaceId: 'source-workspace'
})
)
})
expect(mocks.target).not.toHaveBeenCalled()
expect(mocks.navigate).not.toHaveBeenCalled()
expect(mocks.notice).toHaveBeenCalledWith(
'Open in Orca Browser is only available for local files.'
)
}
)
it.each([null, '{broken', '{"version":1,"executionHostId":"local"}'])(
'rejects an unresolved source %s',
(source) => {
const view = render(<DropSurface />)
fireEvent.drop(view.getByTestId('drop'), { dataTransfer: dropPayload(source) })
expect(mocks.target).not.toHaveBeenCalled()
expect(mocks.navigate).not.toHaveBeenCalled()
}
)
it('accepts a desktop source from another workspace into the floating browser', () => {
const view = render(<DropSurface />)
fireEvent.drop(view.getByTestId('drop'), {
dataTransfer: dropPayload(
JSON.stringify({
version: 1,
executionHostId: 'local',
workspaceId: 'source-workspace'
})
)
})
expect(mocks.target).toHaveBeenCalledWith({
filePath: '/same/path/report.html',
worktreeId: 'global-floating-terminal'
})
expect(mocks.notice).toHaveBeenCalledWith('Browser page is not ready for file drops.')
})
})
@@ -10,11 +10,15 @@ import {
type SetStateAction
} from 'react'
import { translate } from '@/i18n/i18n'
import { getWorkspaceFileBrowserOpenTarget } from '@/lib/file-preview'
import {
getWorkspaceFileBrowserOpenTarget,
REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE
} from '@/lib/file-preview'
import { routeWorkspaceDocAddressSubmission } from './workspace-doc-address-submission'
import {
getWorkspaceFileDragRejectionMessage,
readWorkspaceFileDragPaths,
readWorkspaceFileDragSource,
WORKSPACE_FILE_PATH_MIME
} from '@/lib/workspace-file-drag'
import type { BrowserLoadError } from '../../../../../shared/browser-workspace-types'
@@ -173,6 +177,13 @@ export function useBrowserPageNavigationDownloads({
return
}
// A file URL reads this machine, regardless of which workspace owns the browser.
const source = readWorkspaceFileDragSource(event.dataTransfer)
if (source?.executionHostId !== 'local') {
setResourceNotice(REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE)
return
}
const target = getWorkspaceFileBrowserOpenTarget({ filePath, worktreeId })
if (target.status === 'unsupported') {
setResourceNotice(target.message)
+14
View File
@@ -481,6 +481,20 @@ describe('getWorkspaceFileBrowserOpenTarget', () => {
message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE
})
})
it('refuses paired files even when their connection ID is null', () => {
mocks.environmentId = 'managed-host'
expect(
getWorkspaceFileBrowserOpenTarget({
filePath: '/srv/repo/report.html',
worktreeId: 'wt-1'
})
).toEqual({
status: 'unsupported',
reason: 'remote-worktree',
message: REMOTE_FILE_BROWSER_UNSUPPORTED_MESSAGE
})
})
})
// Why the reuse case is pinned here too: the address bar's way into a document must obey the same
+4 -1
View File
@@ -147,7 +147,10 @@ export function getWorkspaceFileBrowserOpenTarget(params: {
filePath: string
worktreeId: string
}): WorkspaceFileBrowserOpenTarget {
if (getConnectionIdForFile(params.worktreeId, params.filePath) !== null) {
if (
getConnectionIdForFile(params.worktreeId, params.filePath) !== null ||
getRuntimeEnvironmentIdForWorktree(useAppStore.getState(), params.worktreeId)
) {
// Why: Chromium resolves file:// URLs on the local machine. Remote files
// need an Orca-served URL before the browser can render them correctly.
return {
@@ -0,0 +1,204 @@
import { writeFileSync } from 'node:fs'
import path from 'node:path'
import type { ElectronApplication, Page } from '@stablyai/playwright-test'
import { expect, test } from './helpers/orca-app'
import { createRestartSession } from './helpers/orca-restart'
import { startOrcadConvertHost } from './helpers/orcad-convert-host'
import { seedRelayEraProfile } from './helpers/orcad-upgrade-profile'
import { convertAndRetain, serverCall } from './helpers/orcad-convert-flow'
import { waitForSessionReady } from './helpers/store'
import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker'
import { openFileExplorer } from './helpers/file-explorer'
import { shellQuote } from './helpers/docker-ssh-relay-target'
import { startBrowserSplitPageServer } from './helpers/browser-split-page-server'
import {
navigateGuest,
waitForGuestUrl,
waitForGuestIdle
} from './helpers/browser-split-guest-probes'
import { toRuntimeExecutionHostId } from '../../src/shared/execution-host'
import { getWorktreeHostIdentity } from '../../src/shared/worktree/host-qualified-identity'
const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE
test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs owned Docker host')
test.skip(process.platform === 'win32', 'Owned POSIX host collision fixture')
async function dropExplorerFile(page: Page, name: string, tabId: string) {
const row = page.locator('[data-file-explorer-row]').filter({ hasText: name })
await expect(row).toBeVisible()
return row.evaluate((element, browserTabId) => {
const webview = document.querySelector<Electron.WebviewTag>(
`[data-browser-overlay-tab-id="${browserTabId}"] webview`
)
if (!webview || !webview.parentElement) {
throw new Error('Missing actual guest container')
}
const dataTransfer = new DataTransfer()
element.dispatchEvent(new DragEvent('dragstart', { bubbles: true, dataTransfer }))
const payload = Object.fromEntries(
[...dataTransfer.types].map((type) => [type, dataTransfer.getData(type)])
)
const rect = webview.getBoundingClientRect()
if (rect.width < 1 || rect.height < 1) {
throw new Error('Actual guest has no drop surface')
}
webview.parentElement.dispatchEvent(
new DragEvent('drop', {
bubbles: true,
cancelable: true,
dataTransfer,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
})
)
element.dispatchEvent(new DragEvent('dragend', { bubbles: true, dataTransfer }))
return payload
}, tabId)
}
async function guestText(page: Page, tabId: string) {
return page.evaluate(async (id) => {
const webview = document.querySelector<Electron.WebviewTag>(
`[data-browser-overlay-tab-id="${id}"] webview`
)
if (!webview) {
throw new Error('Missing actual guest')
}
return {
url: webview.getURL(),
text: await webview.executeJavaScript('document.body.innerText')
}
}, tabId)
}
test('managed Files drag cannot load a desktop same-path HTML file', async ({
testRepoPath
}, testInfo) => {
test.setTimeout(4 * 60_000)
const fileName = 'DROP_OWNER.html'
const filePath = path.join(testRepoPath, fileName)
writeFileSync(filePath, '<html><body>DESKTOP_BROWSER_DROP_OWNER</body></html>')
const host = startOrcadConvertHost('docker', testInfo)
const server = await startBrowserSplitPageServer()
const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! })
let app: ElectronApplication | null = null
try {
if (!host.exec) {
throw new Error('Missing owned host control')
}
host.exec(
`mkdir -p ${shellQuote(path.dirname(testRepoPath))} && git clone --quiet ${shellQuote(host.remoteRepoPath)} ${shellQuote(testRepoPath)} && printf '%s' '<html><body>REMOTE_BROWSER_DROP_OWNER</body></html>' > ${shellQuote(filePath)}`
)
host.exec(
`printf '%s' REMOTE_DROP_READY > ${shellQuote(path.join(testRepoPath, 'REMOTE_DROP_READY.txt'))}`
)
const first = await session.launch()
app = first.app
await waitForSessionReady(first.page)
const nativeId = await first.page.evaluate(async (repoPath) => {
const result = await window.api.repos.add({ path: repoPath })
if ('error' in result) {
throw new Error(result.error)
}
return `${result.repo.id}::${repoPath}`
}, testRepoPath)
await session.close(app)
app = null
const seeded = seedRelayEraProfile(session.userDataDir, host.input, {
repoPath: testRepoPath,
folderPath: host.remoteFolderPath
})
const launched = await session.launch()
app = launched.app
const page = launched.page
await waitForSessionReady(page)
await convertAndRetain(page, session.userDataDir, seeded)
const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find(
(entry) => entry.orcadDeployment?.sshTargetId === seeded.targetId
)
if (!environment) {
throw new Error('Missing actual managed environment')
}
await dismissTransientAnnouncement(page)
const nativeIdentity = getWorktreeHostIdentity({ id: nativeId, hostId: 'local' })
await page.locator(`[data-worktree-host-identity="${nativeIdentity}"]:visible`).click()
await openFileExplorer(page)
const floatingId = await page.evaluate(
(url) => {
const store = window.__store
const state = store?.getState()
if (!store || !state?.settings) {
throw new Error('Missing ready settings')
}
store.setState({ settings: { ...state.settings, floatingTerminalEnabled: true } })
const tab = store.getState().createBrowserTab('global-floating-terminal', url, {
activate: true,
focusAddressBar: false,
targetGroupId: state.ensureWorktreeRootGroup('global-floating-terminal'),
browserRuntimeEnvironmentId: null
})
return tab.id
},
server.pageUrl('float', 1)
)
await expect(page.locator('[data-floating-terminal-panel]')).toHaveCount(1)
if ((await page.locator('[data-floating-terminal-panel][aria-hidden="false"]').count()) === 0) {
await page.evaluate(() => window.dispatchEvent(new Event('orca-toggle-floating-terminal')))
}
await expect(page.locator(`[data-browser-overlay-tab-id="${floatingId}"]`)).toBeVisible()
await waitForGuestUrl(page, floatingId, server.pageUrl('float', 1))
console.log('[native-actual-drag-payload]', await dropExplorerFile(page, fileName, floatingId))
await expect
.poll(async () => (await guestText(page, floatingId)).text)
.toContain('DESKTOP_BROWSER_DROP_OWNER')
console.log('[native-positive-actual-guest]', await guestText(page, floatingId))
await navigateGuest(page, floatingId, server.pageUrl('float', 2))
await waitForGuestIdle(page, floatingId)
const remoteIdentity = getWorktreeHostIdentity({
id: seeded.worktreeId,
hostId: toRuntimeExecutionHostId(environment.id)
})
await page.locator(`[data-worktree-host-identity="${remoteIdentity}"]:visible`).click()
await openFileExplorer(page)
await page.getByRole('button', { name: 'Refresh Explorer', exact: true }).click()
await expect(
page.locator('[data-file-explorer-row]').filter({ hasText: 'REMOTE_DROP_READY.txt' })
).toBeVisible()
expect(
await page.evaluate(() => window.__store?.getState().activeWorkspaceExecutionHostId)
).toBe(toRuntimeExecutionHostId(environment.id))
console.log(
'[independent-managed-source]',
await serverCall(page, environment.id, 'files.read', {
worktree: `id:${seeded.worktreeId}`,
relativePath: fileName
})
)
const remotePayload = await dropExplorerFile(page, fileName, floatingId)
console.log('[managed-actual-drag-payload]', remotePayload)
expect(
JSON.parse(remotePayload['application/x-orca-workspace-file-source'] ?? '{}')
).toMatchObject({
executionHostId: toRuntimeExecutionHostId(environment.id),
workspaceId: seeded.worktreeId
})
await waitForGuestIdle(page, floatingId)
console.log('[managed-drop-actual-guest]', await guestText(page, floatingId))
await page.screenshot({ path: testInfo.outputPath('managed-browser-drop-owner.png') })
expect((await guestText(page, floatingId)).text).not.toContain('DESKTOP_BROWSER_DROP_OWNER')
await expect(
page.getByText('Open in Orca Browser is only available for local files.', { exact: true })
).toBeVisible()
expect((await guestText(page, floatingId)).url).toBe(server.pageUrl('float', 2))
} finally {
try {
if (app) {
await session.close(app)
}
await session.dispose()
await server.close()
} finally {
host.cleanup()
}
}
})
+8 -17
View File
@@ -65,23 +65,7 @@ test('a managed orcad stops after idling and starts again on the next connect',
app = first.app
await waitForSessionReady(first.page)
// A managed host is reached through its server, not a relay, so no relay repo is added.
const remote = await first.page.evaluate(
async (input) => {
const { target: created } = await window.api.ssh.addTarget({ target: input })
const state = await window.api.ssh.connect({ targetId: created.id })
return { targetId: created.id, managedServer: state?.managedServer ?? null }
},
{
label: `orcad idle E2E ${Date.now()}`,
host: target.host,
port: target.port,
username: 'root',
identityFile: target.identityFile,
identitiesOnly: true,
relayGracePeriodSeconds: 1
}
)
expect(remote.managedServer).toMatchObject({ kind: 'managed' })
const remote = await connectManagedHost(first.page, target)
expect(runningOrcadPids(target)).toHaveLength(1)
// While the client is connected the server stays up past its quiet period.
@@ -112,6 +96,13 @@ test('a managed orcad stops after idling and starts again on the next connect',
// A start inherited from the launch-time connect this reconnect dropped would report `serving`.
expect(JSON.parse(connected)).toMatchObject({ kind: 'managed' })
expect(JSON.parse(connected)).not.toHaveProperty('serving')
await expect
.poll(
async () =>
(await callEnvironment(second.page, remote.environmentId, 'terminal.list', {})).ok,
{ timeout: 60_000 }
)
.toBe(true)
expect(runningOrcadPids(target)).toHaveLength(1)
// The restarted server read the record, so a later crash cannot be mistaken for an idle stop.
expect(readIdleStopRecord(target)).toBeNull()