or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-testing.mdhooks.mdindex.mdmocking.mdrunner.md

runner.mddocs/

0

# Test Runner

1

2

Programmatic test runner for executing test files with advanced configuration options including concurrency, timeouts, and custom reporters. The runner provides fine-grained control over test execution and supports both individual file execution and batch processing.

3

4

## Capabilities

5

6

### Run Function

7

8

The main programmatic interface for running tests with configurable options.

9

10

```javascript { .api }

11

/**

12

* Programmatic test runner with configurable options

13

* @param options - Runner configuration options

14

* @returns TestsStream for monitoring test progress and results

15

*/

16

function run(options?: RunOptions): TestsStream;

17

18

interface RunOptions {

19

/** Number of concurrent test files to run. Default: 1 */

20

concurrency?: number;

21

/** Global timeout for all tests in milliseconds. Default: Infinity */

22

timeout?: number;

23

/** AbortSignal for cancelling test execution */

24

signal?: AbortSignal;

25

/** Array of test file paths to execute. Default: auto-discover */

26

files?: string[];

27

/** Inspector port for debugging test execution */

28

inspectPort?: number;

29

}

30

```

31

32

**Usage Examples:**

33

34

```javascript

35

import { run } from "test";

36

37

// Basic usage - auto-discover and run tests

38

const stream = run();

39

stream.on('test:pass', (test) => {

40

console.log(`✓ ${test.name}`);

41

});

42

stream.on('test:fail', (test) => {

43

console.log(`✗ ${test.name}: ${test.error.message}`);

44

});

45

46

// Run specific files

47

run({

48

files: ['./tests/unit/*.js', './tests/integration/*.js']

49

});

50

51

// Concurrent execution

52

run({

53

concurrency: 4,

54

timeout: 30000

55

});

56

57

// With abort signal

58

const controller = new AbortController();

59

run({

60

signal: controller.signal,

61

files: ['./long-running-test.js']

62

});

63

64

// Cancel after 10 seconds

65

setTimeout(() => controller.abort(), 10000);

66

```

67

68

### TestsStream

69

70

The runner returns a TestsStream that provides real-time test execution feedback and results.

71

72

```javascript { .api }

73

interface TestsStream extends EventEmitter {

74

/** Stream of test events and results */

75

on(event: 'test:start', listener: (test: TestInfo) => void): this;

76

on(event: 'test:pass', listener: (test: TestInfo) => void): this;

77

on(event: 'test:fail', listener: (test: TestInfo) => void): this;

78

on(event: 'test:skip', listener: (test: TestInfo) => void): this;

79

on(event: 'test:todo', listener: (test: TestInfo) => void): this;

80

on(event: 'test:diagnostic', listener: (test: TestInfo, message: string) => void): this;

81

on(event: 'end', listener: (results: TestResults) => void): this;

82

}

83

84

interface TestInfo {

85

name: string;

86

file: string;

87

line?: number;

88

column?: number;

89

duration?: number;

90

error?: Error;

91

}

92

93

interface TestResults {

94

total: number;

95

pass: number;

96

fail: number;

97

skip: number;

98

todo: number;

99

duration: number;

100

files: string[];

101

}

102

```

103

104

**Usage Examples:**

105

106

```javascript

107

import { run } from "test";

108

109

const stream = run({

110

files: ['./tests/**/*.test.js'],

111

concurrency: 2

112

});

113

114

// Handle individual test events

115

stream.on('test:start', (test) => {

116

console.log(`Starting ${test.name}...`);

117

});

118

119

stream.on('test:pass', (test) => {

120

console.log(`✓ ${test.name} (${test.duration}ms)`);

121

});

122

123

stream.on('test:fail', (test) => {

124

console.error(`✗ ${test.name}`);

125

console.error(` ${test.error.message}`);

126

if (test.error.stack) {

127

console.error(` at ${test.file}:${test.line}:${test.column}`);

128

}

129

});

130

131

stream.on('test:skip', (test) => {

132

console.log(`- ${test.name} (skipped)`);

133

});

134

135

stream.on('test:todo', (test) => {

136

console.log(`? ${test.name} (todo)`);

137

});

138

139

stream.on('test:diagnostic', (test, message) => {

140

console.log(`# ${message}`);

141

});

142

143

// Handle completion

144

stream.on('end', (results) => {

145

console.log(`\nResults:`);

146

console.log(` Total: ${results.total}`);

147

console.log(` Pass: ${results.pass}`);

148

console.log(` Fail: ${results.fail}`);

149

console.log(` Skip: ${results.skip}`);

150

console.log(` Todo: ${results.todo}`);

151

console.log(` Duration: ${results.duration}ms`);

152

153

if (results.fail > 0) {

154

process.exit(1);

155

}

156

});

157

```

158

159

## File Discovery

160

161

When no files are specified, the runner automatically discovers test files using these patterns:

162

163

1. Files in `test/` directories with `.js`, `.mjs`, or `.cjs` extensions

164

2. Files ending with `.test.js`, `.test.mjs`, or `.test.cjs`

165

3. Files ending with `.spec.js`, `.spec.mjs`, or `.spec.cjs`

166

4. Excludes `node_modules` directories

167

168

**Examples:**

169

170

```javascript

171

// These files will be auto-discovered:

172

// test/user.js

173

// test/api/auth.test.js

174

// src/utils.spec.mjs

175

// integration.test.cjs

176

177

// These will be ignored:

178

// node_modules/package/test.js

179

// src/helper.js (no test pattern)

180

```

181

182

Manual file specification overrides auto-discovery:

183

184

```javascript

185

run({

186

files: [

187

'./custom-tests/*.js',

188

'./e2e/**/*.test.js'

189

]

190

});

191

```

192

193

## Concurrency Control

194

195

The runner supports concurrent execution of test files to improve performance:

196

197

```javascript

198

// Sequential execution (default)

199

run({ concurrency: 1 });

200

201

// Parallel execution - 4 files at once

202

run({ concurrency: 4 });

203

204

// Unlimited concurrency

205

run({ concurrency: Infinity });

206

207

// Boolean concurrency (uses CPU count)

208

run({ concurrency: true });

209

```

210

211

**Important Notes:**

212

- Concurrency applies to test files, not individual tests within files

213

- Individual test concurrency is controlled by test options

214

- Higher concurrency may reveal race conditions in tests

215

216

## Timeout Configuration

217

218

Set global timeouts for test execution:

219

220

```javascript

221

// 30-second timeout for all tests

222

run({ timeout: 30000 });

223

224

// No timeout (default)

225

run({ timeout: Infinity });

226

```

227

228

Timeout behavior:

229

- Applies to individual test files, not the entire run

230

- Files that timeout are marked as failed

231

- Other files continue to execute

232

233

## Debugging Support

234

235

Enable debugging for test execution:

236

237

```javascript

238

// Run with Node.js inspector

239

run({

240

inspectPort: 9229,

241

files: ['./debug-this-test.js']

242

});

243

244

// Then connect with Chrome DevTools or VS Code

245

```

246

247

## Error Handling and Cancellation

248

249

```javascript

250

import { run } from "test";

251

252

const controller = new AbortController();

253

const stream = run({

254

files: ['./tests/**/*.js'],

255

signal: controller.signal

256

});

257

258

// Handle stream errors

259

stream.on('error', (error) => {

260

console.error('Runner error:', error);

261

});

262

263

// Cancel execution

264

setTimeout(() => {

265

console.log('Cancelling tests...');

266

controller.abort();

267

}, 5000);

268

269

// Handle cancellation

270

controller.signal.addEventListener('abort', () => {

271

console.log('Test execution cancelled');

272

});

273

```

274

275

## Integration Examples

276

277

### Custom Test Reporter

278

279

```javascript

280

import { run } from "test";

281

import fs from "fs";

282

283

const results = [];

284

const stream = run({ files: ['./tests/**/*.js'] });

285

286

stream.on('test:pass', (test) => {

287

results.push({ status: 'pass', name: test.name, duration: test.duration });

288

});

289

290

stream.on('test:fail', (test) => {

291

results.push({

292

status: 'fail',

293

name: test.name,

294

error: test.error.message,

295

duration: test.duration

296

});

297

});

298

299

stream.on('end', () => {

300

// Write custom report

301

fs.writeFileSync('test-results.json', JSON.stringify(results, null, 2));

302

});

303

```

304

305

### CI/CD Integration

306

307

```javascript

308

import { run } from "test";

309

310

async function runTests() {

311

return new Promise((resolve, reject) => {

312

const stream = run({

313

files: process.env.TEST_FILES?.split(',') || undefined,

314

concurrency: parseInt(process.env.TEST_CONCURRENCY) || 1,

315

timeout: parseInt(process.env.TEST_TIMEOUT) || 30000

316

});

317

318

const results = { pass: 0, fail: 0, skip: 0, todo: 0 };

319

320

stream.on('test:pass', () => results.pass++);

321

stream.on('test:fail', () => results.fail++);

322

stream.on('test:skip', () => results.skip++);

323

stream.on('test:todo', () => results.todo++);

324

325

stream.on('end', (finalResults) => {

326

console.log(`Tests completed: ${finalResults.pass}/${finalResults.total} passed`);

327

328

if (finalResults.fail > 0) {

329

reject(new Error(`${finalResults.fail} tests failed`));

330

} else {

331

resolve(finalResults);

332

}

333

});

334

335

stream.on('error', reject);

336

});

337

}

338

339

// Usage in CI

340

runTests()

341

.then(() => process.exit(0))

342

.catch(() => process.exit(1));

343

```

344

345

### Watch Mode Implementation

346

347

```javascript

348

import { run } from "test";

349

import { watch } from "fs";

350

351

let currentRun = null;

352

353

function runTests() {

354

if (currentRun) {

355

currentRun.abort();

356

}

357

358

const controller = new AbortController();

359

currentRun = controller;

360

361

const stream = run({

362

signal: controller.signal,

363

files: ['./src/**/*.test.js']

364

});

365

366

stream.on('end', (results) => {

367

console.log(`\nWatching for changes... (${results.pass}/${results.total} passed)`);

368

currentRun = null;

369

});

370

371

stream.on('error', (error) => {

372

if (error.name !== 'AbortError') {

373

console.error('Run error:', error);

374

}

375

currentRun = null;

376

});

377

}

378

379

// Initial run

380

runTests();

381

382

// Watch for file changes

383

watch('./src', { recursive: true }, (eventType, filename) => {

384

if (filename.endsWith('.js')) {

385

console.log(`\nFile changed: ${filename}`);

386

runTests();

387

}

388

});

389

```