From c9a9ad33b8eee5d3d89b7dba645440fa41822879 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 11 Oct 2026 05:35:50 -0700 Subject: [PATCH] test(e2e): one managed-host setup path and skip gate for the ssh-orcad specs (#27465) Co-authored-by: m4air --- config/scripts/pr-e2e-source-routing.mjs | 6 +- tests/e2e/helpers/orcad-managed-workspace.ts | 67 +++++++- .../ssh-orcad-browser-capabilities.spec.ts | 6 +- .../e2e/ssh-orcad-browser-drop-owner.spec.ts | 78 ++++------ tests/e2e/ssh-orcad-browser-routing.spec.ts | 6 +- .../ssh-orcad-browser-service-status.spec.ts | 6 +- tests/e2e/ssh-orcad-editor-ownership.spec.ts | 146 +++++++----------- .../ssh-orcad-editor-watch-recovery.spec.ts | 86 +++-------- .../ssh-orcad-explorer-selected-host.spec.ts | 10 +- .../ssh-orcad-explorer-watch-recovery.spec.ts | 75 +++------ .../e2e/ssh-orcad-markdown-conversion.spec.ts | 88 ++++------- .../ssh-orcad-markdown-link-refresh.spec.ts | 73 +++------ .../ssh-orcad-markdown-live-documents.spec.ts | 108 ++++--------- tests/e2e/ssh-orcad-open-in-owner.spec.ts | 92 ++++------- tests/e2e/ssh-orcad-terminal-journeys.spec.ts | 38 ++--- .../e2e/ssh-orcad-terminal-root-owner.spec.ts | 97 +++++------- .../e2e/ssh-orcad-vm-provisioned-root.spec.ts | 7 +- tests/e2e/ssh-orcad-workspace-tools.spec.ts | 7 +- 18 files changed, 359 insertions(+), 637 deletions(-) diff --git a/config/scripts/pr-e2e-source-routing.mjs b/config/scripts/pr-e2e-source-routing.mjs index 806c0341bc1..d634a5f378c 100644 --- a/config/scripts/pr-e2e-source-routing.mjs +++ b/config/scripts/pr-e2e-source-routing.mjs @@ -144,7 +144,7 @@ export const PR_E2E_SOURCE_ROUTES = [ 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-host-target|browser-split-(?:guest-probes|page-server))\.ts$/.test( + /^tests\/e2e\/helpers\/(?:orcad-managed-workspace|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target|browser-split-(?:guest-probes|page-server))\.ts$/.test( file ) || (isProductSource(file) && @@ -156,7 +156,7 @@ export const PR_E2E_SOURCE_ROUTES = [ id: 'ssh.orcad-open-in-owner', specs: ['tests/e2e/ssh-orcad-open-in-owner.spec.ts'], matches: (file) => - /^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( + /^tests\/e2e\/helpers\/(?:orcad-managed-workspace|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( file ) || (isProductSource(file) && @@ -168,7 +168,7 @@ export const PR_E2E_SOURCE_ROUTES = [ id: 'ssh.orcad-terminal-root-owner', specs: ['tests/e2e/ssh-orcad-terminal-root-owner.spec.ts'], matches: (file) => - /^tests\/e2e\/helpers\/(?:terminal-workspace-root-link|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( + /^tests\/e2e\/helpers\/(?:terminal-workspace-root-link|orcad-managed-workspace|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( file ) || (isProductSource(file) && diff --git a/tests/e2e/helpers/orcad-managed-workspace.ts b/tests/e2e/helpers/orcad-managed-workspace.ts index b17112155dd..b62c7ee850d 100644 --- a/tests/e2e/helpers/orcad-managed-workspace.ts +++ b/tests/e2e/helpers/orcad-managed-workspace.ts @@ -4,19 +4,27 @@ * managed-host journey specs share, driven through the tab bar and the keyboard. */ import type { ElectronApplication, Page, TestInfo } from '@stablyai/playwright-test' -import { expect } from './orca-app' +import { expect, test } from './orca-app' import { createRestartSession } from './orca-restart' import { startOrcadConvertHost, type OrcadConvertHost } from './orcad-convert-host' import { seedRelayEraProfile, type RelayEraProfile } from './orcad-upgrade-profile' import { convertAndRetain } from './orcad-convert-flow' import { readPairedPaneContent } from './paired-host-terminal' -import { waitForSessionReady } from './store' +import { waitForSessionReady, waitForStartupWorktreeRefresh } from './store' import { dismissTransientAnnouncement } from './ssh-config-host-picker' import { focusActiveTerminalInput } from './terminal' import { toRuntimeExecutionHostId } from '../../../src/shared/execution-host' export const ORCAD_TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE +/** Skips the calling spec file unless the Docker SSH host and the orcad template are available. */ +export function skipUnlessManagedDockerHost(): void { + test.skip( + !ORCAD_TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', + 'Needs Docker and server template' + ) +} + export type ManagedHostFixture = { host: OrcadConvertHost session: ReturnType @@ -53,32 +61,75 @@ export type ManagedWorkspace = { environmentId: string } -/** Leaves the managed repository active; each launch is recorded on `fixture` for teardown. */ -export async function launchManagedWorkspace( - fixture: ManagedHostFixture, +export type ManagedProfileOptions = { + /** Overrides the host's repository, e.g. a same-path desktop collision. */ + repoPath?: string /** Overrides the host's folder workspace, e.g. to keep /tmp outside every workspace. */ - options: { folderPath?: string } = {} + folderPath?: string + /** Runs on the profile-creating launch, before the SSH profile is seeded. */ + beforeSeed?: (page: Page) => Promise + /** Runs as soon as the converting launch starts, before the session is ready. */ + onLaunch?: (page: Page, seeded: RelayEraProfile) => void | Promise +} + +/** Seeds a relay-era profile and converts it; each launch is recorded on `fixture` for teardown. */ +export async function launchConvertedProfile( + fixture: ManagedHostFixture, + options: ManagedProfileOptions = {} ): Promise { const { host, session } = fixture // The first launch only creates the profile the saved SSH host is written into. const first = await session.launch() fixture.app = first.app await waitForSessionReady(first.page) + await options.beforeSeed?.(first.page) await session.close(first.app) fixture.app = null const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, + repoPath: options.repoPath ?? host.remoteRepoPath, folderPath: options.folderPath ?? host.remoteFolderPath }) const { app, page } = await session.launch() fixture.app = app + await options.onLaunch?.(page, seeded) await waitForSessionReady(page) await convertAndRetain(page, session.userDataDir, seeded) const environmentId = await managedEnvironmentId(page, seeded.targetId) - await activateManagedWorkspace(page, seeded.worktreeId, environmentId) return { app, page, seeded, environmentId } } +/** Like `launchConvertedProfile`, and leaves the managed repository active. */ +export async function launchManagedWorkspace( + fixture: ManagedHostFixture, + options: ManagedProfileOptions = {} +): Promise { + const workspace = await launchConvertedProfile(fixture, options) + await activateManagedWorkspace( + workspace.page, + workspace.seeded.worktreeId, + workspace.environmentId + ) + return workspace +} + +/** Quits the live launch and relaunches the same profile, waiting for startup to settle. */ +export async function relaunchManagedApp( + fixture: ManagedHostFixture, + onLaunch?: (page: Page) => void | Promise +): Promise { + if (!fixture.app) { + throw new Error('No live launch to close') + } + await fixture.session.close(fixture.app) + fixture.app = null + const { app, page } = await fixture.session.launch() + fixture.app = app + await onLaunch?.(page) + await waitForSessionReady(page) + await waitForStartupWorktreeRefresh(page) + return page +} + export async function managedEnvironmentId(page: Page, targetId: string): Promise { const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( (entry) => entry.orcadDeployment?.sshTargetId === targetId diff --git a/tests/e2e/ssh-orcad-browser-capabilities.spec.ts b/tests/e2e/ssh-orcad-browser-capabilities.spec.ts index fbe6e29e3d1..0930ef7f762 100644 --- a/tests/e2e/ssh-orcad-browser-capabilities.spec.ts +++ b/tests/e2e/ssh-orcad-browser-capabilities.spec.ts @@ -4,10 +4,10 @@ import { waitForSessionReady } from './helpers/store' import { startOrcadConvertHost } from './helpers/orcad-convert-host' import { reconnect, serverCall } from './helpers/orcad-convert-flow' import { runtimeAdvertisesBrowserClientHosting } from '../../src/shared/browser-client-hosting-eligibility' +import { ORCAD_TEMPLATE, skipUnlessManagedDockerHost } from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') -test.use({ orcaAppExtraEnv: { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE } }) +skipUnlessManagedDockerHost() +test.use({ orcaAppExtraEnv: { ORCA_ORCAD_TEMPLATE_PATH: ORCAD_TEMPLATE } }) test('a backendless managed host does not promise client-hosted browser creation', async ({ orcaPage: page diff --git a/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts b/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts index 465e7c4b980..88de5b66bda 100644 --- a/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts +++ b/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts @@ -1,15 +1,16 @@ import { writeFileSync } from 'node:fs' import path from 'node:path' -import type { ElectronApplication, Page } from '@stablyai/playwright-test' +import type { 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 { serverCall } from './helpers/orcad-convert-flow' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { openFileExplorer } from './helpers/file-explorer' import { startBrowserSplitPageServer } from './helpers/browser-split-page-server' +import { + launchConvertedProfile, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' import { navigateGuest, waitForGuestUrl, @@ -19,8 +20,7 @@ import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { getWorktreeHostIdentity } from '../../src/shared/worktree/host-qualified-identity' import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs owned Docker host') +skipUnlessManagedDockerHost() test.skip(process.platform === 'win32', 'Owned POSIX host collision fixture') async function dropExplorerFile(page: Page, name: string, tabId: string) { @@ -78,11 +78,9 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ const fileName = 'DROP_OWNER.html' const filePath = path.join(testRepoPath, fileName) writeFileSync(filePath, 'DESKTOP_BROWSER_DROP_OWNER') - const host = startOrcadConvertHost('docker', testInfo) const server = await startBrowserSplitPageServer() - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture if (!host.exec) { throw new Error('Missing owned host control') } @@ -92,33 +90,19 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ host.exec( `printf '%s' REMOTE_DROP_READY > ${quotePosixShell(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, { + let nativeId = '' + const { page, seeded, environmentId } = await launchConvertedProfile(fixture, { repoPath: testRepoPath, - folderPath: host.remoteFolderPath + beforeSeed: async (page) => { + nativeId = await 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) + } }) - 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() @@ -156,7 +140,7 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ await waitForGuestIdle(page, floatingId) const remoteIdentity = getWorktreeHostIdentity({ id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) + hostId: toRuntimeExecutionHostId(environmentId) }) await page.locator(`[data-worktree-host-identity="${remoteIdentity}"]:visible`).click() await openFileExplorer(page) @@ -166,10 +150,10 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ ).toBeVisible() expect( await page.evaluate(() => window.__store?.getState().activeWorkspaceExecutionHostId) - ).toBe(toRuntimeExecutionHostId(environment.id)) + ).toBe(toRuntimeExecutionHostId(environmentId)) console.log( '[independent-managed-source]', - await serverCall(page, environment.id, 'files.read', { + await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: fileName }) @@ -179,7 +163,7 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ expect( JSON.parse(remotePayload['application/x-orca-workspace-file-source'] ?? '{}') ).toMatchObject({ - executionHostId: toRuntimeExecutionHostId(environment.id), + executionHostId: toRuntimeExecutionHostId(environmentId), workspaceId: seeded.worktreeId }) await waitForGuestIdle(page, floatingId) @@ -190,15 +174,5 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ 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() - } - } + }).finally(() => server.close()) }) diff --git a/tests/e2e/ssh-orcad-browser-routing.spec.ts b/tests/e2e/ssh-orcad-browser-routing.spec.ts index 8eceb449795..5549570460e 100644 --- a/tests/e2e/ssh-orcad-browser-routing.spec.ts +++ b/tests/e2e/ssh-orcad-browser-routing.spec.ts @@ -24,9 +24,9 @@ import { deriveBrowserRoutePartition } from '../../src/main/browser/browser-rout import { sshExecutionHostStorageIdentity } from '../../src/main/browser/browser-execution-host-storage-identity' import { reconnect } from './helpers/orcad-convert-flow' import { navigateGuest } from './helpers/browser-split-guest-probes' +import { ORCAD_TEMPLATE, skipUnlessManagedDockerHost } from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() /** The partition a relay-era build kept this host's browser storage in (local-ssh-browser-partitions). */ function relayEraPartition(targetId: string): string { @@ -147,7 +147,7 @@ test('retained and new browser tabs keep SSH routing and login cookies after man value: SSH_REMOTE_ONLY_COOKIE_VALUE } ) - cpSync(TEMPLATE!, template, { recursive: true }) + cpSync(ORCAD_TEMPLATE!, template, { recursive: true }) const server = await reconnect(page, remote.targetId) console.log('[browser-conversion-connect]', server) expect(JSON.parse(server)).toMatchObject({ kind: 'managed' }) diff --git a/tests/e2e/ssh-orcad-browser-service-status.spec.ts b/tests/e2e/ssh-orcad-browser-service-status.spec.ts index 414f427655d..d3f95b58929 100644 --- a/tests/e2e/ssh-orcad-browser-service-status.spec.ts +++ b/tests/e2e/ssh-orcad-browser-service-status.spec.ts @@ -16,9 +16,9 @@ import { } from './helpers/ssh-remote-only-browser-fixture' import { createRetentionFixtureDirectory } from './helpers/host-created-terminal-retention-oracle' import { reconnect, serverCall } from './helpers/orcad-convert-flow' +import { ORCAD_TEMPLATE, skipUnlessManagedDockerHost } from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() test('an unavailable browser on a responding managed host does not report a server outage', async (// oxlint-disable-next-line no-empty-pattern -- Owns app launch. {}, testInfo) => { @@ -58,7 +58,7 @@ test('an unavailable browser on a responding managed host does not report a serv } }) await waitForSessionReady(page) - cpSync(TEMPLATE!, template, { recursive: true }) + cpSync(ORCAD_TEMPLATE!, template, { recursive: true }) const server = await reconnect(page, remote.targetId) console.log('[browser-conversion-connect]', server) expect(JSON.parse(server)).toMatchObject({ kind: 'managed' }) diff --git a/tests/e2e/ssh-orcad-editor-ownership.spec.ts b/tests/e2e/ssh-orcad-editor-ownership.spec.ts index e4ac2ace8cd..38d0f8c290a 100644 --- a/tests/e2e/ssh-orcad-editor-ownership.spec.ts +++ b/tests/e2e/ssh-orcad-editor-ownership.spec.ts @@ -1,87 +1,63 @@ -import type { ElectronApplication } from '@stablyai/playwright-test' +import type { 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, waitForStartupWorktreeRefresh } from './helpers/store' -import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' -import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + activateManagedWorkspace, + launchManagedWorkspace, + relaunchManagedApp, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() + +async function reportEditorRecords(page: Page, worktreeId: string): Promise { + page.on('console', (message) => { + if (message.text().startsWith('[editor-records]')) { + console.log(message.text()) + } + }) + await page.evaluate((worktreeId) => { + const store = window.__store + if (!store) { + throw new Error('Missing renderer store') + } + const report = (state: ReturnType) => { + console.log( + '[editor-records]', + JSON.stringify({ + files: state.openFiles.filter((file) => file.worktreeId === worktreeId), + tabs: state.unifiedTabsByWorktree[worktreeId], + activeHost: state.activeWorkspaceExecutionHostId, + trace: new Error('Editor file mutation').stack + }) + ) + } + report(store.getState()) + store.subscribe((state, previous) => { + if (state.openFiles !== previous.openFiles) { + report(state) + } + }) + }, worktreeId) +} test('converted editor has one file record per document', async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(4 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture const marker = `REMOTE_MARKDOWN_${Date.now()}` host.exec!( `printf '# ${marker}\n\nHost-owned Markdown.\n' > '${host.remoteRepoPath}/README.md'` ) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath: host.remoteFolderPath + const { page, seeded, environmentId } = await launchManagedWorkspace(fixture, { + onLaunch: (page, seeded) => reportEditorRecords(page, seeded.worktreeId) }) - const launched = await session.launch() - app = launched.app - const page = launched.page - page.on('console', (message) => { - if (message.text().startsWith('[editor-records]')) { - console.log(message.text()) - } - }) - await page.evaluate((worktreeId) => { - const store = window.__store - if (!store) { - throw new Error('Missing renderer store') - } - const report = (state: ReturnType) => { - console.log( - '[editor-records]', - JSON.stringify({ - files: state.openFiles.filter((file) => file.worktreeId === worktreeId), - tabs: state.unifiedTabsByWorktree[worktreeId], - activeHost: state.activeWorkspaceExecutionHostId, - trace: new Error('Editor file mutation').stack - }) - ) - } - report(store.getState()) - store.subscribe((state, previous) => { - if (state.openFiles !== previous.openFiles) { - report(state) - } - }) - }, seeded.worktreeId) - await waitForSessionReady(page) - await convertAndRetain(page, session.userDataDir, seeded) - const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( - (e) => e.orcadDeployment?.sshTargetId === seeded.targetId - ) - if (!environment) { - throw new Error('Missing managed environment') - } - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(page) await expect(page.locator('.rich-markdown-editor')).toContainText(marker, { timeout: 30_000 }) console.log( '[host-tabs]', - await serverCall(page, environment.id, 'session.tabs.list', { + await serverCall(page, environmentId, 'session.tabs.list', { worktree: `id:${seeded.worktreeId}` }) ) @@ -104,24 +80,12 @@ test('converted editor has one file record per document', async (// oxlint-disab .poll(() => host.exec!(`cat '${seeded.sessionFilePath}'`), { timeout: 30_000 }) .toContain(savedMarker) console.log('[saved-on-host]', host.exec!(`cat '${seeded.sessionFilePath}'`)) - await session.close(app) - app = null - const restarted = await session.launch() - app = restarted.app - await waitForSessionReady(restarted.page) - await waitForStartupWorktreeRefresh(restarted.page) - await restarted.page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(restarted.page) - await expect(restarted.page.locator('.rich-markdown-editor')).toContainText(savedMarker, { + const restarted = await relaunchManagedApp(fixture) + await activateManagedWorkspace(restarted, seeded.worktreeId, environmentId) + await expect(restarted.locator('.rich-markdown-editor')).toContainText(savedMarker, { timeout: 30_000 }) - const restoredFiles = await restarted.page.evaluate( + const restoredFiles = await restarted.evaluate( (worktreeId) => window.__store?.getState().openFiles.filter((file) => file.worktreeId === worktreeId), seeded.worktreeId @@ -130,12 +94,6 @@ test('converted editor has one file record per document', async (// oxlint-disab expect(restoredFiles?.filter((file) => file.filePath === seeded.sessionFilePath)).toHaveLength( 1 ) - await restarted.page.screenshot({ path: testInfo.outputPath('editor-restored.png') }) - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + await restarted.screenshot({ path: testInfo.outputPath('editor-restored.png') }) + }) }) diff --git a/tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts b/tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts index 6541bfb536c..1faae5c3901 100644 --- a/tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts +++ b/tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts @@ -1,15 +1,15 @@ -import type { ElectronApplication, Page } from '@stablyai/playwright-test' +import type { 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, waitForStartupWorktreeRefresh } from './helpers/store' -import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' -import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + activateManagedWorkspace, + launchConvertedProfile, + relaunchManagedApp, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() function captureRendererMessages(page: Page): void { page.on('console', (message) => { @@ -27,57 +27,23 @@ for (const recovery of ['conversion', 'server restart'] as const) { test(`remote editors receive host file changes after ${recovery}`, async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(4 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture if (!host.exec) { throw new Error('Docker controls are unavailable') } const execute = host.exec - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null const marker = `REMOTE_MARKDOWN_${Date.now()}` execute( `printf '# ${marker}\n\nHost-owned Markdown.\n\n[[LATE]]\n' > '${host.remoteRepoPath}/README.md'` ) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath: host.remoteFolderPath - }) - const launched = await session.launch() - app = launched.app - let page = launched.page - captureRendererMessages(page) - await waitForSessionReady(page) - await convertAndRetain(page, session.userDataDir, seeded) - const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( - (e) => e.orcadDeployment?.sshTargetId === seeded.targetId - ) - if (!environment) { - throw new Error('Missing managed environment') - } - if (recovery === 'server restart') { - await session.close(app) - app = null - const restarted = await session.launch() - app = restarted.app - page = restarted.page - captureRendererMessages(page) - await waitForSessionReady(page) - await waitForStartupWorktreeRefresh(page) - } - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(page) + const converted = await launchConvertedProfile(fixture, { onLaunch: captureRendererMessages }) + const { seeded, environmentId } = converted + const page = + recovery === 'server restart' + ? await relaunchManagedApp(fixture, captureRendererMessages) + : converted.page + await activateManagedWorkspace(page, seeded.worktreeId, environmentId) await expect(page.locator('.rich-markdown-editor')).toContainText(marker, { timeout: 30_000 }) await page.evaluate(() => { window.addEventListener('orca:worktree-file-change', (event) => { @@ -122,7 +88,7 @@ for (const recovery of ['conversion', 'server restart'] as const) { }) return response.ok ? JSON.stringify(response.result) : '' }, - { id: environment.id, worktreeId: seeded.worktreeId } + { id: environmentId, worktreeId: seeded.worktreeId } ), { timeout: 180_000 } ) @@ -132,7 +98,7 @@ for (const recovery of ['conversion', 'server restart'] as const) { execute('cat /root/.orca-remote/orcad-*/.orcad-pid').trim() ) } - const content = await serverCall(page, environment.id, 'files.read', { + const content = await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: 'README.md' }) @@ -151,7 +117,7 @@ for (const recovery of ['conversion', 'server restart'] as const) { timeout: 15_000 }) execute(`printf 'watcher recovered\\n' > '${host.remoteRepoPath}/WATCH_RECOVERED.txt'`) - const created = await serverCall(page, environment.id, 'files.read', { + const created = await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: 'WATCH_RECOVERED.txt' }) @@ -159,12 +125,6 @@ for (const recovery of ['conversion', 'server restart'] as const) { expect(created).toContain('watcher recovered') await resumed } - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + }) }) } diff --git a/tests/e2e/ssh-orcad-explorer-selected-host.spec.ts b/tests/e2e/ssh-orcad-explorer-selected-host.spec.ts index 93742f1ede0..4d15f299efd 100644 --- a/tests/e2e/ssh-orcad-explorer-selected-host.spec.ts +++ b/tests/e2e/ssh-orcad-explorer-selected-host.spec.ts @@ -9,9 +9,9 @@ import { waitForSessionReady, waitForStartupWorktreeRefresh } from './helpers/st import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { folderWorkspaceKey } from '../../src/shared/workspace-scope' +import { ORCAD_TEMPLATE, skipUnlessManagedDockerHost } from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() for (const selectedHost of ['A', 'B'] as const) { test(`same-ID folder explorer follows selected host ${selectedHost}`, async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. @@ -19,8 +19,10 @@ for (const selectedHost of ['A', 'B'] as const) { test.setTimeout(5 * 60_000) const hostA = startOrcadConvertHost('docker', testInfo) const hostB = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - const otherSession = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) + const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: ORCAD_TEMPLATE! }) + const otherSession = createRestartSession(testInfo, { + ORCA_ORCAD_TEMPLATE_PATH: ORCAD_TEMPLATE! + }) let app: ElectronApplication | null = null let otherApp: ElectronApplication | null = null try { diff --git a/tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts b/tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts index 22be5034097..75620958670 100644 --- a/tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts +++ b/tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts @@ -1,16 +1,16 @@ -import type { ElectronApplication, Page } from '@stablyai/playwright-test' +import type { 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, waitForStartupWorktreeRefresh } from './helpers/store' -import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' -import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + activateManagedWorkspace, + launchConvertedProfile, + relaunchManagedApp, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' import { folderWorkspaceKey } from '../../src/shared/workspace-scope' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() function captureRendererMessages(page: Page): void { page.on('console', (message) => { @@ -28,44 +28,19 @@ for (const workspace of ['worktree', 'folder'] as const) { test(`remote ${workspace} explorer receives host-created files after server restart`, async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(5 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture if (!host.exec) { throw new Error('Docker controls are unavailable') } const execute = host.exec - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null const folderPath = '/tmp/orca-explorer-folder' execute(`mkdir -p '${folderPath}'`) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath + const { seeded, environmentId } = await launchConvertedProfile(fixture, { + folderPath, + onLaunch: captureRendererMessages }) - const launched = await session.launch() - app = launched.app - captureRendererMessages(launched.page) - await waitForSessionReady(launched.page) - await convertAndRetain(launched.page, session.userDataDir, seeded) - const environment = ( - await launched.page.evaluate(() => window.api.runtimeEnvironments.list()) - ).find((entry) => entry.orcadDeployment?.sshTargetId === seeded.targetId) - if (!environment) { - throw new Error('Missing managed environment') - } - await session.close(app) - app = null - const restarted = await session.launch() - app = restarted.app - const page = restarted.page - captureRendererMessages(page) - await waitForSessionReady(page) - await waitForStartupWorktreeRefresh(page) + const page = await relaunchManagedApp(fixture, captureRendererMessages) const folderId = await page.evaluate( (path) => window.__store?.getState().folderWorkspaces.find((folder) => folder.folderPath === path) @@ -77,11 +52,7 @@ for (const workspace of ['worktree', 'folder'] as const) { } const worktreeId = workspace === 'folder' ? folderWorkspaceKey(folderId) : seeded.worktreeId const rootPath = workspace === 'folder' ? folderPath : host.remoteRepoPath - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { id: worktreeId, hostId: toRuntimeExecutionHostId(environment.id) } - ) - await dismissTransientAnnouncement(page) + await activateManagedWorkspace(page, worktreeId, environmentId) await page.evaluate(() => { window.addEventListener('orca:worktree-file-change', (event) => { if (event instanceof CustomEvent) { @@ -121,7 +92,7 @@ for (const workspace of ['worktree', 'folder'] as const) { }) return response.ok ? JSON.stringify(response.result) : '' }, - { id: environment.id, worktreeId } + { id: environmentId, worktreeId } ), { timeout: 180_000 } ) @@ -137,7 +108,7 @@ for (const workspace of ['worktree', 'folder'] as const) { timeout: 30_000 }) execute(`printf 'watcher recovered\n' > '${rootPath}/WATCH_RECOVERED.txt'`) - const created = await serverCall(page, environment.id, 'files.read', { + const created = await serverCall(page, environmentId, 'files.read', { worktree: `id:${worktreeId}`, relativePath: 'WATCH_RECOVERED.txt' }) @@ -153,12 +124,6 @@ for (const workspace of ['worktree', 'folder'] as const) { timeout: 10_000 }) await page.screenshot({ path: testInfo.outputPath('remote-explorer-refreshed.png') }) - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + }) }) } diff --git a/tests/e2e/ssh-orcad-markdown-conversion.spec.ts b/tests/e2e/ssh-orcad-markdown-conversion.spec.ts index 93f2b43e28b..624989c9527 100644 --- a/tests/e2e/ssh-orcad-markdown-conversion.spec.ts +++ b/tests/e2e/ssh-orcad-markdown-conversion.spec.ts @@ -1,69 +1,43 @@ -import type { ElectronApplication } 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 { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + launchManagedWorkspace, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() test('retained Markdown lists documents on its managed owner after conversion', async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(4 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture const marker = `REMOTE_MARKDOWN_${Date.now()}` host.exec!( `printf '# ${marker}\n\nHost-owned Markdown.\n\n[[NOTES]]\n' > '${host.remoteRepoPath}/README.md'` ) host.exec!(`printf '# SIBLING_${marker}\n' > '${host.remoteRepoPath}/NOTES.md'`) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath: host.remoteFolderPath - }) - const launched = await session.launch() - app = launched.app - const page = launched.page const errors: string[] = [] let failureScreenshot: Promise | undefined - page.on('console', (message) => { - if (message.type() === 'error') { - errors.push(message.text()) - console.log('[renderer-error]', message.text()) - if (message.text().includes('Failed to list markdown documents') && !failureScreenshot) { - failureScreenshot = (async () => { - await page.screenshot({ path: testInfo.outputPath('markdown-list-failure.png') }) - })() - } + const { page, seeded, environmentId } = await launchManagedWorkspace(fixture, { + onLaunch: (page) => { + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + console.log('[renderer-error]', message.text()) + if ( + message.text().includes('Failed to list markdown documents') && + !failureScreenshot + ) { + failureScreenshot = (async () => { + await page.screenshot({ path: testInfo.outputPath('markdown-list-failure.png') }) + })() + } + } + }) } }) - await waitForSessionReady(page) - await convertAndRetain(page, session.userDataDir, seeded) - const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( - (e) => e.orcadDeployment?.sshTargetId === seeded.targetId - ) - if (!environment) { - throw new Error('Missing managed environment') - } - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(page) await expect(page.locator('.rich-markdown-editor')).toContainText(marker, { timeout: 30_000 }) console.log( '[markdown-conversion]', @@ -76,7 +50,7 @@ test('retained Markdown lists documents on its managed owner after conversion', await failureScreenshot expect(errors.filter((e) => e.includes('Failed to list markdown documents'))).toEqual([]) expect( - await serverCall(page, environment.id, 'files.listMarkdownDocuments', { + await serverCall(page, environmentId, 'files.listMarkdownDocuments', { worktree: `id:${seeded.worktreeId}` }) ).toContain('NOTES.md') @@ -96,7 +70,7 @@ test('retained Markdown lists documents on its managed owner after conversion', { filePath: seeded.sessionFilePath, worktreeId: seeded.worktreeId, - environmentId: environment.id + environmentId } ) const previewLink = page @@ -110,11 +84,5 @@ test('retained Markdown lists documents on its managed owner after conversion', ).toBeVisible() expect(errors.filter((e) => e.includes('Failed to list markdown documents'))).toEqual([]) await page.screenshot({ path: testInfo.outputPath('markdown-converted-ready.png') }) - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + }) }) diff --git a/tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts b/tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts index 6fd2a0f0bdd..97e314b5623 100644 --- a/tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts +++ b/tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts @@ -1,63 +1,34 @@ -import type { ElectronApplication } 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 { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + launchManagedWorkspace, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() test('retained rich Markdown links refresh after remote document metadata arrives', async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(4 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture const marker = `REMOTE_MARKDOWN_${Date.now()}` host.exec!( `printf '# ${marker}\n\nHost-owned Markdown.\n\n[[NOTES]]\n' > '${host.remoteRepoPath}/README.md'` ) host.exec!(`printf '# SIBLING_${marker}\n' > '${host.remoteRepoPath}/NOTES.md'`) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath: host.remoteFolderPath - }) - const launched = await session.launch() - app = launched.app - const page = launched.page - page.on('console', (message) => { - if (message.type() === 'error') { - console.log('[renderer-error]', message.text()) + const { page, seeded, environmentId } = await launchManagedWorkspace(fixture, { + onLaunch: (page) => { + page.on('console', (message) => { + if (message.type() === 'error') { + console.log('[renderer-error]', message.text()) + } + }) } }) - await waitForSessionReady(page) - await convertAndRetain(page, session.userDataDir, seeded) - const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( - (e) => e.orcadDeployment?.sshTargetId === seeded.targetId - ) - if (!environment) { - throw new Error('Missing managed environment') - } - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(page) await expect(page.locator('.rich-markdown-editor')).toContainText(marker, { timeout: 30_000 }) - const documents = await serverCall(page, environment.id, 'files.listMarkdownDocuments', { + const documents = await serverCall(page, environmentId, 'files.listMarkdownDocuments', { worktree: `id:${seeded.worktreeId}` }) console.log('[host-documents]', documents) @@ -76,13 +47,7 @@ test('retained rich Markdown links refresh after remote document metadata arrive `${host.remoteRepoPath}/NOTES.md` ) console.log('[rich-link-opened-file]', JSON.stringify(opened)) - expect(opened?.runtimeEnvironmentId).toBe(environment.id) + expect(opened?.runtimeEnvironmentId).toBe(environmentId) await page.screenshot({ path: testInfo.outputPath('markdown-converted-ready.png') }) - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + }) }) diff --git a/tests/e2e/ssh-orcad-markdown-live-documents.spec.ts b/tests/e2e/ssh-orcad-markdown-live-documents.spec.ts index 2b8c71d74f1..8268aa4b10f 100644 --- a/tests/e2e/ssh-orcad-markdown-live-documents.spec.ts +++ b/tests/e2e/ssh-orcad-markdown-live-documents.spec.ts @@ -1,80 +1,44 @@ -import type { ElectronApplication } from '@stablyai/playwright-test' +import type { 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, waitForStartupWorktreeRefresh } from './helpers/store' -import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' -import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' +import { serverCall } from './helpers/orcad-convert-flow' +import { + activateManagedWorkspace, + launchConvertedProfile, + relaunchManagedApp, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker and server template') +skipUnlessManagedDockerHost() + +function captureRendererMessages(page: Page, label: string): void { + page.on('console', (message) => { + if ( + message.type() === 'error' || + message.type() === 'warning' || + message.text().startsWith('[file-change-event]') + ) { + console.log(label, message.text()) + } + }) +} test('open remote Markdown previews discover host-created documents', async (// oxlint-disable-next-line no-empty-pattern -- This exercise owns its app launch. {}, testInfo) => { test.setTimeout(4 * 60_000) - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - await session.close(app) - app = null + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture const marker = `REMOTE_MARKDOWN_${Date.now()}` host.exec!( `printf '# ${marker}\n\nHost-owned Markdown.\n\n[[LATE]]\n' > '${host.remoteRepoPath}/README.md'` ) - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { - repoPath: host.remoteRepoPath, - folderPath: host.remoteFolderPath + const { seeded, environmentId } = await launchConvertedProfile(fixture, { + onLaunch: (page) => captureRendererMessages(page, '[renderer-error]') }) - const launched = await session.launch() - app = launched.app - let page = launched.page - page.on('console', (message) => { - if ( - message.type() === 'error' || - message.type() === 'warning' || - message.text().startsWith('[file-change-event]') - ) { - console.log('[renderer-error]', message.text()) - } - }) - await waitForSessionReady(page) - await convertAndRetain(page, session.userDataDir, seeded) - const environment = (await page.evaluate(() => window.api.runtimeEnvironments.list())).find( - (e) => e.orcadDeployment?.sshTargetId === seeded.targetId + const page = await relaunchManagedApp(fixture, (page) => + captureRendererMessages(page, '[renderer-event]') ) - if (!environment) { - throw new Error('Missing managed environment') - } - await session.close(app) - app = null - const restarted = await session.launch() - app = restarted.app - page = restarted.page - page.on('console', (message) => { - if ( - message.type() === 'error' || - message.type() === 'warning' || - message.text().startsWith('[file-change-event]') - ) { - console.log('[renderer-event]', message.text()) - } - }) - await waitForSessionReady(page) - await waitForStartupWorktreeRefresh(page) - await page.evaluate( - ({ id, hostId }) => window.__store?.getState().setActiveWorktree(id, hostId), - { - id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) - } - ) - await dismissTransientAnnouncement(page) + await activateManagedWorkspace(page, seeded.worktreeId, environmentId) await expect(page.locator('.rich-markdown-editor')).toContainText(marker, { timeout: 30_000 }) await page.evaluate( ({ filePath, worktreeId, environmentId }) => { @@ -89,7 +53,7 @@ test('open remote Markdown previews discover host-created documents', async (// { filePath: seeded.sessionFilePath, worktreeId: seeded.worktreeId, - environmentId: environment.id + environmentId } ) const link = page.locator('.markdown-preview').getByRole('link', { name: 'LATE', exact: true }) @@ -103,7 +67,7 @@ test('open remote Markdown previews discover host-created documents', async (// }) }) host.exec!(`printf '# LATE_${marker}\n' > '${host.remoteRepoPath}/LATE.md'`) - const documents = await serverCall(page, environment.id, 'files.listMarkdownDocuments', { + const documents = await serverCall(page, environmentId, 'files.listMarkdownDocuments', { worktree: `id:${seeded.worktreeId}` }) console.log('[host-created-documents]', documents) @@ -115,7 +79,7 @@ test('open remote Markdown previews discover host-created documents', async (// console.log('[resolved-preview-link]', await link.getAttribute('class')) await page.screenshot({ path: testInfo.outputPath('live-document-created.png') }) host.exec!(`rm '${host.remoteRepoPath}/LATE.md'`) - const removed = await serverCall(page, environment.id, 'files.listMarkdownDocuments', { + const removed = await serverCall(page, environmentId, 'files.listMarkdownDocuments', { worktree: `id:${seeded.worktreeId}` }) console.log('[host-removed-documents]', removed) @@ -123,11 +87,5 @@ test('open remote Markdown previews discover host-created documents', async (// await expect(page.getByText('LATE.md', { exact: true })).not.toBeVisible({ timeout: 30_000 }) await expect(link).toHaveClass(/markdown-doc-link-broken/, { timeout: 10_000 }) await page.screenshot({ path: testInfo.outputPath('live-document-removed.png') }) - } finally { - if (app) { - await session.close(app) - } - await session.dispose() - host.cleanup() - } + }) }) diff --git a/tests/e2e/ssh-orcad-open-in-owner.spec.ts b/tests/e2e/ssh-orcad-open-in-owner.spec.ts index 6433b462c1b..f446020d7f3 100644 --- a/tests/e2e/ssh-orcad-open-in-owner.spec.ts +++ b/tests/e2e/ssh-orcad-open-in-owner.spec.ts @@ -9,24 +9,20 @@ import { } from 'node:fs' import os from 'node:os' import path from 'node:path' -import type { ElectronApplication } 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 { serverCall } from './helpers/orcad-convert-flow' +import { + launchConvertedProfile, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { folderWorkspaceKey } from '../../src/shared/workspace-scope' import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE - -test.skip( - !TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1' || process.platform === 'win32', - 'Needs a POSIX recorder, Docker, and a server template' -) +skipUnlessManagedDockerHost() +test.skip(process.platform === 'win32', 'Needs a POSIX recorder') for (const surface of [ 'source-control', @@ -56,10 +52,8 @@ for (const surface of [ ) chmodSync(recorder, 0o755) const receipt = (): string => (existsSync(receiptPath) ? readFileSync(receiptPath, 'utf8') : '') - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture if (!host.exec) { throw new Error('Missing owned Docker controls') } @@ -67,40 +61,26 @@ for (const surface of [ execute( `mkdir -p ${quotePosixShell(owned)} && git clone --quiet ${quotePosixShell(host.remoteRepoPath)} ${quotePosixShell(repoPath)} && printf '%s\\n' ${quotePosixShell(`# ${remoteMarker}`)} > ${quotePosixShell(filePath)} && mkdir -p ${quotePosixShell(folderPath)} && printf '%s\\n' ${quotePosixShell(`# ${remoteMarker}`)} > ${quotePosixShell(path.join(folderPath, 'README.md'))}` ) - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - expect( - await first.page.evaluate( - async ({ filePath, recorder }) => - window.api.shell.openInExternalEditor({ - path: filePath, - command: recorder, - ownerHostId: 'local' - }), - { filePath, recorder } - ) - ).toEqual({ ok: true }) - await expect.poll(receipt).toContain(localMarker) - console.log('[local-recorder-control]', receipt()) - rmSync(receiptPath) - await session.close(app) - app = null - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { + const { page, seeded, environmentId } = await launchConvertedProfile(fixture, { repoPath, - folderPath + folderPath, + beforeSeed: async (page) => { + expect( + await page.evaluate( + async ({ filePath, recorder }) => + window.api.shell.openInExternalEditor({ + path: filePath, + command: recorder, + ownerHostId: 'local' + }), + { filePath, recorder } + ) + ).toEqual({ ok: true }) + await expect.poll(receipt).toContain(localMarker) + console.log('[local-recorder-control]', receipt()) + rmSync(receiptPath) + } }) - 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 managed environment') - } const readConvertedFolderId = () => page.evaluate( (folderPath) => @@ -119,7 +99,7 @@ for (const surface of [ const workspaceId = convertedFolderId ? folderWorkspaceKey(convertedFolderId) : seeded.worktreeId - const content = await serverCall(page, environment.id, 'files.read', { + const content = await serverCall(page, environmentId, 'files.read', { worktree: `id:${workspaceId}`, relativePath: 'README.md' }) @@ -138,7 +118,7 @@ for (const surface of [ state.setRightSidebarTab('explorer') state.setRightSidebarOpen(true) }, - { id: workspaceId, hostId: toRuntimeExecutionHostId(environment.id), recorder } + { id: workspaceId, hostId: toRuntimeExecutionHostId(environmentId), recorder } ) expect( await page.evaluate(() => window.__store?.getState().settings?.activeRuntimeEnvironmentId) @@ -189,16 +169,6 @@ for (const surface of [ await page.screenshot({ path: testInfo.outputPath(`${surface}-editor-owner-corrected.png`) }) - } finally { - try { - if (app) { - await session.close(app) - } - await session.dispose() - } finally { - host.cleanup() - rmSync(owned, { recursive: true, force: true }) - } - } + }).finally(() => rmSync(owned, { recursive: true, force: true })) }) } diff --git a/tests/e2e/ssh-orcad-terminal-journeys.spec.ts b/tests/e2e/ssh-orcad-terminal-journeys.spec.ts index c9cc0f982e0..99b8f2cc492 100644 --- a/tests/e2e/ssh-orcad-terminal-journeys.spec.ts +++ b/tests/e2e/ssh-orcad-terminal-journeys.spec.ts @@ -7,11 +7,11 @@ * closed while the host is disconnected stays closed. * 3. Faults: a pane outlives a dropped SSH link and a frozen host, and answers input after both. */ -import type { ElectronApplication, Page } from '@stablyai/playwright-test' +import type { Page } from '@stablyai/playwright-test' import { expect, test } from './helpers/orca-app' import { reconnect } from './helpers/orcad-convert-flow' import { readPairedPaneContent } from './helpers/paired-host-terminal' -import { waitForSessionReady, waitForStartupWorktreeRefresh } from './helpers/store' +import { waitForSessionReady } from './helpers/store' import { activateManagedWorkspace, echoMarker, @@ -19,27 +19,18 @@ import { expectTerminalAnswers, launchManagedWorkspace, openTerminalTab, - ORCAD_TEMPLATE, + relaunchManagedApp, selectTerminalTab, + skipUnlessManagedDockerHost, terminalTabIds, typeInTerminal, waitForPane, withManagedHost } from './helpers/orcad-managed-workspace' -test.skip( - !ORCAD_TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', - 'Needs Docker and server template' -) +skipUnlessManagedDockerHost() test.skip(process.platform === 'win32', 'The Docker SSH host uses POSIX tooling') -function relaunchable(app: ElectronApplication | null): ElectronApplication { - if (!app) { - throw new Error('No live launch to close') - } - return app -} - function sshStatus(page: Page, targetId: string): Promise { return page.evaluate( (id) => window.__store?.getState().sshConnectionStates.get(id)?.status ?? null, @@ -142,13 +133,7 @@ test('managed tabs restore exactly once, and a tab closed while disconnected sta expect(expected).toEqual(expect.arrayContaining(opened)) for (let cycle = 0; cycle < 2; cycle += 1) { - await fixture.session.close(relaunchable(fixture.app)) - fixture.app = null - const relaunched = await fixture.session.launch() - fixture.app = relaunched.app - page = relaunched.page - await waitForSessionReady(page) - await waitForStartupWorktreeRefresh(page) + page = await relaunchManagedApp(fixture) await activateManagedWorkspace(page, seeded.worktreeId, environmentId) expect(await settledTabIds(page, seeded.worktreeId), `relaunch ${cycle + 1}`).toEqual( expected @@ -175,14 +160,9 @@ test('managed tabs restore exactly once, and a tab closed while disconnected sta await reconnect(page, seeded.targetId) const remaining = expected.filter((id) => id !== closed) expect(await settledTabIds(page, seeded.worktreeId)).toEqual(remaining) - await fixture.session.close(relaunchable(fixture.app)) - fixture.app = null - const final = await fixture.session.launch() - fixture.app = final.app - await waitForSessionReady(final.page) - await waitForStartupWorktreeRefresh(final.page) - await activateManagedWorkspace(final.page, seeded.worktreeId, environmentId) - expect(await settledTabIds(final.page, seeded.worktreeId)).toEqual(remaining) + const final = await relaunchManagedApp(fixture) + await activateManagedWorkspace(final, seeded.worktreeId, environmentId) + expect(await settledTabIds(final, seeded.worktreeId)).toEqual(remaining) }) }) diff --git a/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts b/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts index 03f83530b59..ea2e36b4f29 100644 --- a/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts +++ b/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts @@ -1,28 +1,27 @@ import { execFileSync } from 'node:child_process' import { mkdirSync, writeFileSync } from 'node:fs' import path from 'node:path' -import type { ElectronApplication } 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 { serverCall } from './helpers/orcad-convert-flow' +import { + launchConvertedProfile, + skipUnlessManagedDockerHost, + withManagedHost +} from './helpers/orcad-managed-workspace' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { openFileExplorer } from './helpers/file-explorer' import { openTerminalWorkspaceRootLink } from './helpers/terminal-workspace-root-link' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { getWorktreeHostIdentity } from '../../src/shared/worktree/host-qualified-identity' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' import { focusActiveTerminalInput, getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' -import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' -const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE -test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs owned Docker host') +skipUnlessManagedDockerHost() test.skip(process.platform === 'win32', 'Owned POSIX host collision fixture') test('a managed terminal path link cannot switch to a desktop workspace at the same path', async ({ @@ -36,10 +35,8 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s mkdirSync(intermediate) execFileSync('git', ['init', '-q', intermediate]) writeFileSync(path.join(intermediate, 'INTERMEDIATE_OWNER.txt'), 'INTERMEDIATE\n') - const host = startOrcadConvertHost('docker', testInfo) - const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE! }) - let app: ElectronApplication | null = null - try { + await withManagedHost(testInfo, async (fixture) => { + const { host } = fixture if (!host.exec) { throw new Error('Missing owned host control') } @@ -47,38 +44,27 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s execute( `mkdir -p ${quotePosixShell(path.dirname(testRepoPath))} && git clone --quiet ${quotePosixShell(host.remoteRepoPath)} ${quotePosixShell(testRepoPath)} && printf 'REMOTE_COMMAND_FILE_OWNER\n' > ${quotePosixShell(filePath)}` ) - const first = await session.launch() - app = first.app - await waitForSessionReady(first.page) - const ids = await first.page.evaluate( - async (paths) => { - const resultIds: string[] = [] - for (const repoPath of paths) { - const result = await window.api.repos.add({ path: repoPath }) - if ('error' in result) { - throw new Error(result.error) - } - resultIds.push(`${result.repo.id}::${repoPath}`) - } - return resultIds - }, - [testRepoPath, intermediate] - ) - await session.close(app) - app = null - const seeded = seedRelayEraProfile(session.userDataDir, host.input, { + let ids: string[] = [] + const { page, seeded, environmentId } = await launchConvertedProfile(fixture, { repoPath: testRepoPath, - folderPath: host.remoteFolderPath + beforeSeed: async (page) => { + ids = await page.evaluate( + async (paths) => { + const resultIds: string[] = [] + for (const repoPath of paths) { + const result = await window.api.repos.add({ path: repoPath }) + if ('error' in result) { + throw new Error(result.error) + } + resultIds.push(`${result.repo.id}::${repoPath}`) + } + return resultIds + }, + [testRepoPath, intermediate] + ) + } }) - 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 || !ids[0] || !ids[1]) { + if (!ids[0] || !ids[1]) { throw new Error('Missing owned workspace') } const markerDirectory = path.join(testRepoPath, 'OWNER_SUBDIRECTORY') @@ -134,7 +120,7 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s ).toBeVisible() const remoteIdentity = getWorktreeHostIdentity({ id: seeded.worktreeId, - hostId: toRuntimeExecutionHostId(environment.id) + hostId: toRuntimeExecutionHostId(environmentId) }) await page.locator(`[data-worktree-host-identity="${remoteIdentity}"]:visible`).click() await openFileExplorer(page) @@ -154,7 +140,7 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s await expect(remoteFile).toBeVisible() console.log( '[actual-remote-source]', - await serverCall(page, environment.id, 'files.read', { + await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: fileName }) @@ -205,13 +191,13 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s }) console.log('[actual-selection-after-managed-link]', route) await page.screenshot({ path: testInfo.outputPath('managed-terminal-root-link-selection.png') }) - expect(route.activeWorkspaceExecutionHostId).toBe(toRuntimeExecutionHostId(environment.id)) + expect(route.activeWorkspaceExecutionHostId).toBe(toRuntimeExecutionHostId(environmentId)) expect(route.activeWorktreeId).toBe(seeded.worktreeId) await expect(foreignSwitch).toHaveCount(0) await popover.getByRole('button', { name: /^Open file/ }).click() console.log( '[managed-directory-click-host-read]', - await serverCall(page, environment.id, 'files.read', { + await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: 'intermediate-command-repo/LINK_DIRECTORY_OWNER.txt' }) @@ -224,13 +210,13 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s } finally { await page.keyboard.up(primaryModifier) } - await serverCall(page, environment.id, 'files.read', { + await serverCall(page, environmentId, 'files.read', { worktree: `id:${seeded.worktreeId}`, relativePath: 'intermediate-command-repo/LINK_DIRECTORY_OWNER.txt' }) await expect .poll(() => page.evaluate(() => window.__store?.getState().activeWorkspaceExecutionHostId)) - .toBe(toRuntimeExecutionHostId(environment.id)) + .toBe(toRuntimeExecutionHostId(environmentId)) await expect .poll(() => page.evaluate(() => window.__store?.getState().activeWorktreeId)) .toBe(seeded.worktreeId) @@ -247,7 +233,7 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s await ownRoot.getByRole('button', { name: /^Switch workspace/ }).click() await expect .poll(() => page.evaluate(() => window.__store?.getState().activeWorkspaceExecutionHostId)) - .toBe(toRuntimeExecutionHostId(environment.id)) + .toBe(toRuntimeExecutionHostId(environmentId)) await expect .poll(() => page.evaluate(() => window.__store?.getState().activeWorktreeId)) .toBe(seeded.worktreeId) @@ -258,14 +244,5 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s host: window.__store?.getState().activeWorkspaceExecutionHostId })) ) - } finally { - try { - if (app) { - await session.close(app) - } - await session.dispose() - } finally { - host.cleanup() - } - } + }) }) diff --git a/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts b/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts index 84c66ae6915..c051ae0a20d 100644 --- a/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts +++ b/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts @@ -13,14 +13,11 @@ import { type DockerSshHostTarget } from './helpers/docker-ssh-host-target' import { ensureTerminalVisible, waitForSessionReady } from './helpers/store' -import { ORCAD_TEMPLATE } from './helpers/orcad-managed-workspace' +import { ORCAD_TEMPLATE, skipUnlessManagedDockerHost } from './helpers/orcad-managed-workspace' import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' /** A recipe VM's SSH root, served by a managed orcad the first connect deploys. */ -test.skip( - !ORCAD_TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', - 'Needs Docker and server template' -) +skipUnlessManagedDockerHost() test.skip(process.platform === 'win32', 'The recipe scripts are POSIX shell') test.use({ seedTestRepo: false, diff --git a/tests/e2e/ssh-orcad-workspace-tools.spec.ts b/tests/e2e/ssh-orcad-workspace-tools.spec.ts index deebcb46ab0..bbb10fce8d5 100644 --- a/tests/e2e/ssh-orcad-workspace-tools.spec.ts +++ b/tests/e2e/ssh-orcad-workspace-tools.spec.ts @@ -13,16 +13,13 @@ import { expectTerminalAnswers, launchManagedWorkspace, openTerminalTab, - ORCAD_TEMPLATE, + skipUnlessManagedDockerHost, typeInTerminal, withManagedHost } from './helpers/orcad-managed-workspace' import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' -test.skip( - !ORCAD_TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', - 'Needs Docker and server template' -) +skipUnlessManagedDockerHost() test.skip(process.platform === 'win32', 'The Docker SSH host uses POSIX tooling') test('Quick Open finds a file in a monorepo-sized managed workspace', async (// oxlint-disable-next-line no-empty-pattern -- Owns its app launch through a restart session.