mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
* fix(e2e): repair seven specs whose assertions drifted from shipped behavior The E2E suite could not collect at all until #13758, so these seven had been failing unobserved. Each is a stale test, not a product defect — verified individually against src/ rather than by making the assertion pass. - worktree-jump-palette-filter: the palette placeholder gained chats and terminals. Hoisted to one SEARCH_PLACEHOLDER const. - tab-create-entry-file-paths: matched the omnibox by its translated aria-label. Switched to aria-controls, which is structural and unlocalized. - browser-local-https-certificate-trust: once a load settles, the toolbar reload button relabels itself "Retry" alongside the failure overlay's own Retry, so the slot-wide locator hit two elements and failed strict mode. The test only ever passed inside the window where the toolbar still read "Stop". Narrowed by visible text; the icon button has none. - repro-7732-gitlab-checks-job-details: the activity-bar label carries a failure suffix ("Checks — Error"), which an exact-name match stops seeing precisely when the checks under test fail. Anchored regex, and bounded the click so the poll retries instead of hanging on a label that flips mid-action. - floating-tab-rename: the panel's open flag is persisted, so after the restart the helper's blind toggle closed the panel it was about to assert on. Made it idempotent. - github-created-issue-start-prefill: starting an issue now routes through the quick-create composer, so the launch command only forms once that is submitted. The spec now drives it. - ssh-config-host-import: P6 waited on "All hosts already in Orca", a string that exists nowhere in src/ and never could have matched. P7 required the tombstoned host to be absent, but listing it behind a "Removed from Orca" badge is deliberate — see the rationale at ssh-config-host-picker.ts:54 and the unit test already pinning it. Both retargeted; P7 still proves the host is excluded from bulk re-adoption, which is what it was for. Verified locally: all seven pass. github-created-issue-start-prefill cannot be verified on a machine whose gh resolves to an Orca terminal-attribution shim, since hydrateShellPath puts that ahead of the fixture's fake gh; CI has no shim. Three further failures are NOT addressed here. Adversarial review found the obvious test-side fix for each would have masked a real product bug, so they are being fixed in the product instead. * fix(e2e): configure the issue spec's git remote before Electron launches The spec added origin inside the test body, after the orcaPage fixture had already launched the app and added the repo. "New GitHub issue" is disabled when no repo is task-eligible, and eligibility is decided by the git remote probe alone: repos.add runs detectRepoIconAndUpstream, and a settled "no remote" writes gitRemoteIdentity = null, which is the ineligible marker. Background enrichment then suppresses re-probing that location for five minutes, so a remote added afterwards can never recover the button. It passed only when the shared seeded repo happened to already carry an origin from an earlier spec in the same worker — github-cli-stall-repro adds one, source-control-create-pr-intent-switch removes one — which is why it passed in the sharded lane and failed in the changed-specs lane. Moved to test.beforeAll, whose worker-scoped testRepoPath runs before the test-scoped Electron fixtures. Reproduced the failure against a fresh origin-less repo, then confirmed the fix on the same, including --repeat-each=2. Note the earlier attribution was wrong: the local failure at this line was never the gh attribution shim. The button's enabled state reads `git remote -v` and never consults gh, so a shim can only bite later, at issue creation.
206 lines
8.0 KiB
TypeScript
206 lines
8.0 KiB
TypeScript
import type { Locator, Page } from '@stablyai/playwright-test'
|
|
|
|
import { expect, test } from './helpers/orca-app'
|
|
import {
|
|
ensureTerminalVisible,
|
|
getActiveWorktreeId,
|
|
waitForActiveWorktree,
|
|
waitForSessionReady
|
|
} from './helpers/store'
|
|
import { startLocalHttpProbeServer, startLocalHttpsServer } from './helpers/local-https-test-server'
|
|
|
|
type CreatedBrowserTab = {
|
|
id: string
|
|
pageId: string
|
|
}
|
|
|
|
async function createBrowserTab(
|
|
page: Page,
|
|
worktreeId: string,
|
|
url: string
|
|
): Promise<CreatedBrowserTab> {
|
|
const tab = await page.evaluate(
|
|
({ targetWorktreeId, targetUrl }) => {
|
|
const state = window.__store?.getState()
|
|
if (!state) {
|
|
return null
|
|
}
|
|
const browserTab = state.createBrowserTab(targetWorktreeId, targetUrl, {
|
|
title: 'Local TLS',
|
|
activate: true
|
|
})
|
|
return { id: browserTab.id, pageId: browserTab.activePageId ?? null }
|
|
},
|
|
{ targetWorktreeId: worktreeId, targetUrl: url }
|
|
)
|
|
if (!tab?.pageId) {
|
|
throw new Error('Failed to create local TLS browser page')
|
|
}
|
|
return { id: tab.id, pageId: tab.pageId }
|
|
}
|
|
|
|
async function switchToBrowserTab(page: Page, worktreeId: string, browserTabId: string) {
|
|
await page.evaluate(
|
|
({ targetWorktreeId, targetBrowserTabId }) => {
|
|
const state = window.__store?.getState()
|
|
if (!state) {
|
|
return
|
|
}
|
|
if (
|
|
!(state.browserTabsByWorktree[targetWorktreeId] ?? []).some(
|
|
(tab) => tab.id === targetBrowserTabId
|
|
)
|
|
) {
|
|
return
|
|
}
|
|
state.setActiveBrowserTab(targetBrowserTabId)
|
|
state.setActiveTabType('browser')
|
|
},
|
|
{ targetWorktreeId: worktreeId, targetBrowserTabId: browserTabId }
|
|
)
|
|
}
|
|
|
|
function browserSlot(page: Page, pageId: string) {
|
|
return page.locator(`[data-browser-overlay-tab-id="${pageId}"]`)
|
|
}
|
|
|
|
// Why: the toolbar reload button is also named "Retry" once a load fails, so match the
|
|
// overlay's button by its visible label — the toolbar one is icon-only.
|
|
function failureOverlayRetryButton(slot: Locator): Locator {
|
|
return slot.getByRole('button', { name: 'Retry' }).filter({ hasText: 'Retry' })
|
|
}
|
|
|
|
async function readBrowserHeading(page: Page, browserTabId: string): Promise<string | null> {
|
|
return page.evaluate(async (targetBrowserTabId) => {
|
|
const slot = [...document.querySelectorAll('[data-browser-overlay-tab-id]')].find(
|
|
(candidate) => candidate.getAttribute('data-browser-overlay-tab-id') === targetBrowserTabId
|
|
)
|
|
const webview = slot?.querySelector('webview') as Electron.WebviewTag | null
|
|
if (!webview) {
|
|
return null
|
|
}
|
|
try {
|
|
return await webview.executeJavaScript('document.querySelector("h1")?.textContent ?? null')
|
|
} catch {
|
|
return null
|
|
}
|
|
}, browserTabId)
|
|
}
|
|
|
|
async function readBrowserState(
|
|
page: Page,
|
|
browserTabId: string,
|
|
stateName: '__localTlsState' | '__siblingTlsProbe'
|
|
): Promise<Record<string, boolean | string> | null> {
|
|
return page.evaluate(
|
|
async ({ targetBrowserTabId, targetStateName }) => {
|
|
const slot = [...document.querySelectorAll('[data-browser-overlay-tab-id]')].find(
|
|
(candidate) => candidate.getAttribute('data-browser-overlay-tab-id') === targetBrowserTabId
|
|
)
|
|
const webview = slot?.querySelector('webview') as Electron.WebviewTag | null
|
|
if (!webview) {
|
|
return null
|
|
}
|
|
try {
|
|
return await webview.executeJavaScript(`window.${targetStateName} ?? null`)
|
|
} catch {
|
|
return null
|
|
}
|
|
},
|
|
{ targetBrowserTabId: browserTabId, targetStateName: stateName }
|
|
)
|
|
}
|
|
|
|
async function reloadBrowserGuest(page: Page, browserTabId: string): Promise<void> {
|
|
await page.evaluate((targetBrowserTabId) => {
|
|
const slot = [...document.querySelectorAll('[data-browser-overlay-tab-id]')].find(
|
|
(candidate) => candidate.getAttribute('data-browser-overlay-tab-id') === targetBrowserTabId
|
|
)
|
|
const webview = slot?.querySelector('webview') as Electron.WebviewTag | null
|
|
if (!webview) {
|
|
throw new Error(`Missing webview for browser tab ${targetBrowserTabId}`)
|
|
}
|
|
webview.reload()
|
|
}, browserTabId)
|
|
}
|
|
|
|
test.describe('local HTTPS certificate trust', () => {
|
|
test.beforeEach(async ({ orcaPage }) => {
|
|
await waitForSessionReady(orcaPage)
|
|
await waitForActiveWorktree(orcaPage)
|
|
await ensureTerminalVisible(orcaPage)
|
|
})
|
|
|
|
test('approves one exact local certificate endpoint without trusting sibling tabs or ports', async ({
|
|
orcaPage
|
|
}) => {
|
|
const firstServer = await startLocalHttpsServer()
|
|
const secondPortServer = await startLocalHttpsServer()
|
|
const siblingProbeServer = await startLocalHttpProbeServer(firstServer)
|
|
try {
|
|
const worktreeId = (await getActiveWorktreeId(orcaPage))!
|
|
const firstTab = await createBrowserTab(orcaPage, worktreeId, firstServer.schemeLessUrl)
|
|
const firstSlot = browserSlot(orcaPage, firstTab.id)
|
|
|
|
await expect(firstSlot.getByRole('button', { name: 'Try HTTPS' })).toBeVisible()
|
|
await firstSlot.getByRole('button', { name: 'Try HTTPS' }).click()
|
|
await expect(
|
|
firstSlot.getByRole('heading', { name: "Connection isn't secure" })
|
|
).toBeVisible()
|
|
// The certificate-failure branch keeps its safe recovery actions and the
|
|
// certificate-specific hint, but never the local-server connectivity hint.
|
|
await expect(firstSlot.getByRole('button', { name: 'Copy Address' })).toBeVisible()
|
|
await expect(failureOverlayRetryButton(firstSlot)).toBeVisible()
|
|
await expect(firstSlot.getByText(/use a trusted local certificate/i)).toBeVisible()
|
|
await expect(firstSlot.getByText(/make sure the server is running/i)).toHaveCount(0)
|
|
await firstSlot.getByRole('button', { name: 'Proceed Anyway (Unsafe)' }).click()
|
|
await expect
|
|
.poll(() => readBrowserHeading(orcaPage, firstTab.id), { timeout: 10_000 })
|
|
.toBe('Local HTTPS request 1')
|
|
await expect
|
|
.poll(() => readBrowserState(orcaPage, firstTab.id, '__localTlsState'))
|
|
.toEqual({ asset: true, webSocket: true })
|
|
expect(firstServer.assetRequestCount()).toBe(1)
|
|
expect(firstServer.webSocketConnectionCount()).toBe(1)
|
|
|
|
const secondTab = await createBrowserTab(orcaPage, worktreeId, firstServer.secureUrl)
|
|
const secondSlot = browserSlot(orcaPage, secondTab.id)
|
|
await expect(
|
|
secondSlot.getByRole('heading', { name: "Connection isn't secure" })
|
|
).toBeVisible()
|
|
// Why: approval is scoped to the first guest WebContents, not its shared
|
|
// profile partition, so this sibling still requires an explicit decision.
|
|
await expect(
|
|
secondSlot.getByRole('button', { name: 'Proceed Anyway (Unsafe)' })
|
|
).toBeVisible()
|
|
|
|
const probeTab = await createBrowserTab(orcaPage, worktreeId, siblingProbeServer.url)
|
|
await expect
|
|
.poll(() => readBrowserState(orcaPage, probeTab.id, '__siblingTlsProbe'))
|
|
.toEqual({ asset: 'blocked', webSocket: 'blocked' })
|
|
expect(firstServer.assetRequestCount()).toBe(1)
|
|
expect(firstServer.webSocketConnectionCount()).toBe(1)
|
|
|
|
await switchToBrowserTab(orcaPage, worktreeId, firstTab.id)
|
|
await reloadBrowserGuest(orcaPage, firstTab.id)
|
|
await expect.poll(firstServer.documentRequestCount, { timeout: 10_000 }).toBe(2)
|
|
await expect
|
|
.poll(() => readBrowserHeading(orcaPage, firstTab.id), { timeout: 10_000 })
|
|
.toBe('Local HTTPS request 2')
|
|
await expect.poll(firstServer.assetRequestCount).toBe(2)
|
|
await expect.poll(firstServer.webSocketConnectionCount).toBe(2)
|
|
|
|
const firstAddressBar = firstSlot.locator('[data-orca-browser-address-bar="true"]')
|
|
await firstAddressBar.fill(secondPortServer.secureUrl)
|
|
await firstAddressBar.press('Enter')
|
|
await expect(
|
|
firstSlot.getByRole('heading', { name: "Connection isn't secure" })
|
|
).toBeVisible()
|
|
await expect(firstSlot.getByRole('button', { name: 'Proceed Anyway (Unsafe)' })).toBeVisible()
|
|
await expect.poll(secondPortServer.documentRequestCount).toBe(0)
|
|
} finally {
|
|
await Promise.all([firstServer.close(), secondPortServer.close(), siblingProbeServer.close()])
|
|
}
|
|
})
|
|
})
|