Qwen-code terminal-capture
Automates terminal UI screenshot testing for CLI commands. Applies when reviewing PRs that affect CLI output, testing slash commands (/about, /context, /auth, /export), generating visual documentation, or when 'terminal screenshot', 'CLI test', 'visual test', or 'terminal-capture' is mentioned.
git clone https://github.com/QwenLM/qwen-code
T=$(mktemp -d) && git clone --depth=1 https://github.com/QwenLM/qwen-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.qwen/skills/terminal-capture" ~/.claude/skills/qwenlm-qwen-code-terminal-capture && rm -rf "$T"
.qwen/skills/terminal-capture/SKILL.mdTerminal Capture — CLI Terminal Screenshot Automation
Drive terminal interactions and screenshots via TypeScript configuration, used for visual verification during PR reviews.
Prerequisites
Ensure the following dependencies are installed before running:
npm install # Install project dependencies (including node-pty, xterm, playwright, etc.) npx playwright install chromium # Install Playwright browser
Architecture
node-pty (pseudo-terminal) → ANSI byte stream → xterm.js (Playwright headless) → Screenshot
Core files:
| File | Purpose |
|---|---|
| Low-level engine (PTY + xterm.js + Playwright) |
| Scenario executor (parses config, drives interactions, auto-screenshots) |
| CLI entry point (batch run scenarios) |
| Scenario configuration files |
Quick Start
1. Write Scenario Configuration
Create a
.ts file under integration-tests/terminal-capture/scenarios/:
import type { ScenarioConfig } from '../scenario-runner.js'; export default { name: '/about', spawn: ['node', 'dist/cli.js', '--yolo'], terminal: { title: 'qwen-code', cwd: '../../..' }, // Relative to this config file's location flow: [ { type: 'Hi, can you help me understand this codebase?' }, { type: '/about' }, ], } satisfies ScenarioConfig;
2. Run
# Single scenario npx tsx integration-tests/terminal-capture/run.ts integration-tests/terminal-capture/scenarios/about.ts # Batch (entire directory) npx tsx integration-tests/terminal-capture/run.ts integration-tests/terminal-capture/scenarios/
3. Output
Screenshots are saved to
integration-tests/terminal-capture/scenarios/screenshots/{name}/:
| File | Description |
|---|---|
| Step 1 input state |
| Step 1 execution result |
| Step 2 input state |
| Step 2 execution result |
| Final state full-length screenshot |
FlowStep API
Each flow step can contain the following fields:
type: string
— Input Text
type: stringAutomatic behavior: Input text → Screenshot (01) → Press Enter → Wait for output to stabilize → Screenshot (02).
{ type: 'Hello'; } // Plain text { type: '/about'; } // Slash command (auto-completion handled automatically)
Special rule: If the next step is
key, do not auto-press Enter (hand over control to the key sequence).
key: string | string[]
— Send Key Press
key: string | string[]Used for menu selection, Tab completion, and other interactions. Does not auto-press Enter or auto-screenshot.
Supported key names:
ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Enter, Tab, Escape, Backspace, Space, Home, End, PageUp, PageDown, Delete
{ key: 'ArrowDown'; } // Single key { key: ['ArrowDown', 'ArrowDown', 'Enter']; } // Multiple keys
Auto-screenshot is triggered after the key sequence ends (when the next step is not a
key).
streaming
— Capture During Execution
streamingCapture multiple screenshots at intervals during long-running output (e.g., progress bars). Optionally generates an animated GIF.
{ type: 'Run this command: bash progress.sh', streaming: { delayMs: 7000, // Wait before first capture (skip initial waiting phase) intervalMs: 500, // Interval between captures count: 20, // Maximum number of captures gif: true, // Generate animated GIF (default: true, requires ffmpeg) }, }
(optional): Milliseconds to wait after pressing Enter before starting captures. Useful for skipping model thinking/approval time.delayMs- Captures stop early if terminal output is unchanged for 3 consecutive intervals.
- Duplicate frames (no output change) are automatically skipped.
GIF prerequisite: If the scenario uses
streaming with GIF enabled (default), check if ffmpeg is installed before running. If not, ask the user whether they'd like to install it:
# Check which ffmpeg # Install (macOS) brew install ffmpeg
If the user declines, the scenario still runs — GIF generation is skipped with a warning.
capture
/ captureFull
— Explicit Screenshot
capturecaptureFullUse as a standalone step, or override automatic naming:
{ capture: 'initial.png'; } // Screenshot current viewport only { captureFull: 'all-output.png'; } // Screenshot full scrollback buffer
Scenario Examples
Basic: Input + Command
flow: [{ type: 'explain this project' }, { type: '/about' }];
Secondary Menu Selection (/auth)
flow: [ { type: '/auth' }, { key: 'ArrowDown' }, // Select API Key option { key: 'Enter' }, // Confirm { type: 'sk-xxx' }, // Input API key ];
Tab Completion Selection (/export)
flow: [ { type: 'Tell me about yourself' }, { type: '/export' }, // No auto-Enter (next step is key) { key: 'Tab' }, // Pop format selection { key: 'ArrowDown' }, // Select format { key: 'Enter' }, // Confirm → auto-screenshot ];
Array Batch (Multiple Scenarios in One File)
export default [ { name: '/about', spawn: [...], flow: [...] }, { name: '/context', spawn: [...], flow: [...] }, ] satisfies ScenarioConfig[];
Integration with PR Review
This tool is commonly used for visual verification during PR reviews.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
Playwright error | Browser not installed | |
| Blank screenshot | Process starts slowly or build failed | Ensure succeeds, check spawn command |
| PTY-related errors | node-pty native module not compiled | |
| Unstable screenshot output | Terminal output not fully rendered | Check if the scenario needs additional wait time |
Full ScenarioConfig Type
interface FlowStep { type?: string; // Input text key?: string | string[]; // Key press(es) capture?: string; // Viewport screenshot filename captureFull?: string; // Full scrollback screenshot filename streaming?: { delayMs?: number; // Delay before first capture (default: 0) intervalMs: number; // Interval between captures in ms count: number; // Maximum number of captures gif?: boolean; // Generate animated GIF (default: true) }; } interface ScenarioConfig { name: string; // Scenario name (also used as screenshot subdirectory name) spawn: string[]; // Launch command ["node", "dist/cli.js", "--yolo"] flow: FlowStep[]; // Interaction steps terminal?: { cols?: number; // Number of columns, default 100 rows?: number; // Number of rows, default 28 theme?: string; // Theme: dracula|one-dark|github-dark|monokai|night-owl chrome?: boolean; // macOS window decorations, default true title?: string; // Window title, default "Terminal" fontSize?: number; // Font size cwd?: string; // Working directory (relative to config file) }; outputDir?: string; // Screenshot output directory (relative to config file) }