Files
stablyai--orca/tests/e2e/windows-project-runtime-smoke.spec.ts
2026-07-13 13:05:33 +08:00

185 lines
7.4 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtemp } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import type { Page } from '@stablyai/playwright-test'
import { test, expect } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
const tempRoots: string[] = []
const CLEANUP_RETRY_COUNT = 6
async function removeTempRoot(root: string): Promise<void> {
for (let attempt = 0; attempt < CLEANUP_RETRY_COUNT; attempt += 1) {
try {
rmSync(root, { recursive: true, force: true })
return
} catch (error) {
if (attempt === CLEANUP_RETRY_COUNT - 1) {
throw error
}
// Why: Windows and WSL-backed git probes can release repo handles shortly
// after the Electron fixture closes; retry to keep the smoke idempotent.
await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1)))
}
}
}
async function createGitRepo(prefix: string, repoName: string): Promise<string> {
const rootPath = await mkdtemp(path.join(os.tmpdir(), prefix))
tempRoots.push(rootPath)
const repoPath = path.join(rootPath, repoName)
mkdirSync(repoPath, { recursive: true })
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'e2e@test.local'], {
cwd: repoPath,
stdio: 'pipe'
})
execFileSync('git', ['config', 'user.name', 'E2E Test'], { cwd: repoPath, stdio: 'pipe' })
writeFileSync(path.join(repoPath, 'README.md'), `# ${repoName}\n`)
execFileSync('git', ['add', 'README.md'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['branch', '-M', 'main'], { cwd: repoPath, stdio: 'pipe' })
return repoPath
}
async function openRepoSettings(page: Page, repoId: string): Promise<void> {
await page.evaluate((nextRepoId) => {
const state = window.__store!.getState()
state.setSettingsSearchQuery('')
state.openSettingsTarget({ pane: 'repo', repoId: nextRepoId })
state.openSettingsPage()
}, repoId)
await expect(page.getByPlaceholder('Search settings')).toBeVisible({ timeout: 10_000 })
const maybeLaterButton = page.getByRole('button', { name: 'Maybe Later' })
if (await maybeLaterButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
await maybeLaterButton.click()
}
}
async function chooseProjectRuntime(
page: Page,
repoId: string,
runtimeLabel: 'Windows' | 'WSL'
): Promise<void> {
const section = page.locator(`[data-settings-section="repo-${repoId}"]`)
await section.getByRole('radio', { name: runtimeLabel, exact: true }).click()
const applyButton = section.getByRole('button', { name: 'Apply runtime change' })
if (await applyButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
await applyButton.click()
}
}
test.afterAll(async () => {
for (const root of tempRoots.splice(0)) {
await removeTempRoot(root)
}
})
test.describe('Windows project runtime smoke', () => {
test('keeps a Windows-host project and WSL project available side by side', async ({
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'Windows project runtime smoke requires Windows')
await waitForSessionReady(orcaPage)
const wsl = await orcaPage.evaluate(async () => ({
available: await window.api.wsl.isAvailable(),
distros: await window.api.wsl.listDistros()
}))
test.skip(!wsl.available || wsl.distros.length === 0, 'WSL distro is required for smoke')
const wslDistro = wsl.distros[0]!
const wslRepoPath = await createGitRepo('orca-e2e-project-runtime-', 'wsl-runtime-project')
const smoke = await orcaPage.evaluate(
async ({ hostRepoPath, wslRepoPath, wslDistro }) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
await window.api.repos.add({ path: wslRepoPath })
await store.getState().fetchRepos()
const state = store.getState()
const hostRepo = state.repos.find((repo) => repo.path === hostRepoPath)
const wslRepo = state.repos.find((repo) => repo.path === wslRepoPath)
if (!hostRepo || !wslRepo) {
throw new Error('Expected host and WSL smoke repos to be loaded')
}
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepo.id)
)
const wslProject = state.projects.find((project) =>
project.sourceRepoIds.includes(wslRepo.id)
)
if (!hostProject || !wslProject) {
throw new Error('Expected host and WSL smoke projects to be loaded')
}
await state.updateProject(hostProject.id, {
localWindowsRuntimePreference: { kind: 'windows-host' }
})
await state.updateProject(wslProject.id, {
localWindowsRuntimePreference: { kind: 'wsl', distro: wslDistro }
})
const hostWorktrees = await window.api.worktrees.listDetected({ repoId: hostRepo.id })
const wslWorktrees = await window.api.worktrees.listDetected({ repoId: wslRepo.id })
return {
hostRepoId: hostRepo.id,
wslRepoId: wslRepo.id,
wslDistro,
hostWorktreeCount: hostWorktrees.worktrees.length,
wslWorktreeCount: wslWorktrees.worktrees.length
}
},
{ hostRepoPath: testRepoPath, wslRepoPath, wslDistro }
)
expect(smoke.hostWorktreeCount).toBeGreaterThan(0)
expect(smoke.wslWorktreeCount).toBeGreaterThan(0)
await openRepoSettings(orcaPage, smoke.hostRepoId)
const hostSection = orcaPage.locator(`[data-settings-section="repo-${smoke.hostRepoId}"]`)
await expect(hostSection.getByText('Project Runtime')).toBeVisible()
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'WSL')
await expect(
hostSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
).toBeVisible()
const hostAfterWslUiSwitch = await orcaPage.evaluate((hostRepoId) => {
const state = window.__store!.getState()
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepoId)
)
return hostProject?.localWindowsRuntimePreference
}, smoke.hostRepoId)
expect(hostAfterWslUiSwitch).toEqual({ kind: 'wsl', distro: smoke.wslDistro })
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'Windows')
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
const hostAfterWindowsUiSwitch = await orcaPage.evaluate((hostRepoId) => {
const state = window.__store!.getState()
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepoId)
)
return hostProject?.localWindowsRuntimePreference
}, smoke.hostRepoId)
expect(hostAfterWindowsUiSwitch).toEqual({ kind: 'windows-host' })
await openRepoSettings(orcaPage, smoke.wslRepoId)
const wslSection = orcaPage.locator(`[data-settings-section="repo-${smoke.wslRepoId}"]`)
await expect(wslSection.getByText('Project Runtime')).toBeVisible()
await expect(
wslSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
).toBeVisible()
await orcaPage.evaluate(async (repoId) => {
await window.api.repos.remove({ repoId })
}, smoke.wslRepoId)
})
})