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
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/chat-launch.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
navigateToOnboarding,
navigateToPlugins,
CHAT_PROMPT_FILE,
} from './testing-framework/index.js';
} from '@spotify-confidence/testing/e2e';

describe('when the user starts chat after onboarding', () => {
it('includes code changes and report file in the prompt', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, navigateToOnboarding } from './testing-framework/index.js';
import { createSession, navigateToOnboarding } from '@spotify-confidence/testing/e2e';

describe('Done screen migration hint', () => {
it('shows /migrate-statsig hint when competitor is detected', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/download-fallback.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
navigateToPlugins,
selectIdeAndOnboard,
readInvocation,
} from './testing-framework/index.js';
} from '@spotify-confidence/testing/e2e';

describe('when CLI plugin install fails', () => {
it('falls back to download and references skills by file path in the prompt', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/empty-project.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession } from './testing-framework/index.js';
import { createSession } from '@spotify-confidence/testing/e2e';

describe('when the project is empty', () => {
it('shows "Select framework" instead of "Start setup" on the Welcome screen', async () => {
Expand Down
10 changes: 7 additions & 3 deletions packages/quickstart/__tests__/e2e/global-setup.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { startMockServer, type MockServer } from './testing-framework/mocks/server.js';
import { createMockBinDir } from './testing-framework/mocks/binaries/index.js';
import {
startMockServer,
type MockServer,
createMockBinDir,
} from '@spotify-confidence/testing/e2e';

let mockServer: MockServer;
let tempBase: string;
Expand All @@ -15,6 +18,7 @@ export async function setup() {

Object.assign(process.env, mockServer.envVars);
process.env.E2E_MOCK_BIN_DIR = mockBinDir;
process.env.E2E_CLI_PATH = resolve(import.meta.dirname, '../../dist/bin/cli.js');
}

export async function teardown() {
Expand Down
6 changes: 5 additions & 1 deletion packages/quickstart/__tests__/e2e/goal-flag.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { createSession, navigatePastWelcome, navigatePastAuth } from './testing-framework/index.js';
import {
createSession,
navigatePastWelcome,
navigatePastAuth,
} from '@spotify-confidence/testing/e2e';

describe('--features flag', () => {
it('skips feature selection screen when --features is provided', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/happy-path.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, simulateAuthCallback } from './testing-framework/index.js';
import { createSession, simulateAuthCallback } from '@spotify-confidence/testing/e2e';

describe('happy-path flow', () => {
it('navigates Welcome → SystemCheck → SelectGoal → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
navigateToPlugins,
selectIdeAndOnboard,
readInvocation,
} from './testing-framework/index.js';
} from '@spotify-confidence/testing/e2e';

const IDE_CASES = [
{
Expand Down
12 changes: 6 additions & 6 deletions packages/quickstart/__tests__/e2e/saved-ide.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, navigatePastWelcome, buildTestJwt } from './testing-framework/index.js';
import { createSession, navigatePastWelcome, buildTestJwt } from '@spotify-confidence/testing/e2e';

describe('when IDE is saved in config from a previous run', () => {
it('auto-selects the saved IDE and skips the selection step', async () => {
Expand All @@ -9,17 +9,17 @@ describe('when IDE is saved in config from a previous run', () => {

await navigatePastWelcome(session);

// Auth — use existing account (token was pre-seeded)
await session.waitForText('Use existing account');
await session.press('Enter');
await session.waitForText('Authenticated');

// SelectGoal
await session.waitForText("Select the features you'd like to set up");
session.checkpoint();
await session.press('Space');
await session.press('Enter');

// Auth — use existing account (token was pre-seeded)
await session.waitForText('Use existing account');
await session.press('Enter');
await session.waitForText('Authenticated');

// InstallPlugins — should auto-select cursor without showing the IDE picker
await session.waitForText('Plugin set up successfully');
session.checkpoint();
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/select-goal.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, navigateToGoalSelection } from './testing-framework/index.js';
import { createSession, navigateToGoalSelection } from '@spotify-confidence/testing/e2e';

describe('SelectGoal screen', () => {
it('shows goal options for a browser framework', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/skip-onboarding.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, navigateToOnboarding } from './testing-framework/index.js';
import { createSession, navigateToOnboarding } from '@spotify-confidence/testing/e2e';

describe('when the user skips onboarding', () => {
it('shows Done screen without report file or code changes', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/skip-tools.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, navigateToConnectTools } from './testing-framework/index.js';
import { createSession, navigateToConnectTools } from '@spotify-confidence/testing/e2e';

describe('when the user skips connecting tools', () => {
it('shows skip message and proceeds to onboarding', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/stale-auth.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
navigatePastGoalSelection,
simulateAuthCallback,
buildTestJwt,
} from './testing-framework/index.js';
} from '@spotify-confidence/testing/e2e';

function buildExpiredJwt(): string {
return buildTestJwt({ exp: Math.floor(Date.now() / 1000) - 3600 });
Expand Down
2 changes: 1 addition & 1 deletion packages/quickstart/__tests__/e2e/stale-mcp-auth.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
navigatePastGoalSelection,
navigatePastAuth,
buildTestJwt,
} from './testing-framework/index.js';
} from '@spotify-confidence/testing/e2e';

function buildExpiredJwt(): string {
return buildTestJwt({ exp: Math.floor(Date.now() / 1000) - 3600 });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dirname } from 'node:path';
import { isWindows } from '@spotify-confidence/testing/env';
import { createSession } from './testing-framework/index.js';
import { createSession } from '@spotify-confidence/testing/e2e';

describe('when system check fails', () => {
it('shows error when git is missing', async () => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession } from './testing-framework/index.js';
import { createSession } from '@spotify-confidence/testing/e2e';

describe('welcome screen navigation', () => {
it('navigates to About screen', async () => {
Expand Down
1 change: 0 additions & 1 deletion packages/quickstart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"eslint": "^10.11.0",
"ink-testing-library": "^4.0.0",
"msw": "^2.15.0",
"node-pty": "1.2.0-beta.14",
"prettier": "^3.9.9",
"tsdown": "^0.22.14",
"tsx": "^4.23.15",
Expand Down
6 changes: 4 additions & 2 deletions packages/testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
"./env": "./src/env/index.ts",
"./terminal": "./src/terminal/index.ts",
"./msw": "./src/msw/setup.ts",
"./msw/localstorage-fake": "./src/msw/localstorage-fake.mjs"
"./msw/localstorage-fake": "./src/msw/localstorage-fake.mjs",
"./e2e": "./src/e2e/index.ts"
},
"dependencies": {
"@spotify-confidence/shared-kernel": "workspace:*",
"msw": "^2.15.0"
"msw": "^2.15.0",
"node-pty": "1.2.0-beta.14"
},
"scripts": {
"typecheck": "tsc --noEmit"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { AUTH_CALLBACK_PORT } from '@spotify-confidence/core';
export const AUTH_CALLBACK_PORT = 8084;

/**
* Baseline environment variables injected into every e2e terminal session.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export { TerminalSession } from './terminal/index.js';
export { createSession } from './session-factory.js';
export { buildTestJwt, CHAT_PROMPT_FILE, ONBOARDING_INVOCATION_FILE } from './mocks/index.js';
export {
startMockServer,
type MockServer,
createMockBinDir,
buildTestJwt,
CHAT_PROMPT_FILE,
ONBOARDING_INVOCATION_FILE,
} from './mocks/index.js';
export { AUTH_CALLBACK_PORT } from './env.js';
export { simulateAuthCallback, readInvocation, type Invocation } from './utils.js';
export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { writeFileSync, chmodSync } from 'node:fs';
import { join } from 'node:path';
import { isWindows } from '@spotify-confidence/testing/env';
import { isWindows } from '../../../env/index.js';

export const writeMockBinary = isWindows ? writeWindowsBinary : writeUnixBinary;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export {
CHAT_PROMPT_FILE,
ONBOARDING_INVOCATION_FILE,
} from './binaries/index.js';
export { buildTestJwt } from '@spotify-confidence/testing/auth';
export { buildTestJwt } from '../../auth/index.js';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createServer, type Server } from 'node:http';
import { buildTestJwt } from '@spotify-confidence/testing/auth';
import { buildTestJwt } from '../../auth/index.js';
import { buildMockEnv } from '../env.js';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { delimiter, join } from 'node:path';
import { TerminalSession } from './terminal/index.js';
import { createProjectDir, type ProjectType } from '@spotify-confidence/testing/scaffold';
import { createProjectDir, type ProjectType } from '../scaffold/index.js';

/**
* Creates an isolated {@link TerminalSession} pre-configured for e2e testing.
Expand Down Expand Up @@ -71,6 +71,7 @@ export function createSession({
}

const session = new TerminalSession({
cliPath: process.env.E2E_CLI_PATH!,
args: ['--debug', '--dir', projectDir, ...extraArgs],
env: sessionEnv,
cwd: projectDir,
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/e2e/terminal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { TerminalSession } from './session.js';
export type { KeyName, Modifiers } from '../../terminal/index.js';
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { spawn as ptySpawn, type IPty } from 'node-pty';
import { resolve } from 'node:path';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { stripAnsi } from './strip-ansi.js';
import { renderScreen, normalizeSnapshot } from './screen-buffer.js';
import { E2E_BASE_ENV } from '../env.js';
import { overlayEnv, isWindows } from '@spotify-confidence/testing/env';
import { resolveKey, type Modifiers } from '@spotify-confidence/testing/terminal';
import { overlayEnv, isWindows } from '../../env/index.js';
import { resolveKey, type Modifiers } from '../../terminal/index.js';

const CLI_PATH = resolve(import.meta.dirname, '../../../../dist/bin/cli.js');
const DEFAULT_COLS = 100;
const DEFAULT_ROWS = 40;
const DEFAULT_TIMEOUT = 15_000;
Expand All @@ -20,6 +18,8 @@ const DEFAULT_TIMEOUT = 15_000;
* @see {@link TerminalSession}
*/
type SessionOptions = {
/** Absolute path to the CLI entry point (e.g. `dist/bin/cli.js`). */
cliPath: string;
/** CLI arguments appended after the binary path. @defaultValue `['--debug']` */
args?: string[];
/** Extra environment variables merged on top of the base e2e env. */
Expand All @@ -39,14 +39,14 @@ function delay(ms: number): Promise<void> {
/**
* Drives a CLI process inside a pseudo-terminal for end-to-end testing.
*
* Wraps `node-pty` to spawn the wizard binary in an isolated environment,
* Wraps `node-pty` to spawn a CLI binary in an isolated environment,
* then exposes high-level methods for sending input, waiting for output,
* and capturing snapshots. Supports the TC39 Explicit Resource Management
* protocol (`using`) for automatic cleanup.
*
* @example
* ```ts
* using session = new TerminalSession({ args: ['--debug'] });
* using session = new TerminalSession({ cliPath: 'dist/bin/cli.js', args: ['--debug'] });
* await session.waitForText('Welcome');
* await session.press('Enter');
* expect(session.snapshot()).toMatchSnapshot('welcome');
Expand All @@ -67,8 +67,15 @@ export class TerminalSession {
readonly cols: number;
readonly rows: number;

constructor(options: SessionOptions = {}) {
const { args = ['--debug'], env = {}, cols = DEFAULT_COLS, rows = DEFAULT_ROWS, cwd } = options;
constructor(options: SessionOptions) {
const {
cliPath,
args = ['--debug'],
env = {},
cols = DEFAULT_COLS,
rows = DEFAULT_ROWS,
cwd,
} = options;

this.cols = cols;
this.rows = rows;
Expand All @@ -77,7 +84,7 @@ export class TerminalSession {
this.cwd = cwd ?? process.cwd();
this.tempDirs.push(isolatedTmpDir);

this.pty = ptySpawn(process.execPath, [CLI_PATH, ...args], {
this.pty = ptySpawn(process.execPath, [cliPath, ...args], {
name: 'xterm-256color',
cols,
rows,
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading