or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-api.mdcore-classes.mdfactories.mdindex.mdplugins.mdreporters.mdtest-management.md
tile.json

plugins.mddocs/

0

# Plugin System

1

2

Plugin architecture for extending functionality with built-in and custom plugins.

3

4

## Capabilities

5

6

### Plugin Function Interface

7

8

Core plugin function signature for creating custom plugins.

9

10

```typescript { .api }

11

/**

12

* Plugin function receives an instance of the runner, emitter, config and CLI args

13

* @param japa - Plugin context with runner, emitter, config, and CLI arguments

14

*/

15

interface PluginFn {

16

(japa: {

17

config: NormalizedConfig;

18

cliArgs: CLIArgs;

19

runner: Runner;

20

emitter: Emitter;

21

}): void | Promise<void>;

22

}

23

```

24

25

### Disallow Pinned Tests Plugin

26

27

Built-in plugin that prevents pinned tests from running in production or CI environments.

28

29

```typescript { .api }

30

/**

31

* Disallows pinned tests by throwing an error before the runner starts executing tests

32

* @param options - Plugin configuration options

33

* @returns Plugin function

34

*/

35

function disallowPinnedTests(options?: {

36

/** Whether to disallow pinned tests (default: true) */

37

disallow?: boolean;

38

/** Custom error message to display */

39

errorMessage?: string;

40

}): PluginFn;

41

```

42

43

**Usage Examples:**

44

45

```typescript

46

import { configure } from "@japa/runner";

47

import { disallowPinnedTests } from "@japa/runner/plugins";

48

49

// Basic usage - disallow pinned tests

50

configure({

51

files: ["tests/**/*.spec.ts"],

52

plugins: [

53

disallowPinnedTests(),

54

],

55

});

56

57

// Custom configuration

58

configure({

59

files: ["tests/**/*.spec.ts"],

60

plugins: [

61

disallowPinnedTests({

62

disallow: process.env.NODE_ENV === "production",

63

errorMessage: "Pinned tests are not allowed in production environment",

64

}),

65

],

66

});

67

68

// Conditional based on environment

69

const isProduction = process.env.NODE_ENV === "production";

70

configure({

71

files: ["tests/**/*.spec.ts"],

72

plugins: [

73

disallowPinnedTests({

74

disallow: isProduction,

75

errorMessage: isProduction

76

? "Production builds cannot contain pinned tests"

77

: "Pinned tests found - remove .pin() before deployment",

78

}),

79

],

80

});

81

```

82

83

### Custom Plugin Creation

84

85

Create custom plugins to extend test runner functionality.

86

87

**Usage Examples:**

88

89

```typescript

90

import { configure } from "@japa/runner";

91

92

// Test timing plugin

93

const testTimingPlugin = () => {

94

const testTimes = new Map();

95

96

return ({ runner, emitter }) => {

97

emitter.on("test:start", (payload) => {

98

testTimes.set(payload.title, Date.now());

99

});

100

101

emitter.on("test:end", (payload) => {

102

const startTime = testTimes.get(payload.title);

103

if (startTime) {

104

const duration = Date.now() - startTime;

105

if (duration > 1000) {

106

console.warn(`Slow test detected: ${payload.title} (${duration}ms)`);

107

}

108

testTimes.delete(payload.title);

109

}

110

});

111

};

112

};

113

114

// Database cleanup plugin

115

const dbCleanupPlugin = () => {

116

return ({ runner, emitter, config }) => {

117

emitter.on("runner:start", async () => {

118

console.log("Setting up test database...");

119

await setupTestDatabase();

120

});

121

122

emitter.on("runner:end", async () => {

123

console.log("Cleaning up test database...");

124

await cleanupTestDatabase();

125

});

126

127

emitter.on("test:end", async (payload) => {

128

if (payload.hasError) {

129

await rollbackTestTransaction();

130

}

131

});

132

};

133

};

134

135

// Environment validation plugin

136

const envValidationPlugin = (requiredEnvVars: string[]) => {

137

return ({ config, cliArgs }) => {

138

for (const envVar of requiredEnvVars) {

139

if (!process.env[envVar]) {

140

throw new Error(`Required environment variable ${envVar} is not set`);

141

}

142

}

143

144

console.log("βœ“ All required environment variables are present");

145

};

146

};

147

148

// Coverage threshold plugin

149

const coverageThresholdPlugin = (threshold: number) => {

150

return ({ runner, emitter }) => {

151

emitter.on("runner:end", () => {

152

const summary = runner.getSummary();

153

const passRate = (summary.aggregates.passed / summary.aggregates.total) * 100;

154

155

if (passRate < threshold) {

156

console.error(`Test coverage ${passRate.toFixed(1)}% is below threshold ${threshold}%`);

157

process.exitCode = 1;

158

}

159

});

160

};

161

};

162

163

// Use custom plugins

164

configure({

165

files: ["tests/**/*.spec.ts"],

166

plugins: [

167

testTimingPlugin(),

168

dbCleanupPlugin(),

169

envValidationPlugin(["DATABASE_URL", "API_KEY"]),

170

coverageThresholdPlugin(80),

171

],

172

});

173

```

174

175

### Plugin Event System

176

177

Plugins can listen to various events throughout the test execution lifecycle.

178

179

```typescript { .api }

180

// Available events for plugin integration

181

interface PluginEvents {

182

"runner:start": () => void;

183

"runner:end": () => void;

184

"suite:start": (payload: { name: string }) => void;

185

"suite:end": (payload: { name: string; hasError: boolean }) => void;

186

"group:start": (payload: { title: string }) => void;

187

"group:end": (payload: { title: string; hasError: boolean }) => void;

188

"test:start": (payload: { title: string }) => void;

189

"test:end": (payload: {

190

title: string;

191

hasError: boolean;

192

duration: number;

193

errors: Error[];

194

}) => void;

195

}

196

```

197

198

**Usage Examples:**

199

200

```typescript

201

// Comprehensive event logging plugin

202

const eventLoggerPlugin = () => {

203

return ({ emitter }) => {

204

emitter.on("runner:start", () => {

205

console.log("πŸƒ Test runner started");

206

});

207

208

emitter.on("suite:start", (payload) => {

209

console.log(`πŸ“‚ Suite started: ${payload.name}`);

210

});

211

212

emitter.on("group:start", (payload) => {

213

console.log(`πŸ“ Group started: ${payload.title}`);

214

});

215

216

emitter.on("test:start", (payload) => {

217

console.log(`πŸ§ͺ Test started: ${payload.title}`);

218

});

219

220

emitter.on("test:end", (payload) => {

221

const status = payload.hasError ? "❌" : "βœ…";

222

console.log(`${status} Test completed: ${payload.title} (${payload.duration}ms)`);

223

});

224

225

emitter.on("group:end", (payload) => {

226

const status = payload.hasError ? "❌" : "βœ…";

227

console.log(`${status} Group completed: ${payload.title}`);

228

});

229

230

emitter.on("suite:end", (payload) => {

231

const status = payload.hasError ? "❌" : "βœ…";

232

console.log(`${status} Suite completed: ${payload.name}`);

233

});

234

235

emitter.on("runner:end", () => {

236

console.log("🏁 Test runner finished");

237

});

238

};

239

};

240

```

241

242

### Plugin Configuration Integration

243

244

Plugins can access and modify configuration during initialization.

245

246

**Usage Examples:**

247

248

```typescript

249

// Dynamic suite configuration plugin

250

const dynamicSuitePlugin = () => {

251

return ({ config, runner }) => {

252

// Add dynamic suite configuration

253

runner.onSuite((suite) => {

254

if (suite.name === "integration") {

255

suite.timeout(30000);

256

}

257

});

258

259

// Modify global configuration based on environment

260

if (process.env.NODE_ENV === "test") {

261

config.timeout = Math.max(config.timeout, 5000);

262

}

263

};

264

};

265

266

// Conditional plugin loading

267

const conditionalFeaturesPlugin = () => {

268

return ({ config, cliArgs, emitter }) => {

269

// Enable additional features based on CLI args

270

if (cliArgs.verbose) {

271

emitter.on("test:start", (payload) => {

272

console.log(`Starting test: ${payload.title}`);

273

});

274

}

275

276

// Modify configuration based on detected environment

277

if (process.env.CI) {

278

config.forceExit = true;

279

}

280

};

281

};

282

```

283

284

## Types

285

286

### Plugin Context Types

287

288

```typescript { .api }

289

interface PluginContext {

290

config: NormalizedConfig;

291

cliArgs: CLIArgs;

292

runner: Runner;

293

emitter: Emitter;

294

}

295

296

interface NormalizedConfig {

297

cwd: string;

298

timeout: number;

299

retries: number;

300

filters: Filters;

301

configureSuite: (suite: Suite) => void;

302

reporters: {

303

activated: string[];

304

list: NamedReporterContract[];

305

};

306

plugins: PluginFn[];

307

importer: (filePath: URL) => void | Promise<void>;

308

refiner: Refiner;

309

forceExit: boolean;

310

setup: SetupHookHandler[];

311

teardown: TeardownHookHandler[];

312

exclude: string[];

313

}

314

315

interface CLIArgs {

316

_?: string[];

317

tags?: string | string[];

318

files?: string | string[];

319

tests?: string | string[];

320

groups?: string | string[];

321

timeout?: string;

322

retries?: string;

323

reporters?: string | string[];

324

forceExit?: boolean;

325

failed?: boolean;

326

help?: boolean;

327

matchAll?: boolean;

328

listPinned?: boolean;

329

bail?: boolean;

330

bailLayer?: string;

331

}

332

```

333

334

### Event Payload Types

335

336

```typescript { .api }

337

interface TestEndPayload {

338

title: string;

339

hasError: boolean;

340

duration: number;

341

errors: Error[];

342

}

343

344

interface SuiteEndPayload {

345

name: string;

346

hasError: boolean;

347

}

348

349

interface GroupEndPayload {

350

title: string;

351

hasError: boolean;

352

}

353

```