Add E2E test for terminal copy selection

Verifies that selecting text in the terminal and releasing the mouse
correctly copies the selection to clipboard. The test confirms the
mouseup → copySelection flow works without interference from the
click → focus handler.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 01:35:26 -05:00
parent 0b2d5cdd81
commit 3d5ae8efca
+87 -1
View File
@@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { test, expect, type BrowserContext } from '@playwright/test';
import { E2E_BACKEND_URL } from '../playwright.config';
// Helper to delete a specific session by ID via API
@@ -182,6 +182,92 @@ test.describe('Tmux Terminal Session', () => {
console.log('[Test] Tmux session properly cleaned up');
});
test('copy selection from terminal works', async ({ page, request, context }) => {
// Grant clipboard permissions
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
// Track session ID for cleanup
let createdSessionId: string | null = null;
// 1. Navigate to homepage and create a tmux session
await page.goto('/');
const createButton = page.locator('button[title="New Session"]');
await createButton.click();
const tmuxOption = page.locator('button:has-text("Terminal (tmux)")');
await tmuxOption.click();
// Wait for navigation to session page
await page.waitForURL(/\/session\/.+/);
const sessionUrl = page.url();
createdSessionId = decodeURIComponent(sessionUrl.split('/session/')[1]);
console.log('[Test] Created session:', createdSessionId);
// 2. Wait for terminal to load
const terminalOutput = page.locator('pre.text-green-400');
await expect(terminalOutput).toBeVisible({ timeout: 10000 });
// 3. Run a command with unique output
const uniqueMarker = `COPY-TEST-${Date.now()}`;
const commandInput = page.locator('input[aria-label="Terminal input"]');
await expect(commandInput).toBeEnabled({ timeout: 5000 });
await commandInput.focus();
await page.keyboard.type(`echo "${uniqueMarker}"`);
await page.keyboard.press('Enter');
console.log('[Test] Sent echo command with marker:', uniqueMarker);
// Wait for output
await expect(async () => {
const content = await terminalOutput.textContent();
expect(content).toContain(uniqueMarker);
}).toPass({ timeout: 5000 });
console.log('[Test] Marker appeared in terminal');
// 4. Clear the clipboard first
await page.evaluate(() => navigator.clipboard.writeText(''));
// 5. Select the marker text by triple-clicking on the line containing it
// First, find the position of the marker in the terminal
const terminalBox = await terminalOutput.boundingBox();
expect(terminalBox).not.toBeNull();
// Get the terminal content to find the marker position
const content = await terminalOutput.textContent();
const lines = content?.split('\n') || [];
const markerLineIndex = lines.findIndex(line => line.includes(uniqueMarker) && !line.includes('echo'));
console.log('[Test] Marker found on line index:', markerLineIndex);
if (markerLineIndex >= 0 && terminalBox) {
// Calculate approximate Y position of the marker line
// Assuming ~20px per line (adjust based on actual font size)
const lineHeight = 20;
const yOffset = markerLineIndex * lineHeight + lineHeight / 2 + 12; // +12 for padding
// Triple-click to select the entire line
await page.mouse.click(terminalBox.x + 50, terminalBox.y + yOffset, { clickCount: 3 });
console.log('[Test] Triple-clicked to select line at y offset:', yOffset);
// Give a moment for the selection to register and copy to happen
await page.waitForTimeout(200);
// 6. Verify clipboard contains the marker
const clipboardContent = await page.evaluate(() => navigator.clipboard.readText());
console.log('[Test] Clipboard content:', clipboardContent);
// The clipboard should contain the marker (the entire line might be selected)
expect(clipboardContent).toContain(uniqueMarker);
console.log('[Test] Copy selection test passed - clipboard contains marker');
} else {
throw new Error(`Could not find marker line in terminal output`);
}
// 7. Cleanup
if (createdSessionId) {
await deleteSession(request, createdSessionId);
}
console.log('[Test] Cleanup complete');
});
test('eject tmux session removes from spiceflow but keeps tmux running', async ({ page, request }) => {
// Track session ID for cleanup
let createdSessionId: string | null = null;