mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
* Fix flaky e2e tests with improved locators and synchronization Add explicit waits, use more robust element selectors, and simplify test setup to reduce race conditions. Replace file-based fixtures with programmatic browser creation, use parent-scoped locators for menu interactions, and poll for stable state before assertions. * Add E2E failure triage report for run 33564563164 - Reconciles 14 failed tests against job logs and trace artifacts - Categorizes failures: 8 product bugs, 2 flaky tests, 4 test updates - Documents test-maintenance fixes and diagnostic findings - Files 8 Linear issues with owners and fresh recurrence evidence - Provides next actions for product owners and repository maintenance * rm artifact notes * Refactor browser creation E2E test to use UI interactions - Click through menu instead of manipulating internal store state - Use Playwright's locator and toBeVisible() assertion patterns * Record E2E browser creation pageId before barrier check Move createdPageId assignment before the barrier arm/fire checks. This ensures the pageId is recorded unconditionally when tracking is enabled, allowing tests to distinguish between creations rejected before the host attempt vs those that failed after creation. * Remove browser page reclamation assertion from restart test Simplifies test by removing page ID tracking and poll checking if pages persist after paired runtime restart.
179 lines
6.2 KiB
TypeScript
179 lines
6.2 KiB
TypeScript
import { randomUUID } from 'node:crypto'
|
|
import type { Page, TestInfo } from '@stablyai/playwright-test'
|
|
import { expect, test } from './helpers/orca-app'
|
|
import { ensureTerminalVisible, waitForSessionReady } from './helpers/store'
|
|
import {
|
|
getTerminalContent,
|
|
sendToTerminal,
|
|
waitForActivePanePtyId,
|
|
waitForActiveTerminalManager,
|
|
waitForTerminalOutput
|
|
} from './helpers/terminal'
|
|
import { waitForPtyShellEcho } from './terminal-pty-readiness'
|
|
|
|
type LinkProbe = {
|
|
tabId: string
|
|
col: number
|
|
row: number
|
|
}
|
|
|
|
type TooltipState = {
|
|
display: string
|
|
text: string
|
|
cursor: string
|
|
paneBottom: number
|
|
terminalBottom: number
|
|
tooltipTop: number
|
|
tooltipBottom: number
|
|
tooltipHeight: number
|
|
}
|
|
|
|
async function locateUrl(page: Page, url: string): Promise<LinkProbe | null> {
|
|
return page.evaluate((url) => {
|
|
const state = window.__store?.getState()
|
|
const worktreeId = state?.activeWorktreeId
|
|
const tabId =
|
|
state?.activeTabType === 'terminal'
|
|
? (state.activeTabId ?? null)
|
|
: worktreeId
|
|
? (state.activeTabIdByWorktree?.[worktreeId] ?? null)
|
|
: null
|
|
const manager = tabId ? window.__paneManagers?.get(tabId) : null
|
|
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
|
|
if (!tabId || !pane) {
|
|
return null
|
|
}
|
|
|
|
const buffer = pane.terminal.buffer.active
|
|
for (let row = 0; row < pane.terminal.rows; row += 1) {
|
|
const line = buffer.getLine(buffer.viewportY + row)
|
|
const col = line?.translateToString(true).indexOf(url) ?? -1
|
|
if (col >= 0) {
|
|
return {
|
|
tabId,
|
|
col: col + Math.floor(url.length / 2),
|
|
row
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}, url)
|
|
}
|
|
|
|
async function moveToLink(page: Page, probe: LinkProbe): Promise<void> {
|
|
const point = await page.evaluate(({ col, row, tabId }) => {
|
|
const manager = window.__paneManagers?.get(tabId)
|
|
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
|
|
const screen = pane?.terminal.element?.querySelector<HTMLElement>('.xterm-screen')
|
|
if (!pane || !screen) {
|
|
throw new Error('xterm-screen element unavailable')
|
|
}
|
|
const cell = pane.terminal.dimensions?.css.cell
|
|
if (!cell?.width || !cell.height) {
|
|
throw new Error('terminal cell dimensions unavailable')
|
|
}
|
|
const rect = screen.getBoundingClientRect()
|
|
return {
|
|
x: rect.left + (col + 0.5) * cell.width,
|
|
y: rect.top + (row + 0.5) * cell.height
|
|
}
|
|
}, probe)
|
|
await page.mouse.move(point.x, point.y)
|
|
}
|
|
|
|
async function readTooltipState(page: Page, tabId: string): Promise<TooltipState> {
|
|
return page.evaluate((tabId) => {
|
|
const manager = window.__paneManagers?.get(tabId)
|
|
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
|
|
const screen = pane?.terminal.element?.querySelector<HTMLElement>('.xterm-screen')
|
|
if (!pane || !screen) {
|
|
throw new Error('terminal pane unavailable')
|
|
}
|
|
|
|
const paneRect = pane.container.getBoundingClientRect()
|
|
const terminalRect = pane.terminal.element?.parentElement?.getBoundingClientRect()
|
|
const tooltipRect = pane.linkTooltip.getBoundingClientRect()
|
|
|
|
return {
|
|
display: pane.linkTooltip.style.display,
|
|
text: pane.linkTooltip.textContent ?? '',
|
|
cursor: getComputedStyle(screen).cursor,
|
|
paneBottom: paneRect.bottom,
|
|
terminalBottom: terminalRect?.bottom ?? 0,
|
|
tooltipTop: tooltipRect.top,
|
|
tooltipBottom: tooltipRect.bottom,
|
|
tooltipHeight: tooltipRect.height
|
|
}
|
|
}, tabId)
|
|
}
|
|
|
|
async function captureProof(page: Page, testInfo: TestInfo, name: string): Promise<void> {
|
|
await page.screenshot({ path: testInfo.outputPath(name), animations: 'disabled' })
|
|
}
|
|
|
|
test.describe('Issue #12656 terminal link tooltip', () => {
|
|
test('clears hover state without permanently shrinking the terminal', async ({
|
|
orcaPage
|
|
}, testInfo) => {
|
|
await waitForSessionReady(orcaPage)
|
|
await ensureTerminalVisible(orcaPage)
|
|
await waitForActiveTerminalManager(orcaPage)
|
|
|
|
const ptyId = await waitForActivePanePtyId(orcaPage)
|
|
await waitForPtyShellEcho(orcaPage, ptyId, 15_000)
|
|
|
|
const url = `https://example.com/orca-issue-12656-${randomUUID().slice(0, 8)}`
|
|
await sendToTerminal(
|
|
orcaPage,
|
|
ptyId,
|
|
`printf 'issue-12656-output-%02d\\n' $(seq 1 64); printf '${url}\\n'\r`
|
|
)
|
|
await waitForTerminalOutput(orcaPage, url)
|
|
|
|
let probe: LinkProbe | null = null
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
probe = await locateUrl(orcaPage, url)
|
|
return probe
|
|
},
|
|
{ timeout: 5_000, message: 'URL did not become visible in the terminal viewport' }
|
|
)
|
|
.not.toBeNull()
|
|
if (!probe) {
|
|
throw new Error('URL probe disappeared before hover')
|
|
}
|
|
const idle = await readTooltipState(orcaPage, probe.tabId)
|
|
expect(Math.abs(idle.paneBottom - idle.terminalBottom)).toBeLessThanOrEqual(1)
|
|
await expect
|
|
.poll(async () => {
|
|
const currentProbe = await locateUrl(orcaPage, url)
|
|
if (!currentProbe) {
|
|
return { display: 'none', text: '' }
|
|
}
|
|
probe = currentProbe
|
|
await moveToLink(orcaPage, currentProbe)
|
|
return readTooltipState(orcaPage, currentProbe.tabId)
|
|
})
|
|
.toMatchObject({ display: '', text: expect.stringContaining(url) })
|
|
|
|
const hovered = await readTooltipState(orcaPage, probe.tabId)
|
|
expect(hovered.text).toContain(url)
|
|
expect(hovered.tooltipHeight).toBeGreaterThan(0)
|
|
expect(Math.abs(hovered.paneBottom - hovered.terminalBottom)).toBeLessThanOrEqual(1)
|
|
expect(Math.abs(hovered.paneBottom - hovered.tooltipBottom)).toBeLessThanOrEqual(1)
|
|
expect(hovered.tooltipTop).toBeLessThan(hovered.terminalBottom)
|
|
await captureProof(orcaPage, testInfo, 'issue-12656-fixed-hover.png')
|
|
|
|
await orcaPage.evaluate(() => window.dispatchEvent(new Event('blur')))
|
|
await expect
|
|
.poll(() => readTooltipState(orcaPage, probe.tabId))
|
|
.toMatchObject({ display: 'none', cursor: 'text' })
|
|
const cleared = await readTooltipState(orcaPage, probe.tabId)
|
|
expect(Math.abs(cleared.paneBottom - cleared.terminalBottom)).toBeLessThanOrEqual(1)
|
|
await captureProof(orcaPage, testInfo, 'issue-12656-fixed-after-blur.png')
|
|
|
|
await expect.poll(() => getTerminalContent(orcaPage)).toContain(url)
|
|
})
|
|
})
|