fix(browser): managed SSH terminal links open in the Orca Browser again (#29215)

* fix(browser): managed SSH terminal links open in the Orca Browser again

A managed server reached over SSH has no browser backend of its own, so
links from its panes were offered only the system browser. They now open
in a desktop tab whose page egresses through the SSH host, the same route
new browser tabs in that workspace already use.

Fixes #29077, #27349

* test(e2e): keep the managed workspace host selected for the Cmd/Ctrl-click step

* refactor(browser): keep owner-route checks private; split tab presentation instead

Moving the owner predicates to their own module made their callers count as
new focus reads. Keep them in workspace-browser-tab-open and move the
title/error presentation helpers out to stay under the line limit. The SSH
deployment check also reads the workspace's owner route instead of the
focus-falling-back host lookup.

* test(e2e): assert the managed SSH link page renders in the visible browser tab

* fix(browser): keep a desktop-hosted link tab owned by its runtime workspace

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
OrcaWin
2026-10-15 17:44:35 -07:00
committed by GitHub
co-authored by m4air Neil
parent f670683b22
commit c023d35cd1
6 changed files with 479 additions and 81 deletions
@@ -42,6 +42,14 @@ it.each([
expect(selectPrE2eSpecs([path])).toContain('tests/e2e/ssh-orcad-browser-routing.spec.ts')
})
it.each([
'src/renderer/src/lib/workspace-browser-tab-open.ts',
'src/renderer/src/lib/http-link-destinations.ts',
'src/renderer/src/components/terminal-pane/terminal-http-link-source-owner.ts'
])('routes the managed terminal link oracle from %s', (path) => {
expect(selectPrE2eSpecs([path])).toContain('tests/e2e/ssh-orcad-terminal-browser-link.spec.ts')
})
const workflow = parse(readFileSync('.github/workflows/e2e.yml', 'utf8'))
const prWorkflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
const classify = (specs) => classifyE2eJobs(JSON.stringify(specs))
+9
View File
@@ -170,6 +170,15 @@ export const PR_E2E_SOURCE_ROUTES = [
file
)
},
{
id: 'ssh.orcad-terminal-browser-link',
specs: ['tests/e2e/ssh-orcad-terminal-browser-link.spec.ts'],
matches: (file) =>
isProductSource(file) &&
/^src\/renderer\/src\/(?:lib\/(?:workspace-browser-tab-open|http-link-(?:destinations|routing)|ssh-workspace-browser-route-eligibility)|components\/terminal-pane\/terminal-http-link-source-owner)\.ts$/.test(
file
)
},
{
id: 'ssh.orcad-browser-drop-owner',
specs: ['tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'],
@@ -599,3 +599,116 @@ describe('openWorkspaceBrowserTab', () => {
expect(mocks.state.createBrowserTab).not.toHaveBeenCalled()
})
})
describe('links from a managed server reached over SSH', () => {
function sshDeployedRuntimeState(
orcadDeployment: Record<string, unknown>,
settings: Record<string, unknown> = {}
): Record<string, unknown> {
return {
...ownerState(toRuntimeExecutionHostId('orcad-env')),
runtimeEnvironments: [{ id: 'orcad-env', orcadDeployment }],
// Headless orcad advertises no screencast backend.
runtimeStatusByEnvironmentId: new Map([
['orcad-env', { status: { capabilities: [] }, checkedAt: 1 }]
]),
settings,
createBrowserTab: vi.fn(),
defaultBrowserSessionProfileId: 'client-profile',
defaultBrowserSessionProfileIdByHostId: {}
}
}
const sshDeployment = {
kind: 'ssh',
sshTargetId: 'ssh-target',
sshTargetGeneration: 2,
localPort: 41000,
remotePort: 41001
}
it('opens the link in an SSH-routed desktop tab the runtime workspace still owns', async () => {
mocks.state = sshDeployedRuntimeState(sshDeployment)
expect(canOpenWorkspaceBrowserTabOnRuntime(mocks.getState(), WORKSPACE_ID, 'orcad-env')).toBe(
true
)
await openWorkspaceBrowserTab({
workspaceId: WORKSPACE_ID,
url: 'http://localhost:3000/',
intent: { kind: 'url' },
expectedRuntimeEnvironmentId: 'orcad-env'
})
expect(mocks.state.createBrowserTab).toHaveBeenCalledWith(
WORKSPACE_ID,
'http://localhost:3000/',
expect.objectContaining({
browserRuntimeEnvironmentId: null,
executionHostId: toRuntimeExecutionHostId('orcad-env')
})
)
expect(mocks.createRemote).not.toHaveBeenCalled()
})
it('keeps a screencast-capable deployment on its runtime browser', async () => {
mocks.state = {
...sshDeployedRuntimeState(sshDeployment),
...browserCapableRuntime('orcad-env')
}
await openWorkspaceBrowserTab({
workspaceId: WORKSPACE_ID,
url: 'http://localhost:3000/',
intent: { kind: 'url' },
expectedRuntimeEnvironmentId: 'orcad-env'
})
expect(mocks.createRemote).toHaveBeenCalledWith(
expect.objectContaining({ environmentId: 'orcad-env', waitForRegistration: true })
)
expect(mocks.state.createBrowserTab).not.toHaveBeenCalled()
})
it.each([
['SSH browser routing is off', sshDeployment, { browserSshWorkspaceRoutingEnabled: false }],
[
'the user browses this host from this device',
sshDeployment,
{ browserSshWorkspaceRoutingDisabledTargetIds: ['ssh-target'] }
],
['the server is a WSL deployment', { kind: 'wsl', distro: 'Ubuntu' }, {}]
])('offers only the system browser when %s', async (_label, deployment, settings) => {
mocks.state = sshDeployedRuntimeState(deployment, settings)
expect(canOpenWorkspaceBrowserTabOnRuntime(mocks.getState(), WORKSPACE_ID, 'orcad-env')).toBe(
false
)
await expect(
openWorkspaceBrowserTab({
workspaceId: WORKSPACE_ID,
url: 'http://localhost:3000/',
intent: { kind: 'url' },
expectedRuntimeEnvironmentId: 'orcad-env'
})
).rejects.toThrow('Unable to open URL.')
expect(mocks.state.createBrowserTab).not.toHaveBeenCalled()
expect(mocks.createRemote).not.toHaveBeenCalled()
})
it('does not lend the SSH route to a link asserting another runtime', () => {
mocks.state = sshDeployedRuntimeState(sshDeployment)
expect(canOpenWorkspaceBrowserTabOnRuntime(mocks.getState(), WORKSPACE_ID, 'other-env')).toBe(
false
)
})
it('offers no in-app browser from the paired web client', () => {
mocks.pairedWeb = true
mocks.state = sshDeployedRuntimeState(sshDeployment)
expect(canOpenWorkspaceBrowserTabOnRuntime(mocks.getState(), WORKSPACE_ID, 'orcad-env')).toBe(
false
)
})
})
@@ -1,4 +1,3 @@
import { translate } from '@/i18n/i18n'
import { createWebRuntimeSessionBrowserTab } from '@/runtime/web-runtime-browser-creation'
import { useAppStore } from '@/store'
import type { AppState } from '@/store/types'
@@ -7,15 +6,20 @@ import {
parseExecutionHostId,
type ExecutionHostId
} from '../../../shared/execution-host'
import { SEARCH_ENGINE_LABELS, type SearchEngine } from '../../../shared/browser-url'
import type { BrowserClientHostPlacementPreference } from '../../../shared/browser-client-host-placement'
import type { SearchEngine } from '../../../shared/browser-url'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import { BROWSER_SCREENCAST_RUNTIME_CAPABILITY } from '../../../shared/protocol-version'
import type { BrowserClientHostPlacementPreference } from '../../../shared/browser-client-host-placement'
import {
getClientCreationActionPolicy,
type ClientCreationActionAvailability
} from './client-creation-action-policy'
import { resolveWorktreeOperationRoute } from './worktree-operation-route'
import {
intentPresentation,
openFailure,
validateTarget
} from './workspace-browser-tab-presentation'
import { getExecutionHostIdForWorktree } from './worktree-runtime-owner'
import { resolveSshWorkspaceBrowserRouteEligibility } from './ssh-workspace-browser-route-eligibility'
@@ -65,6 +69,39 @@ function isExpectedRuntimeBrowserRoute(
)
}
// Why: a managed server reached over SSH has no browser of its own, so its links open on this
// desktop, whose page the SSH-routed browser gate sends through that host (as new tabs do).
function isSshDeploymentClientBrowserRoute(
state: AppState,
availability: ClientCreationActionAvailability,
route: ReturnType<typeof resolveWorktreeOperationRoute>,
workspaceId: string,
expectedRuntimeEnvironmentId: string
): boolean {
if (
availability.state !== 'enabled' ||
availability.provider !== 'local-client' ||
workspaceId === FLOATING_TERMINAL_WORKTREE_ID ||
!route
) {
return false
}
const expectedEnvironmentId = expectedRuntimeEnvironmentId.trim()
const host = parseExecutionHostId(route.executionHostId)
const eligibility = resolveSshWorkspaceBrowserRouteEligibility(
route.executionHostId,
state.settings,
state.runtimeEnvironments
)
return (
Boolean(expectedEnvironmentId) &&
route.runtimeEnvironmentId?.trim() === expectedEnvironmentId &&
host?.kind === 'runtime' &&
host.environmentId === expectedEnvironmentId &&
eligibility?.eligible === true
)
}
export function canOpenWorkspaceBrowserTabOnRuntime(
state: AppState,
workspaceId: string,
@@ -72,12 +109,21 @@ export function canOpenWorkspaceBrowserTabOnRuntime(
): boolean {
const availability = getClientCreationActionPolicy(state, workspaceId)['managed-browser']
const route = resolveWorktreeOperationRoute(state, workspaceId)
return isExpectedRuntimeBrowserRoute(
state,
availability,
route,
workspaceId,
expectedRuntimeEnvironmentId
return (
isExpectedRuntimeBrowserRoute(
state,
availability,
route,
workspaceId,
expectedRuntimeEnvironmentId
) ||
isSshDeploymentClientBrowserRoute(
state,
availability,
route,
workspaceId,
expectedRuntimeEnvironmentId
)
)
}
@@ -117,82 +163,21 @@ export function canOpenWorkspaceBrowserTabOnSsh(
return isExpectedSshBrowserRoute(state, availability, route, workspaceId, expectedSshConnectionId)
}
// Why: concurrent URL tabs are indistinguishable under a shared "Open URL"
// label until the page title loads; the query string stays out so a typed URL
// does not park credentials or tokens in persisted tab state.
function urlTabTitle(url: string): string | null {
try {
// host, not hostname: localhost:3000 and localhost:5173 are different tabs.
const parsed = new URL(url)
return `${parsed.host}${parsed.pathname === '/' ? '' : parsed.pathname}`
} catch {
return null
}
}
function intentPresentation(
intent: WorkspaceBrowserTabIntent,
url: string
): {
error: string
title: string
} {
if (intent.kind === 'url') {
return {
error: translate('auto.lib.workspace.browser.tab.open.urlFailed', 'Unable to open URL.'),
title:
urlTabTitle(url) ??
translate('auto.components.tab.bar.TabBarCreateEntry.7cdf8ee0c8', 'Open URL')
}
}
const engine = SEARCH_ENGINE_LABELS[intent.engine]
return {
error: translate(
'auto.lib.workspace.browser.tab.open.searchFailed',
'Unable to search with {{value0}}.',
{ value0: engine }
),
title: translate(
'auto.components.tab.bar.TabBarCreateEntry.searchProvider',
'Search {{value0}}',
{ value0: engine }
)
}
}
// Why: the UI string stays friendly and query-free, but the diagnosable reason
// rides along as `cause` so a failed open is not indistinguishable in logs.
function openFailure(message: string, reason: string, cause?: unknown): Error {
// Why: callers only surface `message`, so log the reason here or a failed open
// leaves no trace at all. The reason alone — never `cause` — keeps the typed
// URL and search query out of the console.
console.warn(`[workspace-browser-tab-open] ${reason}`)
return new Error(message, {
cause: new Error(reason, cause === undefined ? undefined : { cause })
})
}
function validateTarget(url: string): boolean {
try {
const parsed = new URL(url)
return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && !!parsed.hostname
} catch {
return false
}
}
function createClientBrowserTab(
state: AppState,
request: OpenWorkspaceBrowserTabRequest,
hostId: ExecutionHostId,
presentation: { error: string; title: string }
presentation: { error: string; title: string },
// Why: a desktop-hosted page in a runtime workspace stays owned by that runtime, or the
// unified tab is disowned from its worktree (palette and activation skip it).
ownerHostId: ExecutionHostId = hostId
): void {
try {
state.createBrowserTab(request.workspaceId, request.url, {
activate: request.focusOnCreate !== false,
browserRuntimeEnvironmentId: null,
focusAddressBar: false,
executionHostId: hostId,
executionHostId: ownerHostId,
sessionProfileId:
state.defaultBrowserSessionProfileIdByHostId[hostId] ??
state.defaultBrowserSessionProfileId,
@@ -238,16 +223,26 @@ export async function openWorkspaceBrowserTab(
if (expectedEnvironmentId !== null && expectedSshConnectionId !== null) {
throw openFailure(presentation.error, 'browser owner assertion is ambiguous')
}
if (
const runtimeOwnerRoute =
expectedEnvironmentId !== null &&
!isExpectedRuntimeBrowserRoute(
isExpectedRuntimeBrowserRoute(
state,
availability,
route,
request.workspaceId,
expectedEnvironmentId
)
) {
const sshDeploymentClientRoute =
expectedEnvironmentId !== null &&
!runtimeOwnerRoute &&
isSshDeploymentClientBrowserRoute(
state,
availability,
route,
request.workspaceId,
expectedEnvironmentId
)
if (expectedEnvironmentId !== null && !runtimeOwnerRoute && !sshDeploymentClientRoute) {
throw openFailure(presentation.error, 'asserted runtime cannot provide this managed browser')
}
if (
@@ -280,10 +275,16 @@ export async function openWorkspaceBrowserTab(
)
}
// An asserted runtime owns links opened from remote panes; provider policy may describe the
// viewing client's generic browser surface rather than that pane's execution host.
if (expectedEnvironmentId === null && availability.provider === 'local-client') {
// viewing client's generic browser surface rather than that pane's execution host. The one
// exception is an SSH-deployed server, whose links this desktop hosts on the SSH route.
if (
(expectedEnvironmentId === null || sshDeploymentClientRoute) &&
availability.provider === 'local-client'
) {
const localHostId = host && host.kind !== 'runtime' ? host.id : LOCAL_EXECUTION_HOST_ID
createClientBrowserTab(state, request, localHostId, presentation)
const ownerHostId =
host?.kind === 'runtime' && route.executionHostId ? route.executionHostId : localHostId
createClientBrowserTab(state, request, localHostId, presentation, ownerHostId)
return
}
let created = false
@@ -0,0 +1,67 @@
import { translate } from '@/i18n/i18n'
import { SEARCH_ENGINE_LABELS } from '../../../shared/browser-url'
import type { WorkspaceBrowserTabIntent } from './workspace-browser-tab-open'
// Why: concurrent URL tabs are indistinguishable under a shared "Open URL"
// label until the page title loads; the query string stays out so a typed URL
// does not park credentials or tokens in persisted tab state.
function urlTabTitle(url: string): string | null {
try {
// host, not hostname: localhost:3000 and localhost:5173 are different tabs.
const parsed = new URL(url)
return `${parsed.host}${parsed.pathname === '/' ? '' : parsed.pathname}`
} catch {
return null
}
}
export function intentPresentation(
intent: WorkspaceBrowserTabIntent,
url: string
): {
error: string
title: string
} {
if (intent.kind === 'url') {
return {
error: translate('auto.lib.workspace.browser.tab.open.urlFailed', 'Unable to open URL.'),
title:
urlTabTitle(url) ??
translate('auto.components.tab.bar.TabBarCreateEntry.7cdf8ee0c8', 'Open URL')
}
}
const engine = SEARCH_ENGINE_LABELS[intent.engine]
return {
error: translate(
'auto.lib.workspace.browser.tab.open.searchFailed',
'Unable to search with {{value0}}.',
{ value0: engine }
),
title: translate(
'auto.components.tab.bar.TabBarCreateEntry.searchProvider',
'Search {{value0}}',
{ value0: engine }
)
}
}
// Why: the UI string stays friendly and query-free, but the diagnosable reason
// rides along as `cause` so a failed open is not indistinguishable in logs.
export function openFailure(message: string, reason: string, cause?: unknown): Error {
// Why: callers only surface `message`, so log the reason here or a failed open
// leaves no trace at all. The reason alone — never `cause` — keeps the typed
// URL and search query out of the console.
console.warn(`[workspace-browser-tab-open] ${reason}`)
return new Error(message, {
cause: new Error(reason, cause === undefined ? undefined : { cause })
})
}
export function validateTarget(url: string): boolean {
try {
const parsed = new URL(url)
return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && !!parsed.hostname
} catch {
return false
}
}
@@ -0,0 +1,200 @@
import type { ElectronApplication, Page } from '@stablyai/playwright-test'
import { expect, test } from './helpers/orca-app'
import { createRestartSession } from './helpers/orca-restart'
import { waitForSessionReady } from './helpers/store'
import {
cleanupDockerSshHostTarget,
DOCKER_SSH_HOST_REMOTE_REPO_PATH,
startDockerSshHostTarget
} from './helpers/docker-ssh-host-target'
import { seedRelayEraProfile } from './helpers/orcad-upgrade-profile'
import {
readSshRemoteOnlyRequests,
SSH_REMOTE_ONLY_ORIGIN,
SSH_REMOTE_ONLY_PORT,
startSshRemoteOnlyBrowserFixture
} from './helpers/ssh-remote-only-browser-fixture'
import { convertAndRetain } from './helpers/orcad-convert-flow'
import {
activateManagedWorkspace,
expectPaneText,
managedEnvironmentId,
openTerminalTab,
ORCAD_TEMPLATE,
skipUnlessManagedDockerHost,
typeInTerminal
} from './helpers/orcad-managed-workspace'
skipUnlessManagedDockerHost()
async function terminalLinkPoint(
page: Page,
tabId: string,
link: string
): Promise<{ x: number; y: number }> {
return page.evaluate(
({ tabId, link }) => {
const pane = window.__paneManagers?.get(tabId)?.getActivePane?.()
const screen = pane?.terminal.element?.querySelector<HTMLElement>('.xterm-screen')
const cell = pane?.terminal.dimensions?.css.cell
if (!pane || !screen || !cell?.width || !cell.height) {
throw new Error('terminal screen unavailable')
}
const buffer = pane.terminal.buffer.active
for (let row = pane.terminal.rows - 1; row >= 0; row -= 1) {
const text = buffer.getLine(buffer.viewportY + row)?.translateToString(false) ?? ''
const column = text.indexOf(link)
if (column !== -1) {
const rect = screen.getBoundingClientRect()
return {
x: rect.left + (column + link.length / 2) * cell.width,
y: rect.top + (row + 0.5) * cell.height
}
}
}
throw new Error(`terminal link ${link} not on screen`)
},
{ tabId, link }
)
}
/** Prints origin+path as one link; split apart so the typed command never shows the whole URL. */
async function printLink(page: Page, tabId: string, origin: string, path: string): Promise<void> {
await typeInTerminal(page, tabId, `printf '%s%s\\n' '${origin}' '${path}'`)
await expectPaneText(page, tabId, `${origin}${path}`)
}
function browserTabUrls(page: Page, worktreeId: string): Promise<string[]> {
return page.evaluate(
(id) => (window.__store?.getState().browserTabsByWorktree[id] ?? []).map((tab) => tab.url),
worktreeId
)
}
function browserTabIdForUrl(page: Page, worktreeId: string, url: string): Promise<string | null> {
return page.evaluate(
({ id, url }) =>
(window.__store?.getState().browserTabsByWorktree[id] ?? []).find((tab) => tab.url === url)
?.id ?? null,
{ id: worktreeId, url }
)
}
/** The opened page must be the visible browser surface and render the host's response. */
async function expectRenderedLink(page: Page, worktreeId: string, url: string): Promise<void> {
await expect.poll(() => browserTabIdForUrl(page, worktreeId, url)).not.toBeNull()
const tabId = await browserTabIdForUrl(page, worktreeId, url)
const webview = page.locator(`[data-browser-overlay-tab-id="${tabId}"] webview`)
await expect(webview).toBeVisible({ timeout: 30_000 })
await expect
.poll(
() =>
webview.evaluate(async (guest: Electron.WebviewTag) => {
try {
return await guest.executeJavaScript('document.querySelector("#marker")?.textContent')
} catch {
return null
}
}),
{ timeout: 60_000, message: 'the opened page never rendered the host response' }
)
.toMatch(/^cookie:/)
}
test('a managed SSH terminal link opens in the Orca Browser through the SSH host', async (// oxlint-disable-next-line no-empty-pattern -- Owns app launch.
{}, testInfo) => {
test.setTimeout(5 * 60_000)
const target = startDockerSshHostTarget(testInfo)
const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: ORCAD_TEMPLATE! })
let app: ElectronApplication | null = null
try {
startSshRemoteOnlyBrowserFixture(target)
// The first launch only creates the profile the SSH host is saved into.
const first = await session.launch()
app = first.app
await waitForSessionReady(first.page)
await session.close(first.app)
app = null
const seeded = seedRelayEraProfile(
session.userDataDir,
{
label: `orcad terminal link E2E ${Date.now()}`,
host: target.host,
port: target.port,
username: 'root',
identityFile: target.identityFile,
identitiesOnly: true,
relayGracePeriodSeconds: 1
},
{ repoPath: DOCKER_SSH_HOST_REMOTE_REPO_PATH, folderPath: '/tmp' }
)
const launched = await session.launch()
app = launched.app
const page = launched.page
await waitForSessionReady(page)
await convertAndRetain(page, session.userDataDir, seeded)
const environmentId = await managedEnvironmentId(page, seeded.targetId)
await activateManagedWorkspace(page, seeded.worktreeId, environmentId)
await page.evaluate(() =>
window.__store?.getState().updateSettings({
openLinksInApp: true,
terminalLinkClickBehavior: 'actions'
})
)
const tabId = await openTerminalTab(page, seeded.worktreeId)
// The link action popover offers the Orca Browser for a managed server's pane.
const popoverUrl = `${SSH_REMOTE_ONLY_ORIGIN}/echo/popover`
await printLink(page, tabId, SSH_REMOTE_ONLY_ORIGIN, '/echo/popover')
const popoverPoint = await terminalLinkPoint(page, tabId, popoverUrl)
await page.mouse.move(popoverPoint.x, popoverPoint.y)
await page.mouse.click(popoverPoint.x, popoverPoint.y)
const popover = page.locator('[data-terminal-link-action-popover]')
await expect(popover).toBeVisible()
await expect(popover.getByRole('button').filter({ hasText: 'System Browser' })).toBeVisible()
const orcaBrowser = popover.getByRole('button').filter({ hasText: 'Orca Browser' })
await expect(orcaBrowser).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('managed-terminal-link-popover.png') })
await orcaBrowser.click()
await expect
.poll(() => browserTabUrls(page, seeded.worktreeId), { timeout: 30_000 })
.toContain(popoverUrl)
// Only the SSH host resolves this origin, so the request proves the page egressed there.
await expect
.poll(() => readSshRemoteOnlyRequests(target).map((request) => request.path), {
timeout: 60_000,
message: 'the Orca Browser page never reached the SSH host'
})
.toContain('/echo/popover')
await expectRenderedLink(page, seeded.worktreeId, popoverUrl)
// Cmd/Ctrl-click opens a remote localhost dev-server URL straight into the Orca Browser.
const modifierUrl = `http://localhost:${SSH_REMOTE_ONLY_PORT}/echo/modifier`
await printLink(page, tabId, `http://localhost:${SSH_REMOTE_ONLY_PORT}`, '/echo/modifier')
const modifierPoint = await terminalLinkPoint(page, tabId, modifierUrl)
await page.mouse.move(modifierPoint.x, modifierPoint.y)
await page.keyboard.down('ControlOrMeta')
await page.mouse.click(modifierPoint.x, modifierPoint.y)
await page.keyboard.up('ControlOrMeta')
await expect
.poll(() => browserTabUrls(page, seeded.worktreeId), { timeout: 30_000 })
.toContain(modifierUrl)
await expect
.poll(() => readSshRemoteOnlyRequests(target).map((request) => request.path), {
timeout: 60_000,
message: 'the Cmd/Ctrl-clicked localhost link never reached the SSH host'
})
.toContain('/echo/modifier')
await expectRenderedLink(page, seeded.worktreeId, modifierUrl)
await page.screenshot({ path: testInfo.outputPath('managed-terminal-link-opened.png') })
} finally {
try {
if (app) {
await session.close(app)
}
await session.dispose()
} finally {
cleanupDockerSshHostTarget(target)
}
}
})