Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ jobs:
run: npm run verify:capture-host
- name: Terminal and Browser runtime smoke
timeout-minutes: 15
env:
# PRs verify the complete host path with a short fixture; main keeps
# the full 720p render for release confidence.
SEQVIO_CAPTURE_SMOKE_PROFILE: ${{ github.event_name == 'pull_request' && 'quick' || 'full' }}
run: npm run smoke:capture-host-runtime

build-test:
Expand Down Expand Up @@ -92,4 +96,7 @@ jobs:

- name: Release pipeline smoke (terminal + browser)
timeout-minutes: 15
env:
# Keep the PR gate fast while main/release retain the full render.
SEQVIO_RELEASE_SMOKE_PROFILE: ${{ github.event_name == 'pull_request' && 'quick' || 'full' }}
run: npm run smoke:release-pipeline
15 changes: 8 additions & 7 deletions packages/terminal-narrator/src/sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import type { TerminalNarratorPlan } from './types';
export function samplePlan(): TerminalNarratorPlan {
// PowerShell emits VT color sequences on modern Windows — better VHS demo than cmd echo.
const isWin = process.platform === 'win32';
const quickSmoke = process.env.SEQVIO_CAPTURE_SMOKE_PROFILE === 'quick';
return {
version: '1.0',
name: 'VHS-style terminal demo',
viewport: { width: 1280, height: 720 },
renderFps: 30,
maxLines: 220,
viewport: quickSmoke ? { width: 640, height: 360 } : { width: 1280, height: 720 },
renderFps: quickSmoke ? 10 : 30,
maxLines: quickSmoke ? 80 : 220,
presentation: 'vhs',
typeDelayMs: 28,
startupWaitMs: 5000,
Expand All @@ -27,7 +28,7 @@ export function samplePlan(): TerminalNarratorPlan {
rows: 28,
cwd: process.cwd(),
},
inputs: isWin
inputs: (isWin
? [
{
id: 'hello',
Expand Down Expand Up @@ -70,9 +71,9 @@ export function samplePlan(): TerminalNarratorPlan {
text: "printf '\\033[35mDone\\033[0m\\n'",
afterMs: 700,
},
],
finalWaitMs: 1400,
timeoutMs: 45_000,
]).slice(0, quickSmoke ? 1 : 3),
finalWaitMs: quickSmoke ? 250 : 1400,
timeoutMs: quickSmoke ? 20_000 : 45_000,
readyPattern: isWin ? 'PS [^\\r\\n>]*>' : undefined,
};
}
Expand Down
21 changes: 21 additions & 0 deletions packages/terminal-narrator/tests/sample.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ test('Windows sample uses the platform PTY default and waits for observed output
}
});

test('samplePlan has an explicit short CI profile without changing the default', () => {
const previous = process.env.SEQVIO_CAPTURE_SMOKE_PROFILE;
process.env.SEQVIO_CAPTURE_SMOKE_PROFILE = 'quick';
try {
const quick = samplePlan();
assert.deepEqual(quick.viewport, { width: 640, height: 360 });
assert.equal(quick.renderFps, 10);
assert.equal(quick.startupWaitMs, 5000);
assert.equal(quick.typeDelayMs, 28);
assert.equal(quick.finalWaitMs, 250);
assert.equal(quick.inputs.length, 1);
} finally {
if (previous === undefined) delete process.env.SEQVIO_CAPTURE_SMOKE_PROFILE;
else process.env.SEQVIO_CAPTURE_SMOKE_PROFILE = previous;
}
const full = samplePlan();
assert.deepEqual(full.viewport, { width: 1280, height: 720 });
assert.equal(full.renderFps, 30);
assert.equal(full.inputs.length, 3);
});

test('sampleClaudePlan uses the shared Claude executable resolver', () => {
const explicit = sampleClaudePlan({ claudeBin: 'custom-claude' });
assert.equal(explicit.shell.command, 'custom-claude');
Expand Down
20 changes: 13 additions & 7 deletions scripts/capture-host-runtime-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import * as http from 'node:http';
import * as os from 'node:os';
import * as path from 'node:path';

function runNode(args) {
function runNode(args, env = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, args, { cwd: process.cwd(), stdio: 'inherit' });
const child = spawn(process.execPath, args, {
cwd: process.cwd(),
stdio: 'inherit',
env: { ...process.env, ...env },
});
child.once('error', reject);
child.once('exit', (code) => code === 0
? resolve()
Expand Down Expand Up @@ -35,6 +39,8 @@ const requestedKind = process.argv.includes('--kind')
if (!['all', 'terminal', 'browser'].includes(requestedKind)) {
throw new Error('--kind must be all, terminal, or browser');
}
const quick = process.env.SEQVIO_CAPTURE_SMOKE_PROFILE === 'quick';
const smokeViewport = quick ? { width: 640, height: 360 } : { width: 1280, height: 720 };
let succeeded = false;
const server = http.createServer((_request, response) => {
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
Expand All @@ -54,19 +60,19 @@ try {
version: '1.0',
name: 'Host runtime smoke',
startUrl: `http://127.0.0.1:${address.port}`,
viewport: { width: 1280, height: 720 },
captureFps: 10,
renderFps: 30,
viewport: smokeViewport,
captureFps: quick ? 5 : 10,
renderFps: quick ? 10 : 30,
actions: [
{ id: 'run', type: 'click', label: 'Run', selector: '#run', afterMs: 250 },
{ id: 'run', type: 'click', label: 'Run', selector: '#run', afterMs: quick ? 50 : 250 },
],
}, null, 2)}\n`, 'utf8');

if (requestedKind === 'all' || requestedKind === 'terminal') {
await runNode([
'packages/terminal-narrator/dist/cli.js', 'record', '--sample',
'--outputDir', tempRoot, '--jobId', 'terminal', '--json',
]);
], quick ? { SEQVIO_CAPTURE_SMOKE_PROFILE: 'quick' } : {});
assertComplete(path.join(tempRoot, 'terminal'));
}
if (requestedKind === 'all' || requestedKind === 'browser') {
Expand Down
30 changes: 18 additions & 12 deletions scripts/release-pipeline-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {

const execFileAsync = promisify(execFile);
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const fps = 30;
const quick = process.env.SEQVIO_RELEASE_SMOKE_PROFILE === 'quick';
const fps = numericArgument('fps', quick ? 10 : 30);

function numericArgument(name, fallback) {
const index = process.argv.indexOf(`--${name}`);
Expand All @@ -34,8 +35,8 @@ function numericArgument(name, fallback) {
return Math.round(value);
}

const width = numericArgument('width', 1280);
const height = numericArgument('height', 720);
const width = numericArgument('width', quick ? 640 : 1280);
const height = numericArgument('height', quick ? 360 : 720);

function requestedKinds() {
const kindIndex = process.argv.indexOf('--kind');
Expand Down Expand Up @@ -132,17 +133,17 @@ async function createCapture(kind, workDir) {
manifest: {
kind: 'terminal',
name: 'terminal-release-smoke',
durationMs: 1200,
durationMs: quick ? 700 : 1200,
viewport: { width, height },
renderFps: fps,
cols: terminalCols,
rows: terminalRows,
renderOptions: { title: 'Release checks' },
events: [
{ timeMs: 0, kind: 'stdout', text: '$ ', transient: false },
{ timeMs: 180, kind: 'stdin', text: 'printf release-ok', transient: true },
{ timeMs: quick ? 100 : 180, kind: 'stdin', text: 'printf release-ok', transient: true },
{
timeMs: 700,
timeMs: quick ? 450 : 700,
kind: 'stdout',
text: 'printf release-ok\r\nrelease-ok\r\n$ ',
transient: false,
Expand All @@ -152,13 +153,13 @@ async function createCapture(kind, workDir) {
{
id: 'command',
label: 'Run the release check',
timeMs: 180,
timeMs: quick ? 100 : 180,
capturedState: { kind: 'terminal', stdout: 'release-ok' },
},
{
id: 'result',
label: 'Confirm the command output',
timeMs: 700,
timeMs: quick ? 450 : 700,
capturedState: { kind: 'terminal', stdout: 'release-ok' },
},
],
Expand Down Expand Up @@ -230,8 +231,8 @@ async function createCapture(kind, workDir) {
}

async function createResolvedAudio(sourceManifest, workDir) {
const durationsMs = [1500, 1550];
const gapMs = 180;
const durationsMs = quick ? [450, 500] : [1500, 1550];
const gapMs = quick ? 50 : 180;
const tracks = [];
const synthesizedNarration = [];
let cursorMs = 0;
Expand Down Expand Up @@ -266,7 +267,7 @@ async function createResolvedAudio(sourceManifest, workDir) {
sourceManifest,
synthesizedNarration,
fps,
{ cueGapMs: gapMs, sceneTailMs: 600 },
{ cueGapMs: gapMs, sceneTailMs: quick ? 150 : 600 },
);
return {
...sourceManifest,
Expand Down Expand Up @@ -326,7 +327,12 @@ async function runPipeline(kind, smokeTempRoot, artifactDir) {
'--width', String(width),
'--height', String(height),
'--fps', String(fps),
'--frames', `0,30,90,${Math.max(0, resolvedManifest.duration - 1)}`,
'--frames', [
0,
Math.round(fps * 0.5),
Math.round(fps * 1.5),
Math.max(0, resolvedManifest.duration - 1),
].filter((frame, index, frames) => frame < resolvedManifest.duration && frames.indexOf(frame) === index).join(','),
'--pixelRatio', '1',
'--ci',
]);
Expand Down
Loading