0
# Plugin Development
1
2
Interfaces and types for creating compatible plugins that work with both Playwright Extra and puppeteer-extra. The plugin system provides comprehensive lifecycle hooks and compatibility features for extending browser automation capabilities.
3
4
## Capabilities
5
6
### Plugin Interface
7
8
The core interface that all plugins must implement to be compatible with Playwright Extra.
9
10
```typescript { .api }
11
/**
12
* PuppeteerExtraPlugin interface, strongly typed for internal use
13
*/
14
interface PuppeteerExtraPlugin extends Partial<PluginLifecycleMethods> {
15
/** Plugin identification flag - must be true */
16
_isPuppeteerExtraPlugin: boolean;
17
/** Unique plugin name */
18
name: string;
19
/** Disable the puppeteer compatibility shim for this plugin */
20
noPuppeteerShim?: boolean;
21
/** Plugin requirements and capabilities */
22
requirements?: PluginRequirements;
23
/** Plugin dependencies that should be auto-loaded */
24
dependencies?: PluginDependencies;
25
/** Data that can be shared with other plugins */
26
data?: PluginData[];
27
/** Access data from other plugins (if dataFromPlugins requirement is set) */
28
getDataFromPlugins?(name?: string): void;
29
/** Method for registering child class members (PuppeteerExtraPlugin compatibility) */
30
_registerChildClassMembers?(prototype: any): void;
31
/** List of child class members (PuppeteerExtraPlugin compatibility) */
32
_childClassMembers?: string[];
33
/** Sub-plugins that should be registered automatically */
34
plugins?: CompatiblePlugin[];
35
}
36
```
37
38
### Plugin Lifecycle Methods
39
40
Abstract class defining all available lifecycle hooks that plugins can implement.
41
42
```typescript { .api }
43
/**
44
* Strongly typed plugin lifecycle events for internal use
45
*/
46
abstract class PluginLifecycleMethods {
47
/** Called when plugin is registered with the framework */
48
async onPluginRegistered(env?: PluginEnv): Promise<void> {}
49
50
/** Called before browser launch, can modify launch options */
51
async beforeLaunch(options: LaunchOptions): Promise<LaunchOptions | void> {}
52
53
/** Called after browser launch, receives browser or context instance */
54
async afterLaunch(browserOrContext?: Browser | BrowserContext): Promise<void> {}
55
56
/** Called before connecting to existing browser, can modify connect options */
57
async beforeConnect(options: ConnectOptions): Promise<ConnectOptions | void> {}
58
59
/** Called after connecting to existing browser */
60
async afterConnect(browser: Browser): Promise<void> {}
61
62
/** Called when browser instance is available */
63
async onBrowser(browser: Browser): Promise<void> {}
64
65
/** Called when a new page is created */
66
async onPageCreated(page: Page): Promise<void> {}
67
68
/** Called when a page is closed */
69
async onPageClose(page: Page): Promise<void> {}
70
71
/** Called when browser is disconnected */
72
async onDisconnected(browser?: Browser): Promise<void> {}
73
74
/** Called before creating browser context, can modify context options */
75
async beforeContext(
76
options?: BrowserContextOptions,
77
browser?: Browser
78
): Promise<BrowserContextOptions | void> {}
79
80
/** Called when a new browser context is created */
81
async onContextCreated(
82
context?: BrowserContext,
83
options?: BrowserContextOptions
84
): Promise<void> {}
85
}
86
```
87
88
### Basic Plugin Example
89
90
Simple plugin implementation demonstrating the core structure:
91
92
```typescript
93
import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin';
94
95
class MyPlugin extends PuppeteerExtraPlugin {
96
constructor(opts = {}) {
97
super(opts);
98
}
99
100
get name() {
101
return 'my-plugin';
102
}
103
104
async onPluginRegistered() {
105
console.log('Plugin registered with Playwright Extra');
106
}
107
108
async beforeLaunch(options) {
109
console.log('Before launch:', options);
110
// Modify launch options
111
options.args = options.args || [];
112
options.args.push('--custom-flag');
113
return options;
114
}
115
116
async onPageCreated(page) {
117
console.log('New page created');
118
// Add custom functionality to pages
119
await page.addInitScript(() => {
120
console.log('Custom script injected');
121
});
122
}
123
}
124
125
module.exports = (pluginConfig) => new MyPlugin(pluginConfig);
126
```
127
128
### Plugin Requirements
129
130
Plugins can declare requirements that affect their behavior and execution order:
131
132
```typescript { .api }
133
type PluginRequirements = Set<
134
| 'launch' // Plugin requires browser launch
135
| 'headful' // Plugin requires headful (non-headless) mode
136
| 'dataFromPlugins' // Plugin needs access to data from other plugins
137
| 'runLast' // Plugin should run after all other plugins
138
>;
139
```
140
141
**Usage Examples:**
142
143
```typescript
144
class DataCollectorPlugin extends PuppeteerExtraPlugin {
145
constructor(opts = {}) {
146
super(opts);
147
this.requirements = new Set(['dataFromPlugins', 'runLast']);
148
}
149
150
get name() {
151
return 'data-collector';
152
}
153
154
async onPluginRegistered() {
155
// This plugin will have access to getDataFromPlugins method
156
const allData = this.getDataFromPlugins();
157
console.log('Data from other plugins:', allData);
158
}
159
}
160
```
161
162
### Plugin Dependencies
163
164
Plugins can declare dependencies that will be automatically loaded:
165
166
```typescript { .api }
167
type PluginDependencies = Set<string> | Map<string, any> | string[];
168
```
169
170
**Usage Examples:**
171
172
```typescript
173
class CompositePlugin extends PuppeteerExtraPlugin {
174
constructor(opts = {}) {
175
super(opts);
176
// Declare dependencies as Set
177
this.dependencies = new Set([
178
'stealth/evasions/webgl.vendor',
179
'stealth/evasions/user-agent-override'
180
]);
181
}
182
183
get name() {
184
return 'composite-plugin';
185
}
186
}
187
188
class ConfigurablePlugin extends PuppeteerExtraPlugin {
189
constructor(opts = {}) {
190
super(opts);
191
// Declare dependencies with options as Map
192
this.dependencies = new Map([
193
['stealth/evasions/webgl.vendor', {
194
vendor: 'Custom',
195
renderer: 'Custom'
196
}],
197
['stealth/evasions/user-agent-override', {
198
userAgent: 'Custom User Agent'
199
}]
200
]);
201
}
202
203
get name() {
204
return 'configurable-plugin';
205
}
206
}
207
```
208
209
### Plugin Data Sharing
210
211
Plugins can expose data that other plugins can access:
212
213
```typescript { .api }
214
interface PluginData {
215
name: string | { [key: string]: any };
216
value: { [key: string]: any };
217
}
218
```
219
220
**Usage Examples:**
221
222
```typescript
223
class DataProviderPlugin extends PuppeteerExtraPlugin {
224
constructor(opts = {}) {
225
super(opts);
226
this.data = [
227
{
228
name: 'config',
229
value: { apiKey: 'secret-key', endpoint: 'https://api.example.com' }
230
},
231
{
232
name: 'cache',
233
value: { results: [], timestamps: [] }
234
}
235
];
236
}
237
238
get name() {
239
return 'data-provider';
240
}
241
}
242
243
class DataConsumerPlugin extends PuppeteerExtraPlugin {
244
constructor(opts = {}) {
245
super(opts);
246
this.requirements = new Set(['dataFromPlugins']);
247
}
248
249
get name() {
250
return 'data-consumer';
251
}
252
253
async onPluginRegistered() {
254
// Access data from other plugins
255
const configData = this.getDataFromPlugins('config');
256
const cacheData = this.getDataFromPlugins('cache');
257
258
console.log('Config:', configData);
259
console.log('Cache:', cacheData);
260
}
261
}
262
```
263
264
### Advanced Plugin Pattern
265
266
Complex plugin with multiple lifecycle hooks and error handling:
267
268
```typescript
269
class AdvancedPlugin extends PuppeteerExtraPlugin {
270
constructor(opts = {}) {
271
super(opts);
272
this.options = { timeout: 30000, retry: 3, ...opts };
273
}
274
275
get name() {
276
return 'advanced-plugin';
277
}
278
279
async beforeLaunch(options) {
280
try {
281
// Pre-launch setup
282
console.log('Configuring browser launch');
283
options.args = options.args || [];
284
options.args.push(`--timeout=${this.options.timeout}`);
285
return options;
286
} catch (error) {
287
console.error('Error in beforeLaunch:', error);
288
throw error;
289
}
290
}
291
292
async onBrowser(browser) {
293
// Set up browser-level monitoring
294
browser.on('disconnected', () => {
295
console.log('Browser disconnected');
296
});
297
}
298
299
async beforeContext(options, browser) {
300
// Configure context options
301
options = options || {};
302
options.userAgent = 'Custom User Agent';
303
return options;
304
}
305
306
async onPageCreated(page) {
307
// Set up page-level functionality
308
await page.addInitScript(() => {
309
// Inject custom functionality
310
window.customAPI = {
311
version: '1.0.0',
312
initialized: true
313
};
314
});
315
316
// Add error handling
317
page.on('pageerror', error => {
318
console.error('Page error:', error);
319
});
320
}
321
}
322
323
module.exports = (pluginConfig) => new AdvancedPlugin(pluginConfig);
324
```
325
326
## Core Types
327
328
```typescript { .api }
329
type PluginMethodName = keyof PluginLifecycleMethods;
330
331
interface PluginEnv {
332
framework: 'playwright';
333
}
334
335
type CompatiblePlugin =
336
| CompatiblePuppeteerPlugin
337
| CompatiblePlaywrightPlugin
338
| CompatibleExtraPlugin;
339
340
interface CompatiblePuppeteerPlugin {
341
_isPuppeteerExtraPlugin: boolean;
342
name?: string;
343
}
344
345
interface CompatiblePlaywrightPlugin {
346
_isPlaywrightExtraPlugin: boolean;
347
name?: string;
348
}
349
350
interface CompatibleExtraPlugin {
351
_isExtraPlugin: boolean;
352
name?: string;
353
}
354
```