or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-environment.mdbuild-tools.mdcli.mdconfiguration.mdindex.mdplugin-system.mdtest-runner.md

plugin-system.mddocs/

0

# Plugin System

1

2

Web Component Tester features an extensible plugin architecture for adding custom functionality, browser support, and testing environments. The plugin system provides hooks into the test lifecycle and configuration system.

3

4

## Capabilities

5

6

### Plugin Base Class

7

8

Base plugin class for creating custom WCT plugins.

9

10

```typescript { .api }

11

/**

12

* Base plugin class for extending WCT functionality

13

*/

14

interface Plugin {

15

/** Plugin name */

16

name: string;

17

/** CLI configuration options */

18

cliConfig: any;

19

/** Execute plugin during test run */

20

execute(context: Context): Promise<void>;

21

}

22

23

/**

24

* Get plugin instance by name

25

* @param name - Plugin name (e.g., 'local', 'sauce', 'custom-plugin')

26

* @returns Promise resolving to plugin instance

27

*/

28

function get(name: string): Promise<Plugin>;

29

```

30

31

**Usage Examples:**

32

33

```javascript

34

// Using built-in plugins

35

const wct = require('web-component-tester');

36

37

const localPlugin = await wct.Plugin.get('local');

38

const saucePlugin = await wct.Plugin.get('sauce');

39

40

// Plugin has CLI configuration

41

console.log(localPlugin.cliConfig); // Available command line options

42

```

43

44

### Context and Hooks

45

46

Test execution context with plugin hook system for lifecycle integration.

47

48

```typescript { .api }

49

/**

50

* Test execution context with event system and plugin hooks

51

*/

52

interface Context extends EventEmitter {

53

/** Current configuration options */

54

options: Config;

55

/** Emit plugin hook for all loaded plugins */

56

emitHook(name: string): Promise<void>;

57

/** Get all loaded plugins */

58

plugins(): Promise<Plugin[]>;

59

}

60

```

61

62

**Available Hooks:**

63

- `configure` - After configuration is loaded and merged

64

- `prepare` - Before test execution begins

65

- `cleanup` - After all tests complete

66

67

**Usage Examples:**

68

69

```javascript

70

const wct = require('web-component-tester');

71

72

const context = new wct.Context({

73

suites: ['test/*.html'],

74

verbose: true

75

});

76

77

// Hook into test lifecycle

78

context.on('run-start', (options) => {

79

console.log('Starting tests with', options.activeBrowsers.length, 'browsers');

80

});

81

82

// Custom hook registration

83

context.options.registerHooks = (context) => {

84

context.hookRegister('prepare', async () => {

85

console.log('Custom prepare hook');

86

// Custom preparation logic

87

});

88

89

context.hookRegister('cleanup', async () => {

90

console.log('Custom cleanup hook');

91

// Custom cleanup logic

92

});

93

};

94

95

await wct.test(context);

96

```

97

98

## Built-in Plugins

99

100

### Local Plugin

101

102

Runs tests on locally installed browsers.

103

104

```typescript { .api }

105

interface LocalPluginOptions {

106

/** Disable local testing */

107

disabled?: boolean;

108

/** Browsers to run locally */

109

browsers?: string[];

110

/** Browser-specific command line options */

111

browserOptions?: {[browserName: string]: string[]};

112

/** Skip automatic Selenium installation */

113

skipSeleniumInstall?: boolean;

114

}

115

```

116

117

**Configuration Examples:**

118

119

```json

120

{

121

"plugins": {

122

"local": {

123

"browsers": ["chrome", "firefox", "safari"],

124

"browserOptions": {

125

"chrome": ["--headless", "--disable-gpu"],

126

"firefox": ["-headless"]

127

}

128

}

129

}

130

}

131

```

132

133

### Sauce Plugin

134

135

Runs tests on Sauce Labs remote browsers.

136

137

```typescript { .api }

138

interface SaucePluginOptions {

139

/** Disable Sauce Labs testing */

140

disabled?: boolean;

141

/** Sauce Labs username */

142

username?: string;

143

/** Sauce Labs access key */

144

accessKey?: string;

145

/** Existing tunnel ID to use */

146

tunnelId?: string;

147

/** Remote browser configurations */

148

browsers?: SauceBrowserDef[];

149

/** Build identifier */

150

build?: string;

151

/** Test tags */

152

tags?: string[];

153

}

154

155

interface SauceBrowserDef {

156

browserName: string;

157

platform?: string;

158

version?: string;

159

deviceName?: string;

160

[capability: string]: any;

161

}

162

```

163

164

**Configuration Examples:**

165

166

```json

167

{

168

"plugins": {

169

"sauce": {

170

"username": "${SAUCE_USERNAME}",

171

"accessKey": "${SAUCE_ACCESS_KEY}",

172

"build": "${BUILD_NUMBER}",

173

"tags": ["web-components", "polymer"],

174

"browsers": [

175

{

176

"browserName": "chrome",

177

"platform": "Windows 10",

178

"version": "latest"

179

},

180

{

181

"browserName": "safari",

182

"platform": "macOS 10.15",

183

"version": "latest"

184

}

185

]

186

}

187

}

188

}

189

```

190

191

## Custom Plugin Development

192

193

### Plugin Module Structure

194

195

```javascript

196

// package.json

197

{

198

"name": "wct-custom-plugin",

199

"wct-plugin": {

200

"cli-options": {

201

"customOption": {

202

"help": "Description of custom option"

203

}

204

}

205

}

206

}

207

208

// plugin.js (main module)

209

module.exports = function(context, pluginOptions, plugin) {

210

// Plugin initialization logic

211

212

// Register for lifecycle hooks

213

context.hookRegister('configure', async () => {

214

console.log('Custom plugin configure hook');

215

// Modify context.options if needed

216

});

217

218

context.hookRegister('prepare', async () => {

219

console.log('Custom plugin prepare hook');

220

// Start any long-running processes

221

});

222

223

context.hookRegister('cleanup', async () => {

224

console.log('Custom plugin cleanup hook');

225

// Cleanup resources

226

});

227

228

// Handle test events

229

context.on('browser-start', (browser, metadata, stats) => {

230

console.log(`Custom plugin: browser ${browser.browserName} started`);

231

});

232

233

context.on('test-end', (browser, test, stats) => {

234

if (test.state === 'failed') {

235

console.log(`Custom plugin: test failed - ${test.title}`);

236

}

237

});

238

};

239

```

240

241

### Plugin Registration

242

243

```javascript

244

// Custom plugin loading

245

const wct = require('web-component-tester');

246

247

await wct.test({

248

suites: ['test/*.html'],

249

plugins: {

250

'custom-plugin': {

251

customOption: 'value',

252

enabled: true

253

}

254

}

255

});

256

```

257

258

### Advanced Plugin Examples

259

260

```javascript

261

// Screenshot plugin

262

module.exports = function(context, pluginOptions, plugin) {

263

const screenshots = [];

264

265

context.on('test-end', async (browser, test, stats) => {

266

if (test.state === 'failed' && pluginOptions.screenshotOnFailure) {

267

const screenshot = await browser.takeScreenshot();

268

screenshots.push({

269

test: test.title,

270

browser: browser.browserName,

271

screenshot: screenshot

272

});

273

}

274

});

275

276

context.hookRegister('cleanup', async () => {

277

if (screenshots.length > 0) {

278

const fs = require('fs');

279

fs.writeFileSync('failed-test-screenshots.json',

280

JSON.stringify(screenshots, null, 2));

281

}

282

});

283

};

284

285

// Performance monitoring plugin

286

module.exports = function(context, pluginOptions, plugin) {

287

const performanceData = [];

288

289

context.on('test-end', (browser, test, stats) => {

290

if (test.duration > pluginOptions.slowThreshold) {

291

performanceData.push({

292

test: test.title,

293

browser: browser.browserName,

294

duration: test.duration,

295

timestamp: new Date().toISOString()

296

});

297

}

298

});

299

300

context.hookRegister('cleanup', async () => {

301

if (performanceData.length > 0) {

302

console.warn('Slow tests detected:');

303

performanceData.forEach(data => {

304

console.warn(` ${data.test} (${data.browser}): ${data.duration}ms`);

305

});

306

}

307

});

308

};

309

310

// Custom browser plugin

311

module.exports = function(context, pluginOptions, plugin) {

312

context.hookRegister('prepare', async () => {

313

// Add custom browser to active browsers

314

context.options.activeBrowsers.push({

315

browserName: 'custom-browser',

316

platform: 'Custom Platform',

317

// Custom browser launch logic

318

startBrowser: async () => {

319

// Implementation for starting custom browser

320

}

321

});

322

});

323

};

324

```

325

326

## Plugin Configuration

327

328

### Plugin Discovery

329

330

WCT discovers plugins in several ways:

331

1. Built-in plugins (`local`, `sauce`)

332

2. NPM modules with `wct-plugin` in package.json

333

3. Local plugins via relative paths

334

4. Plugins specified in configuration

335

336

### Plugin Loading Order

337

338

1. Built-in plugins are loaded first

339

2. NPM plugin modules are discovered and loaded

340

3. Plugins are initialized in dependency order

341

4. Plugin hooks are executed during test lifecycle

342

343

### Plugin CLI Integration

344

345

```javascript

346

// package.json

347

{

348

"wct-plugin": {

349

"cli-options": {

350

"customBrowser": {

351

"help": "Browser executable path",

352

"metavar": "<path>"

353

},

354

"customTimeout": {

355

"help": "Custom timeout in seconds",

356

"type": "number",

357

"default": 30

358

},

359

"customFlag": {

360

"help": "Enable custom feature",

361

"flag": true

362

}

363

}

364

}

365

}

366

```

367

368

### Plugin Error Handling

369

370

```javascript

371

module.exports = function(context, pluginOptions, plugin) {

372

context.hookRegister('prepare', async () => {

373

try {

374

// Plugin preparation logic

375

await initializeCustomService();

376

} catch (error) {

377

context.emit('log:error', 'Custom plugin failed to initialize:', error);

378

throw error; // This will fail the test run

379

}

380

});

381

382

// Graceful error handling

383

context.on('browser-end', (browser, error, stats) => {

384

if (error && pluginOptions.continueOnError) {

385

context.emit('log:warn', 'Ignoring browser error due to continueOnError option');

386

// Don't re-throw the error

387

}

388

});

389

};

390

```

391

392

## Plugin Examples

393

394

### Real-world Plugin Usage

395

396

```json

397

{

398

"plugins": {

399

"local": {

400

"browsers": ["chrome", "firefox"]

401

},

402

"sauce": {

403

"disabled": true

404

},

405

"wct-coverage": {

406

"threshold": 80,

407

"reporters": ["lcov", "json"]

408

},

409

"wct-screenshot": {

410

"screenshotOnFailure": true,

411

"outputDir": "screenshots"

412

},

413

"wct-performance": {

414

"slowThreshold": 5000,

415

"generateReport": true

416

}

417

}

418

}

419

```

420

421

### Plugin Development Workflow

422

423

```bash

424

# Create plugin project

425

mkdir wct-my-plugin

426

cd wct-my-plugin

427

npm init

428

429

# Add plugin metadata to package.json

430

# Implement plugin in index.js

431

# Test with local WCT project

432

433

# Link for development

434

npm link

435

cd ../my-wct-project

436

npm link wct-my-plugin

437

438

# Use in wct.conf.json

439

{

440

"plugins": {

441

"my-plugin": {

442

"option": "value"

443

}

444

}

445

}

446

```

447

448

## Types

449

450

```typescript { .api }

451

interface PluginHook {

452

(context: Context): Promise<void>;

453

}

454

455

interface PluginMetadata {

456

'cli-options'?: {

457

[optionName: string]: {

458

help: string;

459

type?: 'string' | 'number' | 'boolean';

460

flag?: boolean;

461

default?: any;

462

metavar?: string;

463

};

464

};

465

}

466

467

interface PluginModule {

468

(context: Context, pluginOptions: any, plugin: Plugin): void;

469

}

470

```