mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 05:02:58 +00:00
490 lines
21 KiB
TypeScript
490 lines
21 KiB
TypeScript
import { runWorkspaceActionFromCommandCenter } from "../support/helpers/command-center-workspace-actions";
|
|
import { spawnSync } from "node:child_process";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { expect, test, type Page } from "../support/fixtures";
|
|
import { TerminalE2EHarness } from "../support/helpers/terminal-dsl";
|
|
import { getTerminalBufferText, buildTerminalWorkspaceUrl } from "../support/helpers/terminal-perf";
|
|
|
|
function query(page: Page) {
|
|
return page.getByRole("textbox", { name: "Find in pane", exact: true }).filter({ visible: true });
|
|
}
|
|
function status(page: Page) {
|
|
return page.getByRole("status", { name: "Find matches" }).filter({ visible: true });
|
|
}
|
|
function input(page: Page) {
|
|
return page
|
|
.getByTestId("terminal-surface")
|
|
.filter({ visible: true })
|
|
.locator(".xterm-helper-textarea");
|
|
}
|
|
async function openFind(page: Page, text?: string) {
|
|
await input(page).press("ControlOrMeta+f");
|
|
await expect(query(page)).toBeFocused();
|
|
if (text !== undefined) await query(page).fill(text);
|
|
}
|
|
async function expectSelectedQuery(page: Page, text: string) {
|
|
await expect(query(page)).toBeFocused();
|
|
await expect(query(page)).toHaveValue(text);
|
|
await expect
|
|
.poll(() =>
|
|
query(page).evaluate((el: HTMLInputElement) => [el.selectionStart, el.selectionEnd]),
|
|
)
|
|
.toEqual([0, text.length]);
|
|
}
|
|
async function expectUncoveredMatch(page: Page) {
|
|
const widget = page.getByLabel("Find", { exact: true });
|
|
await expect
|
|
.poll(async () => {
|
|
const selected = await page.evaluate(() => {
|
|
const term = (
|
|
window as Window & {
|
|
__paseoTerminal?: {
|
|
rows: number;
|
|
cols: number;
|
|
buffer: { active: { viewportY: number } };
|
|
getSelectionPosition():
|
|
| { start: { x: number; y: number }; end: { x: number; y: number } }
|
|
| undefined;
|
|
};
|
|
}
|
|
).__paseoTerminal;
|
|
const selection = term?.getSelectionPosition();
|
|
return term && selection
|
|
? { selection, rows: term.rows, cols: term.cols, firstRow: term.buffer.active.viewportY }
|
|
: null;
|
|
});
|
|
const screen = await page.locator(".xterm-screen").filter({ visible: true }).boundingBox();
|
|
const w = await widget.boundingBox();
|
|
if (!selected || !screen || !w) return false;
|
|
const x = screen.x + (selected.selection.start.x * screen.width) / selected.cols;
|
|
const y =
|
|
screen.y +
|
|
((selected.selection.start.y - selected.firstRow) * screen.height) / selected.rows;
|
|
const height = screen.height / selected.rows;
|
|
return (
|
|
y >= screen.y &&
|
|
y + height <= screen.y + screen.height + 1 &&
|
|
!(x >= w.x && x < w.x + w.width && y < w.y + w.height && y + height > w.y)
|
|
);
|
|
})
|
|
.toBe(true);
|
|
}
|
|
async function viewport(page: Page) {
|
|
return page.evaluate(
|
|
() =>
|
|
(window as Window & { __paseoTerminal?: { buffer: { active: { viewportY: number } } } })
|
|
.__paseoTerminal?.buffer.active.viewportY,
|
|
);
|
|
}
|
|
|
|
async function openControlCharacterTerminal(page: Page) {
|
|
await test.step("Run cat -v in a real bash terminal", async () => {
|
|
const terminal = await harness.createTerminal({
|
|
name: "Control characters",
|
|
command: "bash",
|
|
// Disable the tty's echo so ^F proves cat received the byte. Noncanonical
|
|
// input lets cat print it immediately, without waiting for Enter.
|
|
args: ["--noprofile", "--norc", "-c", "stty -echo -icanon; printf 'CAT_READY\\n'; cat -v"],
|
|
});
|
|
await harness.openTerminal(page, { terminalId: terminal.id });
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("CAT_READY");
|
|
});
|
|
}
|
|
|
|
async function pressTerminalShortcut(page: Page, shortcut: string) {
|
|
await test.step(`Press ${shortcut} in the terminal`, async () => {
|
|
await input(page).press(shortcut);
|
|
});
|
|
}
|
|
|
|
async function expectFindClosed(page: Page) {
|
|
await expect(query(page)).toBeHidden();
|
|
await expect(input(page)).toBeFocused();
|
|
}
|
|
|
|
async function recordTerminalEvidence(name: string, body: string) {
|
|
const evidencePath = test.info().outputPath(`${name}.txt`);
|
|
await writeFile(evidencePath, body);
|
|
await test.info().attach(name, { path: evidencePath, contentType: "text/plain" });
|
|
}
|
|
|
|
async function expectCatControlF(page: Page) {
|
|
await test.step("Cat displays ^F while Find stays closed", async () => {
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("^F");
|
|
await expectFindClosed(page);
|
|
await recordTerminalEvidence("cat-control-f", await getTerminalBufferText(page));
|
|
});
|
|
}
|
|
|
|
async function expectVimPageDown(page: Page, before: { topLine: number; lastLine: number }) {
|
|
await test.step("Vim pages down while Find stays closed", async () => {
|
|
try {
|
|
// Vim's Ctrl+F keeps two lines of overlap, so the new page starts before the old page ends.
|
|
await expect.poll(() => visibleTopLineNumber(page)).toBeGreaterThan(before.topLine);
|
|
await expect.poll(() => visibleTopLineNumber(page)).toBeGreaterThanOrEqual(40);
|
|
await expectFindClosed(page);
|
|
} finally {
|
|
await recordTerminalEvidence("vim-after-control-f", await getTerminalBufferText(page));
|
|
await recordTerminalEvidence(
|
|
"vim-page-down",
|
|
JSON.stringify({ before, after: { topLine: await visibleTopLineNumber(page) } }),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function expectCatRoundTrip(page: Page) {
|
|
await test.step("Wait for cat to echo subsequent input from the pty", async () => {
|
|
await input(page).pressSequentially("PTY_ROUND_TRIP");
|
|
await input(page).press("Enter");
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("PTY_ROUND_TRIP");
|
|
});
|
|
}
|
|
|
|
async function openNumberedFileInVim(page: Page) {
|
|
return test.step("Open 300 numbered lines in vim without user configuration", async () => {
|
|
const terminal = await harness.createTerminal({
|
|
name: "Vim page down",
|
|
command: "bash",
|
|
args: [
|
|
"--noprofile",
|
|
"--norc",
|
|
"-c",
|
|
"seq 1 300 > numbered-lines.txt && vim -u NONE -i NONE numbered-lines.txt; printf 'VIM_EXITED\\n'; cat",
|
|
],
|
|
});
|
|
await harness.openTerminal(page, { terminalId: terminal.id });
|
|
await expectVimFirstPage(page);
|
|
const screen = await readVimScreen(page);
|
|
await recordTerminalEvidence("vim-before-control-f", screen.text);
|
|
return { topLine: screen.numberedLines[0], lastLine: screen.numberedLines.at(-1)! };
|
|
});
|
|
}
|
|
|
|
async function readVimScreen(page: Page) {
|
|
const rows = await page.evaluate(
|
|
() => (window as Window & { __paseoTerminal: { rows: number } }).__paseoTerminal.rows,
|
|
);
|
|
const text = await getTerminalBufferText(page);
|
|
const numberedLines = text
|
|
.split("\n")
|
|
.map((line) => line.trim())
|
|
.filter((line) => /^\d+$/.test(line))
|
|
.map(Number);
|
|
return { rows, text, numberedLines };
|
|
}
|
|
|
|
async function expectVimFirstPage(page: Page) {
|
|
await expect(async () => {
|
|
const screen = await readVimScreen(page);
|
|
// The captured screen has one numbered file line per row, except Vim's
|
|
// bottom command row. Derive the last visible line from the fitted terminal.
|
|
expect(screen.numberedLines).toEqual(Array.from({ length: screen.rows - 1 }, (_, i) => i + 1));
|
|
expect(screen.text).not.toContain("300L");
|
|
}).toPass({ timeout: 10_000 });
|
|
}
|
|
|
|
async function visibleTopLineNumber(page: Page) {
|
|
// Vim uses the alternate screen, so its active buffer starts at the visible top row.
|
|
return Number((await getTerminalBufferText(page)).split("\n")[0].trim());
|
|
}
|
|
|
|
async function quitVim(page: Page) {
|
|
await test.step("Leave vim with Escape and :q!", async () => {
|
|
await input(page).press("Escape");
|
|
await input(page).pressSequentially(":q!");
|
|
await input(page).press("Enter");
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("VIM_EXITED");
|
|
});
|
|
}
|
|
|
|
interface TerminalChord {
|
|
key: string;
|
|
bytes: string;
|
|
}
|
|
|
|
async function openKeyboardCapture(page: Page) {
|
|
await test.step("Open a real terminal process that reports received bytes", async () => {
|
|
const terminal = await harness.createTerminal({
|
|
name: "Keyboard bytes",
|
|
command: process.execPath,
|
|
args: [
|
|
"-e",
|
|
`
|
|
process.stdin.setRawMode(true);
|
|
let received = Buffer.alloc(0);
|
|
process.stdin.on('data', chunk => {
|
|
received = Buffer.concat([received, chunk]);
|
|
process.stdout.write('\\r\\nBYTES:' + received.toString('hex') + ':END\\r\\n');
|
|
});
|
|
console.log('KEYBOARD_READY');
|
|
`,
|
|
],
|
|
});
|
|
await harness.openTerminal(page, { terminalId: terminal.id });
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("KEYBOARD_READY");
|
|
});
|
|
}
|
|
|
|
async function expectTerminalChords(page: Page, chords: TerminalChord[]) {
|
|
let received = "";
|
|
for (const chord of chords) {
|
|
await test.step(`${chord.key} sends ${JSON.stringify(chord.bytes)}`, async () => {
|
|
await pressTerminalShortcut(page, chord.key);
|
|
// A printable sentinel proves delivery even for chords expected to send nothing.
|
|
await input(page).pressSequentially("x");
|
|
received += Buffer.from(chord.bytes + "x").toString("hex");
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain(`BYTES:${received}:END`);
|
|
});
|
|
}
|
|
await recordTerminalEvidence("keyboard-bytes", await getTerminalBufferText(page));
|
|
await page.screenshot({ path: test.info().outputPath("keyboard-chords.png") });
|
|
}
|
|
|
|
let harness: TerminalE2EHarness;
|
|
test.beforeEach(async () => {
|
|
harness = await TerminalE2EHarness.create({ tempPrefix: "terminal-find-" });
|
|
});
|
|
test.afterEach(async () => {
|
|
await harness.cleanup();
|
|
});
|
|
|
|
test.describe("macOS terminal shortcuts", () => {
|
|
test.use({
|
|
userAgent:
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
|
});
|
|
|
|
test("encodes editing chords without confusing Control and Command", async ({ page }) => {
|
|
await openKeyboardCapture(page);
|
|
await expectTerminalChords(page, [
|
|
{ key: "Meta+ArrowLeft", bytes: "\x01" },
|
|
{ key: "Meta+ArrowRight", bytes: "\x05" },
|
|
{ key: "Alt+ArrowLeft", bytes: "\x1bb" },
|
|
{ key: "Alt+ArrowRight", bytes: "\x1bf" },
|
|
{ key: "Meta+Backspace", bytes: "\x15" },
|
|
{ key: "Control+c", bytes: "\x03" },
|
|
{ key: "Control+a", bytes: "\x01" },
|
|
{ key: "Control+e", bytes: "\x05" },
|
|
{ key: "Control+v", bytes: "\x16" },
|
|
{ key: "Control+f", bytes: "\x06" },
|
|
{ key: "ArrowLeft", bytes: "\x1b[D" },
|
|
{ key: "ArrowRight", bytes: "\x1b[C" },
|
|
{ key: "Alt+Backspace", bytes: "\x1b\x7f" },
|
|
{ key: "Control+Alt+ArrowLeft", bytes: "\x1b[1;7D" },
|
|
]);
|
|
await pressTerminalShortcut(page, "Meta+f");
|
|
await expect(query(page)).toBeFocused();
|
|
});
|
|
|
|
test("sends Control+f to cat and opens Find with Meta+f", async ({ page }) => {
|
|
await openControlCharacterTerminal(page);
|
|
await pressTerminalShortcut(page, "Control+f");
|
|
await expectCatControlF(page);
|
|
await pressTerminalShortcut(page, "Meta+f");
|
|
await expect(query(page)).toBeFocused();
|
|
});
|
|
|
|
test("sends Control+f to vim to page down", async ({ page }) => {
|
|
const vim = spawnSync("which", ["vim"], { encoding: "utf8" });
|
|
await recordTerminalEvidence("which-vim", `status: ${vim.status}\n${vim.stdout}${vim.stderr}`);
|
|
test.skip(vim.status !== 0, "vim is not installed on this runner (which vim failed)");
|
|
const before = await openNumberedFileInVim(page);
|
|
await pressTerminalShortcut(page, "Control+f");
|
|
await expectVimPageDown(page, before);
|
|
await quitVim(page);
|
|
});
|
|
});
|
|
|
|
async function useLinuxKeyboardPlatform(page: Page) {
|
|
// A user agent alone leaves navigator.platform as MacIntel on macOS hosts.
|
|
// Emulate both browser identity fields before the app reads its keyboard policy.
|
|
const session = await page.context().newCDPSession(page);
|
|
await session.send("Emulation.setUserAgentOverride", {
|
|
userAgent:
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
|
platform: "Linux x86_64",
|
|
});
|
|
}
|
|
|
|
test("Linux terminal chords keep Control, Alt and Meta distinct", async ({ page }) => {
|
|
await useLinuxKeyboardPlatform(page);
|
|
await openKeyboardCapture(page);
|
|
await expectTerminalChords(page, [
|
|
{ key: "Meta+ArrowLeft", bytes: "" },
|
|
{ key: "Meta+ArrowRight", bytes: "" },
|
|
{ key: "Alt+ArrowLeft", bytes: "\x1b[1;3D" },
|
|
{ key: "Alt+ArrowRight", bytes: "\x1b[1;3C" },
|
|
{ key: "Meta+Backspace", bytes: "\x7f" },
|
|
{ key: "Control+c", bytes: "\x03" },
|
|
{ key: "Control+a", bytes: "\x01" },
|
|
{ key: "Control+e", bytes: "\x05" },
|
|
{ key: "Control+ArrowLeft", bytes: "\x1b[1;5D" },
|
|
{ key: "Control+ArrowRight", bytes: "\x1b[1;5C" },
|
|
{ key: "Control+Backspace", bytes: "\x08" },
|
|
{ key: "Alt+Backspace", bytes: "\x1b\x7f" },
|
|
]);
|
|
await pressTerminalShortcut(page, "Control+f");
|
|
await expect(query(page)).toBeFocused();
|
|
});
|
|
|
|
test("opens Find with Control+f on Linux without sending ^F to cat", async ({ page }) => {
|
|
await useLinuxKeyboardPlatform(page);
|
|
await openControlCharacterTerminal(page);
|
|
await pressTerminalShortcut(page, "Control+f");
|
|
await expect(query(page)).toBeFocused();
|
|
await test.step("Close Find and return focus to the terminal", async () => {
|
|
await query(page).press("Escape");
|
|
await expectFindClosed(page);
|
|
});
|
|
await expectCatRoundTrip(page);
|
|
await test.step("Cat received subsequent input without any ^F", async () => {
|
|
const output = await getTerminalBufferText(page);
|
|
expect(output).not.toContain("^F");
|
|
await recordTerminalEvidence("cat-without-control-f", output);
|
|
});
|
|
});
|
|
|
|
test("searches retained output without sending Find input to the shell", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const received = path.join(harness.tempRepo.path, "received.txt");
|
|
const trigger = path.join(harness.tempRepo.path, "output.trigger");
|
|
const bottomTrigger = path.join(harness.tempRepo.path, "bottom.trigger");
|
|
const script = `stty -echo; printf 'first a.b\\n'; for i in $(seq 1 90); do printf 'line %s\\n' "$i"; done; printf 'last A.B\\nREADY\\n'; (while [ ! -f output.trigger ]; do sleep 0.05; done; printf 'new output\\n'; while [ ! -f bottom.trigger ]; do sleep 0.05; done; for i in $(seq 1 30); do printf 'bottom output %s\\n' "$i"; done) & while IFS= read -r line; do printf '%s\\n' "$line" >> received.txt; done`;
|
|
await writeFile(received, "");
|
|
const terminal = await harness.createTerminal({
|
|
name: "Find fixture",
|
|
command: "bash",
|
|
args: ["--noprofile", "--norc", "-c", script],
|
|
});
|
|
await harness.openTerminal(page, { terminalId: terminal.id });
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("READY");
|
|
await openFind(page, "a.b");
|
|
await expect(status(page)).toHaveText("2 of 2");
|
|
await query(page).press("Shift+Enter");
|
|
await expect(status(page)).toHaveText("1 of 2");
|
|
await expectUncoveredMatch(page);
|
|
const findBox = await page.getByLabel("Find", { exact: true }).boundingBox();
|
|
const surfaceBox = await page.getByTestId("terminal-surface").boundingBox();
|
|
expect(findBox!.y).toBeLessThan(surfaceBox!.y + 70);
|
|
const inspected = await viewport(page);
|
|
await writeFile(trigger, "go");
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("new output");
|
|
await expect.poll(() => viewport(page)).toBe(inspected);
|
|
await page.screenshot({ path: testInfo.outputPath("scrollback-find.png") });
|
|
await query(page).press("Enter");
|
|
await expect(status(page)).toHaveText("2 of 2");
|
|
const inspectedBottom = await viewport(page);
|
|
await writeFile(bottomTrigger, "go");
|
|
await expect.poll(() => getTerminalBufferText(page)).toContain("bottom output 30");
|
|
await expect.poll(() => viewport(page)).toBe(inspectedBottom);
|
|
await expectUncoveredMatch(page);
|
|
await query(page).press("Enter");
|
|
await expect(status(page)).toHaveText("1 of 2");
|
|
await query(page).press("ControlOrMeta+f");
|
|
await expectSelectedQuery(page, "a.b");
|
|
await query(page).fill("A.B");
|
|
await expect(status(page)).toHaveText("1 of 2");
|
|
await query(page).fill("absent[.*]");
|
|
await expect(status(page)).toHaveText("No matches");
|
|
await query(page).fill("a.b");
|
|
await query(page).press("Escape");
|
|
await expect(query(page)).toBeHidden();
|
|
await expect(input(page)).toBeFocused();
|
|
await expect(page.locator(".xterm-find-result-decoration")).toHaveCount(0);
|
|
await input(page).pressSequentially("only-shell-input");
|
|
await input(page).press("Enter");
|
|
await expect.poll(() => readFile(received, "utf8")).toBe("only-shell-input\n");
|
|
await input(page).pressSequentially("discard");
|
|
await input(page).press("Control+u");
|
|
await input(page).pressSequentially("ordinary-input");
|
|
await input(page).press("Enter");
|
|
await expect.poll(() => readFile(received, "utf8")).toBe("only-shell-input\nordinary-input\n");
|
|
await openFind(page);
|
|
await expectSelectedQuery(page, "a.b");
|
|
await expect(status(page)).toHaveText(/of 2/);
|
|
await expectUncoveredMatch(page);
|
|
await expect(page.getByRole("button", { name: "Toggle replace" })).toHaveCount(0);
|
|
await expect(page.getByRole("button", { name: "Find", exact: true })).toHaveCount(0);
|
|
});
|
|
|
|
test("keeps terminal Find owned by retained tabs and the focused split pane", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const first = await harness.createTerminal({
|
|
name: "First search",
|
|
command: "bash",
|
|
args: ["-c", "printf 'first needle\\n'; cat"],
|
|
});
|
|
const second = await harness.createTerminal({
|
|
name: "Second search",
|
|
command: "bash",
|
|
args: ["-c", "printf 'second needle\\nsecond needle\\n'; cat"],
|
|
});
|
|
await harness.openTerminal(page, { terminalId: first.id });
|
|
await openFind(page, "needle");
|
|
await expect(status(page)).toHaveText("1 of 1");
|
|
await page.getByTestId(`workspace-tab-terminal_${second.id}`).first().click();
|
|
await openFind(page, "needle");
|
|
await expect(status(page).filter({ visible: true })).toHaveText("2 of 2");
|
|
await query(page).filter({ visible: true }).press("Escape");
|
|
await page.getByTestId(`workspace-tab-terminal_${first.id}`).first().click();
|
|
await input(page).press("ControlOrMeta+f");
|
|
await expectSelectedQuery(page, "needle");
|
|
await expect(status(page).filter({ visible: true })).toHaveText("1 of 1");
|
|
await query(page).filter({ visible: true }).press("Escape");
|
|
await runWorkspaceActionFromCommandCenter(page, "Split pane right");
|
|
await page.getByTestId(`workspace-tab-terminal_${second.id}`).first().click();
|
|
await runWorkspaceActionFromCommandCenter(page, "Move tab right");
|
|
const left = page
|
|
.getByTestId("split-group-child")
|
|
.filter({ has: page.getByTestId(`workspace-tab-terminal_${first.id}`) });
|
|
const right = page
|
|
.getByTestId("split-group-child")
|
|
.filter({ has: page.getByTestId(`workspace-tab-terminal_${second.id}`) });
|
|
await expect(left.locator(".xterm-helper-textarea")).toHaveCount(1);
|
|
await expect(right.locator(".xterm-helper-textarea")).toHaveCount(1);
|
|
await right.getByTestId("terminal-surface").click();
|
|
await right.locator(".xterm-helper-textarea").press("ControlOrMeta+f");
|
|
await expect(right.getByRole("textbox", { name: "Find in pane", exact: true })).toBeFocused();
|
|
await right.getByRole("textbox", { name: "Find in pane", exact: true }).fill("needle");
|
|
await expect(right.getByRole("status", { name: "Find matches" })).toHaveText(/of 2/);
|
|
await expect(left.getByRole("textbox", { name: "Find in pane", exact: true })).toHaveCount(0);
|
|
await right.getByRole("textbox", { name: "Find in pane", exact: true }).press("Escape");
|
|
await left.getByTestId("terminal-surface").click();
|
|
await left.locator(".xterm-helper-textarea").press("ControlOrMeta+f");
|
|
await expect(left.getByRole("textbox", { name: "Find in pane", exact: true })).toBeFocused();
|
|
await left.getByRole("textbox", { name: "Find in pane", exact: true }).fill("needle");
|
|
await expect(left.getByRole("status", { name: "Find matches" })).toHaveText("1 of 1");
|
|
await page.screenshot({ path: testInfo.outputPath("split-find.png") });
|
|
});
|
|
|
|
test("keeps the shared Find controls usable in a compact dark browser", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await page.setViewportSize({ width: 390, height: 720 });
|
|
await page.emulateMedia({ colorScheme: "dark" });
|
|
const terminal = await harness.createTerminal({
|
|
name: "Compact search",
|
|
command: "bash",
|
|
args: ["-c", "printf 'needle\\nneedle\\n'; cat"],
|
|
});
|
|
await page.goto(buildTerminalWorkspaceUrl(harness.workspaceId, terminal.id));
|
|
await expect(page.getByTestId("terminal-surface")).toBeVisible();
|
|
await page.getByTestId("terminal-surface").click();
|
|
await openFind(page, "needle");
|
|
await expect(status(page)).toHaveText("2 of 2");
|
|
await page.getByRole("button", { name: "Previous match" }).click();
|
|
await expect(status(page)).toHaveText("1 of 2");
|
|
await page.getByRole("button", { name: "Next match" }).click();
|
|
await expect(status(page)).toHaveText("2 of 2");
|
|
const box = await page.getByLabel("Find", { exact: true }).boundingBox();
|
|
expect(box!.x).toBeGreaterThanOrEqual(0);
|
|
expect(box!.x + box!.width).toBeLessThanOrEqual(390);
|
|
await page.screenshot({ path: testInfo.outputPath("compact-find.png") });
|
|
await page.getByRole("button", { name: "Close Find" }).click();
|
|
await expect(input(page)).toBeFocused();
|
|
});
|