Files
paseo/packages/app/e2e/terminal-alternate-screen.spec.ts
T

136 lines
4.6 KiB
TypeScript

import type { Page } from "@playwright/test";
import { test, expect } from "./fixtures";
import { TerminalE2EHarness, withTerminalInApp } from "./helpers/terminal-dsl";
import {
installTerminalRenderProbe,
readTerminalRenderProbe,
resetTerminalRenderProbe,
startTerminalFrameSampling,
summarizeTerminalRenderProbe,
} from "./helpers/terminal-probes";
import { getTerminalBufferText, waitForTerminalContent } from "./helpers/terminal-perf";
async function waitForAlternateScreenExit(page: Page, afterAlt: string, timeout: number) {
let lastBufferText = "";
let lastProbe = await readTerminalRenderProbe(page);
try {
await expect
.poll(
async () => {
lastBufferText = await getTerminalBufferText(page);
lastProbe = await readTerminalRenderProbe(page);
return (
lastProbe.altEnterWrites > 0 &&
lastProbe.altExitWrites > 0 &&
lastBufferText.includes(afterAlt)
);
},
{
intervals: [50],
message: `wait for alternate-screen exit and ${afterAlt} output`,
timeout,
},
)
.toBe(true);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`Timed out waiting for alternate-screen exit: ${message}\n${JSON.stringify(
{
afterAlt,
probe: summarizeTerminalRenderProbe(lastProbe),
bufferTextTail: lastBufferText.slice(-500),
},
null,
2,
)}`,
);
}
return lastProbe;
}
test.describe("Terminal alternate-screen transitions", () => {
let harness: TerminalE2EHarness;
test.beforeAll(async () => {
harness = await TerminalE2EHarness.create({ tempPrefix: "terminal-alt-" });
});
test.afterAll(async () => {
await harness?.cleanup();
});
test("restores the normal screen after full-screen alternate buffer exit without remounting", async ({
page,
}, testInfo) => {
test.setTimeout(60_000);
await installTerminalRenderProbe(page);
await withTerminalInApp(page, harness, { name: "alternate-screen" }, async () => {
await harness.setupPrompt(page);
const terminal = harness.terminalSurface(page);
const historyReady = `HISTORY_READY_${Date.now()}`;
await terminal.pressSequentially(
`for i in $(seq 1 80); do echo HISTORY_$i; done; echo ${historyReady}\n`,
{ delay: 0 },
);
await waitForTerminalContent(page, (text) => text.includes(historyReady), 10_000);
await resetTerminalRenderProbe(page);
await page.waitForTimeout(500);
const settledProbe = await readTerminalRenderProbe(page);
expect(settledProbe.resetWrites, "terminal should be idle before alternate-screen act").toBe(
0,
);
await resetTerminalRenderProbe(page);
const afterAlt = `AFTER_ALT_${Date.now()}`;
await startTerminalFrameSampling(page);
await terminal.pressSequentially(
`printf '\\033[?1049h\\033[2J\\033[HALT_SCREEN_TOP\\n'; sleep 0.25; printf '\\033[?1049l'; echo ${afterAlt}\n`,
{ delay: 0 },
);
const probe = await waitForAlternateScreenExit(page, afterAlt, 10_000);
const probeSummary = summarizeTerminalRenderProbe(probe);
await testInfo.attach("alternate-screen-probe", {
body: JSON.stringify({ summary: probeSummary, probe }, null, 2),
contentType: "application/json",
});
expect(probe.setCount, "terminal instance should not be replaced after attach").toBe(0);
expect(probe.unsetCount, "terminal instance should not be unset after attach").toBe(0);
expect(
probe.altEnterWrites,
"test command should enter the alternate screen",
).toBeGreaterThan(0);
expect(probe.altExitWrites, "test command should exit the alternate screen").toBeGreaterThan(
0,
);
expect(probe.resetWrites, "alternate-screen exit should not replay a snapshot reset").toBe(0);
const finalBufferText = await getTerminalBufferText(page);
expect(finalBufferText).toContain(historyReady);
expect(finalBufferText).toContain(afterAlt);
const suspiciousFrames = probe.frames.filter(
(frame) =>
frame.text.includes("$") &&
!frame.text.includes(historyReady) &&
!frame.text.includes(afterAlt) &&
frame.nonEmptyRows <= 2 &&
(frame.firstNonEmptyRow ?? Number.POSITIVE_INFINITY) <= 1,
);
expect(
suspiciousFrames,
"normal-screen restore should not flash to a mostly blank prompt-at-top frame",
).toEqual([]);
});
});
});