mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-23 18:55:37 -04:00
9e0d3976e1
Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Jay <air@live.ca> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
256 lines
9.3 KiB
TypeScript
256 lines
9.3 KiB
TypeScript
import { test } from "@playwright/test"
|
|
import {
|
|
defineVisualRegions,
|
|
reportVisualStability,
|
|
startVisualProbe,
|
|
stopVisualProbe,
|
|
visualPlan,
|
|
} from "../../utils/visual-stability"
|
|
import {
|
|
assistantMessage,
|
|
partUpdated,
|
|
setupTimeline,
|
|
shell,
|
|
textPart,
|
|
userMessage,
|
|
waitForVisualSettle,
|
|
} from "./fixture"
|
|
|
|
const profiles = [
|
|
{
|
|
name: "empty running to completed",
|
|
updates: [{ state: "completed" as const, output: "", delay: 350 }],
|
|
},
|
|
{
|
|
name: "50 lines arriving incrementally",
|
|
updates: [
|
|
{ state: "running" as const, output: lines(1), delay: 100 },
|
|
{ state: "running" as const, output: lines(10), delay: 160 },
|
|
{ state: "running" as const, output: lines(25), delay: 90 },
|
|
{ state: "running" as const, output: lines(50), delay: 220 },
|
|
{ state: "completed" as const, output: lines(50), delay: 500 },
|
|
],
|
|
},
|
|
{
|
|
name: "wide ANSI and CRLF output",
|
|
updates: [
|
|
{
|
|
state: "running" as const,
|
|
output: Array.from({ length: 20 }, (_, index) => `\u001b[32mline ${index}\u001b[0m ${"wide-".repeat(30)}`).join(
|
|
"\r\n",
|
|
),
|
|
delay: 240,
|
|
},
|
|
{
|
|
state: "completed" as const,
|
|
output: Array.from({ length: 20 }, (_, index) => `line ${index} ${"wide-".repeat(30)}`).join("\n"),
|
|
delay: 500,
|
|
},
|
|
],
|
|
},
|
|
] as const
|
|
|
|
for (const profile of profiles) {
|
|
test(`keeps rows stable for shell ${profile.name}`, async ({ page }, testInfo) => {
|
|
const shellID = `prt_matrix_${profiles.indexOf(profile)}_01_shell`
|
|
const followingID = `prt_matrix_${profiles.indexOf(profile)}_02_following`
|
|
const timeline = await setupTimeline(page, {
|
|
messages: [
|
|
userMessage(),
|
|
assistantMessage([shell(shellID, "running"), textPart(followingID, "Following shell row")], {
|
|
completed: false,
|
|
}),
|
|
],
|
|
settings: { shellToolPartsExpanded: true },
|
|
cpuRate: 4,
|
|
seedHistory: true,
|
|
})
|
|
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
|
|
await scroller.evaluate((element) => (element.scrollTop = element.scrollHeight))
|
|
await waitForVisualSettle(page, [
|
|
`[data-timeline-part-id="${shellID}"]`,
|
|
`[data-timeline-part-id="${followingID}"]`,
|
|
])
|
|
const regions = defineVisualRegions({
|
|
shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
following: {
|
|
selector: `[data-timeline-part-id="${followingID}"]`,
|
|
closest: '[data-timeline-row="AssistantPart"]',
|
|
},
|
|
})
|
|
await startVisualProbe(page, regions)
|
|
for (const update of profile.updates) {
|
|
await timeline.send(partUpdated(shell(shellID, update.state, update.output)), update.delay)
|
|
}
|
|
const trace = await stopVisualProbe<keyof typeof regions>(page)
|
|
await reportVisualStability(
|
|
testInfo,
|
|
`shell-${profiles.indexOf(profile)}`,
|
|
trace,
|
|
visualPlan(
|
|
regions,
|
|
[
|
|
{ type: "required", regions: ["shell", "following"] },
|
|
{ type: "unique", regions: ["shell", "following"] },
|
|
{ type: "stable", regions: ["shell", "following"] },
|
|
{ type: "opacity", regions: "all" },
|
|
{ type: "continuity", regions: "all" },
|
|
{ type: "motion", regions: "all", maxPositionReversals: 0, maxReversals: 1 },
|
|
{ type: "label-stability", regions: "all" },
|
|
{ type: "preserve-bottom-anchor" },
|
|
{ type: "flow", regions: ["shell", "following"] },
|
|
],
|
|
{ perMarker: true },
|
|
),
|
|
)
|
|
})
|
|
}
|
|
|
|
test("keeps following row stable when a collapsed shell receives 50 lines", async ({ page }, testInfo) => {
|
|
const shellID = "prt_matrix_collapsed_01_shell"
|
|
const followingID = "prt_matrix_collapsed_02_following"
|
|
const timeline = await setupTimeline(page, {
|
|
messages: [
|
|
userMessage(),
|
|
assistantMessage([shell(shellID, "running"), textPart(followingID, "Following collapsed shell")], {
|
|
completed: false,
|
|
}),
|
|
],
|
|
settings: { shellToolPartsExpanded: false },
|
|
cpuRate: 4,
|
|
seedHistory: true,
|
|
})
|
|
await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`, `[data-timeline-part-id="${followingID}"]`])
|
|
const regions = defineVisualRegions({
|
|
shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
})
|
|
await startVisualProbe(page, regions)
|
|
await timeline.send(partUpdated(shell(shellID, "running", lines(50))), 240)
|
|
await timeline.send(partUpdated(shell(shellID, "completed", lines(50))), 500)
|
|
const trace = await stopVisualProbe<keyof typeof regions>(page)
|
|
await reportVisualStability(
|
|
testInfo,
|
|
"collapsed-shell",
|
|
trace,
|
|
visualPlan(
|
|
regions,
|
|
[
|
|
{ type: "required", regions: ["shell", "following"] },
|
|
{ type: "unique", regions: ["shell", "following"] },
|
|
{ type: "stable", regions: ["shell", "following"] },
|
|
{ type: "opacity", regions: "all" },
|
|
{ type: "continuity", regions: "all" },
|
|
{ type: "motion", regions: ["following"], maxPositionReversals: 0 },
|
|
{ type: "label-stability", regions: "all" },
|
|
{ type: "flow", regions: ["shell", "following"] },
|
|
],
|
|
{ perMarker: true },
|
|
),
|
|
)
|
|
})
|
|
|
|
test("keeps rows stable when a running shell becomes an error", async ({ page }, testInfo) => {
|
|
const shellID = "prt_matrix_error_01_shell"
|
|
const followingID = "prt_matrix_error_02_following"
|
|
const timeline = await setupTimeline(page, {
|
|
messages: [
|
|
userMessage(),
|
|
assistantMessage([shell(shellID, "running", lines(10)), textPart(followingID, "Following failed shell")], {
|
|
completed: false,
|
|
}),
|
|
],
|
|
settings: { shellToolPartsExpanded: true },
|
|
cpuRate: 4,
|
|
seedHistory: true,
|
|
})
|
|
await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`, `[data-timeline-part-id="${followingID}"]`])
|
|
const regions = defineVisualRegions({
|
|
shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
following: { selector: `[data-timeline-part-id="${followingID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
})
|
|
await startVisualProbe(page, regions)
|
|
await timeline.send(
|
|
partUpdated({
|
|
...shell(shellID, "error"),
|
|
state: {
|
|
status: "error",
|
|
input: { command: `echo ${shellID}` },
|
|
error: "Command failed after output",
|
|
metadata: {},
|
|
time: { start: 1700000001000, end: 1700000002000 },
|
|
},
|
|
}),
|
|
500,
|
|
)
|
|
const trace = await stopVisualProbe<keyof typeof regions>(page)
|
|
await reportVisualStability(
|
|
testInfo,
|
|
"shell-error",
|
|
trace,
|
|
visualPlan(
|
|
regions,
|
|
[
|
|
{ type: "required", regions: ["shell", "following"] },
|
|
{ type: "unique", regions: ["shell", "following"] },
|
|
{ type: "stable", regions: ["shell", "following"] },
|
|
{ type: "opacity", regions: "all" },
|
|
{ type: "continuity", regions: "all" },
|
|
{ type: "motion", regions: ["following"], maxPositionReversals: 0 },
|
|
{ type: "label-stability", regions: "all" },
|
|
{ type: "preserve-bottom-anchor" },
|
|
{ type: "flow", regions: ["shell", "following"] },
|
|
],
|
|
{ perMarker: true },
|
|
),
|
|
)
|
|
})
|
|
|
|
test("keeps rows stable when later text arrives before shell output", async ({ page }, testInfo) => {
|
|
const shellID = "prt_late_text_01_shell"
|
|
const followingID = "prt_late_text_02_following"
|
|
const timeline = await setupTimeline(page, {
|
|
messages: [userMessage(), assistantMessage([shell(shellID, "running")], { completed: false })],
|
|
settings: { shellToolPartsExpanded: true },
|
|
cpuRate: 4,
|
|
seedHistory: true,
|
|
})
|
|
await waitForVisualSettle(page, [`[data-timeline-part-id="${shellID}"]`])
|
|
const regions = defineVisualRegions({
|
|
shell: { selector: `[data-timeline-part-id="${shellID}"]`, closest: '[data-timeline-row="AssistantPart"]' },
|
|
following: {
|
|
selector: `[data-timeline-part-id="${followingID}"]`,
|
|
closest: '[data-timeline-row="AssistantPart"]',
|
|
},
|
|
})
|
|
await startVisualProbe(page, regions)
|
|
await timeline.send(partUpdated(textPart(followingID, "Later assistant content arrived before shell output.")), 240)
|
|
await timeline.send(partUpdated(shell(shellID, "running", lines(20))), 300)
|
|
await timeline.send(partUpdated(shell(shellID, "completed", lines(20))), 600)
|
|
const trace = await stopVisualProbe<keyof typeof regions>(page)
|
|
await reportVisualStability(
|
|
testInfo,
|
|
"late-text-before-shell-output",
|
|
trace,
|
|
visualPlan(
|
|
regions,
|
|
[
|
|
{ type: "required", regions: ["shell", "following"] },
|
|
{ type: "unique", regions: ["shell", "following"] },
|
|
{ type: "stable", regions: ["shell"] },
|
|
{ type: "opacity", regions: "all" },
|
|
{ type: "continuity", regions: "all" },
|
|
{ type: "motion", regions: "all", maxPositionReversals: 0 },
|
|
{ type: "label-stability", regions: "all" },
|
|
{ type: "preserve-bottom-anchor" },
|
|
{ type: "flow", regions: ["shell", "following"] },
|
|
],
|
|
{ perMarker: true },
|
|
),
|
|
)
|
|
})
|
|
|
|
function lines(count: number) {
|
|
return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n")
|
|
}
|