opencodekit 0.16.19 → 0.16.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -759,7 +759,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
759
759
|
// package.json
|
|
760
760
|
var package_default = {
|
|
761
761
|
name: "opencodekit",
|
|
762
|
-
version: "0.16.
|
|
762
|
+
version: "0.16.20",
|
|
763
763
|
description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
|
|
764
764
|
keywords: ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
|
|
765
765
|
license: "MIT",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"agent": {
|
|
4
4
|
"build": {
|
|
5
5
|
"description": "Primary development agent with full codebase access",
|
|
6
|
-
"model": "
|
|
6
|
+
"model": "opencode/kimi-k2.5-free"
|
|
7
7
|
},
|
|
8
8
|
"compaction": {
|
|
9
9
|
"description": "Session summarizer for context continuity across compactions"
|
|
@@ -193,9 +193,6 @@
|
|
|
193
193
|
],
|
|
194
194
|
"provider": {
|
|
195
195
|
"github-copilot": {
|
|
196
|
-
"options": {
|
|
197
|
-
"timeout": 600000
|
|
198
|
-
},
|
|
199
196
|
"models": {
|
|
200
197
|
"claude-haiku-4.5": {
|
|
201
198
|
"attachment": true,
|
|
@@ -260,11 +257,11 @@
|
|
|
260
257
|
"variants": {
|
|
261
258
|
"adaptive": {
|
|
262
259
|
"options": {
|
|
263
|
-
"thinking": {
|
|
264
|
-
"type": "adaptive"
|
|
265
|
-
},
|
|
266
260
|
"output_config": {
|
|
267
261
|
"effort": "high"
|
|
262
|
+
},
|
|
263
|
+
"thinking": {
|
|
264
|
+
"type": "adaptive"
|
|
268
265
|
}
|
|
269
266
|
}
|
|
270
267
|
},
|
|
@@ -368,6 +365,9 @@
|
|
|
368
365
|
}
|
|
369
366
|
}
|
|
370
367
|
}
|
|
368
|
+
},
|
|
369
|
+
"options": {
|
|
370
|
+
"timeout": 600000
|
|
371
371
|
}
|
|
372
372
|
},
|
|
373
373
|
"kimi-for-coding": {
|
|
@@ -547,6 +547,32 @@
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
},
|
|
550
|
+
"claude-opus-4-6-thinking": {
|
|
551
|
+
"limit": {
|
|
552
|
+
"context": 200000,
|
|
553
|
+
"output": 64000
|
|
554
|
+
},
|
|
555
|
+
"name": "Claude Opus 4 6 Thinking",
|
|
556
|
+
"options": {
|
|
557
|
+
"thinking": {
|
|
558
|
+
"budgetTokens": 8192,
|
|
559
|
+
"type": "enabled"
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
"reasoning": true,
|
|
563
|
+
"variants": {
|
|
564
|
+
"low": {
|
|
565
|
+
"thinkingConfig": {
|
|
566
|
+
"thinkingBudget": 8192
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
"max": {
|
|
570
|
+
"thinkingConfig": {
|
|
571
|
+
"thinkingBudget": 32768
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
},
|
|
550
576
|
"claude-sonnet-4-5": {
|
|
551
577
|
"limit": {
|
|
552
578
|
"context": 200000,
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf-extract
|
|
3
|
+
description: Extract text, images, tables, and metadata from PDF files. Choose the right library based on extraction needs - text only, structured data, or complex layouts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PDF Content Extraction
|
|
7
|
+
|
|
8
|
+
Extract content from PDF files using the best library for your specific use case.
|
|
9
|
+
|
|
10
|
+
## Quick Decision Guide
|
|
11
|
+
|
|
12
|
+
| Use Case | Recommended Library | Why |
|
|
13
|
+
| --------------------------- | ------------------- | ---------------------------------- |
|
|
14
|
+
| Simple text extraction | `pdf-parse` (v2+) | Fast, lightweight, pure TypeScript |
|
|
15
|
+
| Complex layouts/coordinates | `pdfjs-dist` | Full control, precise positioning |
|
|
16
|
+
| Tables/tabular data | `pdf-data-parser` | Built for grid-based content |
|
|
17
|
+
| Forms (XFA) | `pdf-lib` + custom | Form field extraction |
|
|
18
|
+
| Browser + Node.js | `pdf-parse` v2 | Cross-platform, works everywhere |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Library 1: pdf-parse (Recommended for Text)
|
|
23
|
+
|
|
24
|
+
**Best for:** Simple text extraction, metadata, fast processing
|
|
25
|
+
|
|
26
|
+
### Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install pdf-parse
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Basic Text Extraction
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { PDFParse } from "pdf-parse";
|
|
36
|
+
import { readFile } from "fs/promises";
|
|
37
|
+
|
|
38
|
+
async function extractText(filePath: string): Promise<string> {
|
|
39
|
+
const parser = new PDFParse();
|
|
40
|
+
const buffer = await readFile(filePath);
|
|
41
|
+
|
|
42
|
+
const result = await parser.parse(buffer);
|
|
43
|
+
return result.text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Usage
|
|
47
|
+
const text = await extractText("./document.pdf");
|
|
48
|
+
console.log(text);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Extract with Metadata
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { PDFParse } from "pdf-parse";
|
|
55
|
+
|
|
56
|
+
async function extractWithMetadata(filePath: string) {
|
|
57
|
+
const parser = new PDFParse();
|
|
58
|
+
const buffer = await readFile(filePath);
|
|
59
|
+
|
|
60
|
+
const result = await parser.parse(buffer);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
text: result.text,
|
|
64
|
+
info: result.info, // Document metadata
|
|
65
|
+
numpages: result.numpages,
|
|
66
|
+
version: result.version,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Extract Specific Pages
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { PDFParse } from "pdf-parse";
|
|
75
|
+
|
|
76
|
+
async function extractPage(filePath: string, pageNum: number) {
|
|
77
|
+
const parser = new PDFParse();
|
|
78
|
+
const buffer = await readFile(filePath);
|
|
79
|
+
|
|
80
|
+
const result = await parser.parse(buffer, {
|
|
81
|
+
max: pageNum,
|
|
82
|
+
min: pageNum,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return result.text;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### URL-based Extraction (without downloading full file)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { getHeader } from "pdf-parse/node";
|
|
93
|
+
|
|
94
|
+
async function checkPDFHeaders(url: string) {
|
|
95
|
+
// Check file size and headers before downloading
|
|
96
|
+
const headers = await getHeader(url, true);
|
|
97
|
+
console.log(`File size: ${headers.size} bytes`);
|
|
98
|
+
|
|
99
|
+
if (headers.size > 10 * 1024 * 1024) {
|
|
100
|
+
console.warn("Large PDF - consider streaming");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Library 2: pdfjs-dist (Mozilla PDF.js)
|
|
108
|
+
|
|
109
|
+
**Best for:** Complex layouts, coordinates, images, page-by-page control
|
|
110
|
+
|
|
111
|
+
### Installation
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm install pdfjs-dist
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Basic Text Extraction with Coordinates
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
121
|
+
import { readFile } from "fs/promises";
|
|
122
|
+
import path from "path";
|
|
123
|
+
|
|
124
|
+
async function extractWithCoordinates(pdfPath: string) {
|
|
125
|
+
const data = await readFile(pdfPath);
|
|
126
|
+
const dataArray = new Uint8Array(data);
|
|
127
|
+
|
|
128
|
+
const pdfDocument = await pdfjsLib.getDocument({
|
|
129
|
+
data: dataArray,
|
|
130
|
+
standardFontDataUrl: path.join(process.cwd(), "node_modules/pdfjs-dist/standard_fonts/"),
|
|
131
|
+
}).promise;
|
|
132
|
+
|
|
133
|
+
const numPages = pdfDocument.numPages;
|
|
134
|
+
const results = [];
|
|
135
|
+
|
|
136
|
+
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
|
137
|
+
const page = await pdfDocument.getPage(pageNum);
|
|
138
|
+
const textContent = await page.getTextContent();
|
|
139
|
+
|
|
140
|
+
const pageText = textContent.items.map((item: any) => ({
|
|
141
|
+
text: item.str,
|
|
142
|
+
x: item.transform[4],
|
|
143
|
+
y: item.transform[5],
|
|
144
|
+
font: item.fontName,
|
|
145
|
+
width: item.width,
|
|
146
|
+
height: item.height,
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
results.push({
|
|
150
|
+
page: pageNum,
|
|
151
|
+
items: pageText,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Extract Images from PDF
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
163
|
+
|
|
164
|
+
async function extractImages(pdfPath: string) {
|
|
165
|
+
const data = await readFile(pdfPath);
|
|
166
|
+
const pdfDocument = await pdfjsLib.getDocument({ data: new Uint8Array(data) }).promise;
|
|
167
|
+
|
|
168
|
+
const images = [];
|
|
169
|
+
|
|
170
|
+
for (let pageNum = 1; pageNum <= pdfDocument.numPages; pageNum++) {
|
|
171
|
+
const page = await pdfDocument.getPage(pageNum);
|
|
172
|
+
const ops = await page.getOperatorList();
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < ops.fnArray.length; i++) {
|
|
175
|
+
if (ops.fnArray[i] === pdfjsLib.OPS.paintImageXObject) {
|
|
176
|
+
const imageName = ops.argsArray[i][0];
|
|
177
|
+
const image = await page.objs.get(imageName);
|
|
178
|
+
|
|
179
|
+
images.push({
|
|
180
|
+
page: pageNum,
|
|
181
|
+
name: imageName,
|
|
182
|
+
width: image.width,
|
|
183
|
+
height: image.height,
|
|
184
|
+
data: image.data, // Raw image data
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return images;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Render Page to Image
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
198
|
+
import { createCanvas } from "canvas";
|
|
199
|
+
import { writeFile } from "fs/promises";
|
|
200
|
+
|
|
201
|
+
async function renderPageToImage(pdfPath: string, pageNum: number, outputPath: string) {
|
|
202
|
+
const data = await readFile(pdfPath);
|
|
203
|
+
const pdfDocument = await pdfjsLib.getDocument({ data: new Uint8Array(data) }).promise;
|
|
204
|
+
|
|
205
|
+
const page = await pdfDocument.getPage(pageNum);
|
|
206
|
+
const viewport = page.getViewport({ scale: 2.0 }); // Higher scale = better quality
|
|
207
|
+
|
|
208
|
+
const canvas = createCanvas(viewport.width, viewport.height);
|
|
209
|
+
const context = canvas.getContext("2d");
|
|
210
|
+
|
|
211
|
+
await page.render({
|
|
212
|
+
canvasContext: context,
|
|
213
|
+
viewport: viewport,
|
|
214
|
+
}).promise;
|
|
215
|
+
|
|
216
|
+
const buffer = canvas.toBuffer("image/png");
|
|
217
|
+
await writeFile(outputPath, buffer);
|
|
218
|
+
|
|
219
|
+
console.log(`Page ${pageNum} saved to ${outputPath}`);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Library 3: pdf-data-parser (Tables)
|
|
226
|
+
|
|
227
|
+
**Best for:** Tabular data, structured grid content
|
|
228
|
+
|
|
229
|
+
### Installation
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm install pdf-data-parser
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Extract Tables
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { PdfDataParser } from "pdf-data-parser";
|
|
239
|
+
|
|
240
|
+
async function extractTables(pdfPath: string) {
|
|
241
|
+
const parser = new PdfDataParser({
|
|
242
|
+
url: pdfPath,
|
|
243
|
+
// Options
|
|
244
|
+
heading: "Table Title", // Filter to specific table
|
|
245
|
+
cells: 3, // Minimum cells per row
|
|
246
|
+
headers: ["Name", "Amount"], // Expected headers
|
|
247
|
+
repeating: false, // Handle repeating headers
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const rows = await parser.parse();
|
|
251
|
+
return rows; // Array of arrays
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Stream Large PDFs
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { PdfDataReader } from "pdf-data-parser";
|
|
259
|
+
import { createWriteStream } from "fs";
|
|
260
|
+
|
|
261
|
+
async function streamToCSV(pdfPath: string, outputPath: string) {
|
|
262
|
+
const reader = new PdfDataReader({
|
|
263
|
+
url: pdfPath,
|
|
264
|
+
cells: 2,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const output = createWriteStream(outputPath);
|
|
268
|
+
|
|
269
|
+
reader.on("data", (row: string[]) => {
|
|
270
|
+
output.write(row.join(",") + "\n");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
reader.on("end", () => {
|
|
274
|
+
output.end();
|
|
275
|
+
console.log("CSV created");
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Best Practices
|
|
283
|
+
|
|
284
|
+
### 1. Error Handling
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
async function safeExtract(filePath: string) {
|
|
288
|
+
try {
|
|
289
|
+
const buffer = await readFile(filePath);
|
|
290
|
+
|
|
291
|
+
// Validate PDF header
|
|
292
|
+
const header = buffer.slice(0, 5).toString();
|
|
293
|
+
if (header !== "%PDF-") {
|
|
294
|
+
throw new Error("Invalid PDF file");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const result = await parser.parse(buffer);
|
|
298
|
+
return result;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error.message.includes("password")) {
|
|
301
|
+
throw new Error("PDF is password protected");
|
|
302
|
+
}
|
|
303
|
+
if (error.message.includes("damaged")) {
|
|
304
|
+
throw new Error("PDF is corrupted");
|
|
305
|
+
}
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 2. Memory Management (Large Files)
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// For large PDFs, process page by page
|
|
315
|
+
async function extractLargePDF(pdfPath: string) {
|
|
316
|
+
const data = await readFile(pdfPath);
|
|
317
|
+
const pdfDocument = await pdfjsLib.getDocument({ data: new Uint8Array(data) }).promise;
|
|
318
|
+
|
|
319
|
+
// Don't load all pages at once
|
|
320
|
+
for (let i = 1; i <= pdfDocument.numPages; i++) {
|
|
321
|
+
const page = await pdfDocument.getPage(i);
|
|
322
|
+
const text = await page.getTextContent();
|
|
323
|
+
|
|
324
|
+
// Process immediately, don't accumulate
|
|
325
|
+
await processPageText(text);
|
|
326
|
+
|
|
327
|
+
// Clean up
|
|
328
|
+
page.cleanup();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 3. Text Cleaning
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
function cleanExtractedText(text: string): string {
|
|
337
|
+
return text
|
|
338
|
+
.replace(/\s+/g, " ") // Normalize whitespace
|
|
339
|
+
.replace(/[^\x20-\x7E\n]/g, "") // Remove non-printable chars
|
|
340
|
+
.trim();
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### 4. Performance Tips
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// Parallel extraction for multiple files
|
|
348
|
+
async function extractMultiple(files: string[]) {
|
|
349
|
+
const results = await Promise.all(
|
|
350
|
+
files.map((file) => extractText(file).catch((err) => ({ file, error: err }))),
|
|
351
|
+
);
|
|
352
|
+
return results;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Use streams for very large files
|
|
356
|
+
import { createReadStream } from "fs";
|
|
357
|
+
import { PdfDataReader } from "pdf-data-parser";
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Common Issues & Solutions
|
|
363
|
+
|
|
364
|
+
| Issue | Cause | Solution |
|
|
365
|
+
| -------------------- | --------------------- | ------------------------------------- |
|
|
366
|
+
| Text appears garbled | Encoding issue | Use pdfjs-dist with explicit encoding |
|
|
367
|
+
| Missing text | Scanned image PDF | Use OCR (Tesseract) before extraction |
|
|
368
|
+
| Out of memory | Large PDF | Stream processing, page-by-page |
|
|
369
|
+
| Password error | Encrypted PDF | Use `pdf-lib` to decrypt first |
|
|
370
|
+
| Missing coordinates | Wrong library | Use pdfjs-dist for positioning |
|
|
371
|
+
| Table structure lost | Plain text extraction | Use pdf-data-parser |
|
|
372
|
+
| Font warnings | Missing fonts | Set `standardFontDataUrl` option |
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Complete Example: Document Processor
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { PDFParse } from "pdf-parse";
|
|
380
|
+
import { readFile } from "fs/promises";
|
|
381
|
+
|
|
382
|
+
interface DocumentResult {
|
|
383
|
+
text: string;
|
|
384
|
+
metadata: {
|
|
385
|
+
title?: string;
|
|
386
|
+
author?: string;
|
|
387
|
+
pages: number;
|
|
388
|
+
creationDate?: Date;
|
|
389
|
+
};
|
|
390
|
+
summary: string;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function processDocument(filePath: string): Promise<DocumentResult> {
|
|
394
|
+
const parser = new PDFParse();
|
|
395
|
+
const buffer = await readFile(filePath);
|
|
396
|
+
|
|
397
|
+
const result = await parser.parse(buffer);
|
|
398
|
+
|
|
399
|
+
// Generate summary (first 500 chars)
|
|
400
|
+
const summary = result.text.replace(/\s+/g, " ").slice(0, 500).trim() + "...";
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
text: result.text,
|
|
404
|
+
metadata: {
|
|
405
|
+
title: result.info?.Title,
|
|
406
|
+
author: result.info?.Author,
|
|
407
|
+
pages: result.numpages,
|
|
408
|
+
creationDate: result.info?.CreationDate ? new Date(result.info.CreationDate) : undefined,
|
|
409
|
+
},
|
|
410
|
+
summary,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Usage
|
|
415
|
+
const doc = await processDocument("./report.pdf");
|
|
416
|
+
console.log(`Document: ${doc.metadata.title}`);
|
|
417
|
+
console.log(`Pages: ${doc.metadata.pages}`);
|
|
418
|
+
console.log(`Summary: ${doc.summary}`);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## References
|
|
424
|
+
|
|
425
|
+
- [pdf-parse npm](https://www.npmjs.com/package/pdf-parse)
|
|
426
|
+
- [pdfjs-dist docs](https://mozilla.github.io/pdf.js/)
|
|
427
|
+
- [pdf-data-parser GitHub](https://github.com/drewletcher/pdf-data-parser)
|
|
428
|
+
- [pdf-lib GitHub](https://github.com/Hopding/pdf-lib)
|
|
@@ -1,112 +1,310 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: playwright
|
|
3
|
-
description: Browser automation
|
|
3
|
+
description: Browser automation for testing, screenshots, form validation, and UX verification. Uses Playwright CLI for token-efficient automation, with MCP fallback for complex exploratory workflows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Playwright Browser Automation
|
|
6
|
+
# Playwright Browser Automation
|
|
7
7
|
|
|
8
|
-
Browser automation via Playwright MCP
|
|
8
|
+
Browser automation via **Playwright CLI** (primary) and **Playwright MCP** (fallback for complex workflows).
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Quick Decision
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- `browser_wait_for` - Wait for text or selector
|
|
19
|
-
- `browser_resize` - Resize viewport or emulate device
|
|
12
|
+
| Scenario | Use |
|
|
13
|
+
| --------------------------------------------------- | ------- |
|
|
14
|
+
| Quick screenshots, simple forms, token efficiency | **CLI** |
|
|
15
|
+
| Complex exploratory testing, self-healing workflows | **MCP** |
|
|
16
|
+
|
|
17
|
+
---
|
|
20
18
|
|
|
21
|
-
##
|
|
19
|
+
## CLI Mode (Recommended)
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
2. **Snapshot** to see page structure and element refs using `browser_snapshot`
|
|
25
|
-
3. **Interact** using element refs from snapshot (`browser_click`, `browser_fill`)
|
|
26
|
-
4. **Screenshot** to capture results using `browser_take_screenshot`
|
|
21
|
+
The CLI approach is **token-efficient** - no large schemas or verbose accessibility trees in context. Best for most automation tasks.
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
### Installation
|
|
29
24
|
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @playwright/cli@latest
|
|
27
|
+
playwright-cli install --skills # Optional: install for Claude/Copilot
|
|
30
28
|
```
|
|
31
|
-
# Navigate to page
|
|
32
|
-
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "https://example.com"}')
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
skill_mcp(skill_name="playwright", tool_name="browser_snapshot")
|
|
30
|
+
### Core Workflow
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
```
|
|
33
|
+
# 1. Open browser and navigate
|
|
34
|
+
bash({ command: "playwright-cli open https://example.com" })
|
|
35
|
+
|
|
36
|
+
# 2. Get element refs (snapshot)
|
|
37
|
+
bash({ command: "playwright-cli snapshot" })
|
|
39
38
|
|
|
40
|
-
#
|
|
41
|
-
|
|
39
|
+
# 3. Interact using refs
|
|
40
|
+
bash({ command: "playwright-cli fill e12 'test@example.com'" })
|
|
41
|
+
bash({ command: "playwright-cli click e34" })
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
|
|
43
|
+
# 4. Screenshot
|
|
44
|
+
bash({ command: "playwright-cli screenshot --filename=/tmp/result.png" })
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
### Commands Reference
|
|
48
|
+
|
|
49
|
+
#### Navigation
|
|
50
|
+
|
|
51
|
+
| Command | Description |
|
|
52
|
+
| ------------ | ---------------------------------------- |
|
|
53
|
+
| `open [url]` | Open browser, optionally navigate to URL |
|
|
54
|
+
| `goto <url>` | Navigate to URL |
|
|
55
|
+
| `close` | Close the page/browser |
|
|
56
|
+
| `go-back` | Go back to previous page |
|
|
57
|
+
| `go-forward` | Go forward to next page |
|
|
58
|
+
| `reload` | Reload current page |
|
|
59
|
+
|
|
60
|
+
#### Interaction
|
|
61
|
+
|
|
62
|
+
| Command | Description |
|
|
63
|
+
| -------------------------- | ----------------------------------------- |
|
|
64
|
+
| `snapshot` | Capture page snapshot to get element refs |
|
|
65
|
+
| `type <text>` | Type text into focused element |
|
|
66
|
+
| `fill <ref> <text>` | Fill text into specific element |
|
|
67
|
+
| `click <ref> [button]` | Click element (left/right/middle) |
|
|
68
|
+
| `dblclick <ref>` | Double-click element |
|
|
69
|
+
| `hover <ref>` | Hover over element |
|
|
70
|
+
| `drag <startRef> <endRef>` | Drag and drop |
|
|
71
|
+
| `select <ref> <value>` | Select dropdown option |
|
|
72
|
+
| `check <ref>` | Check checkbox/radio |
|
|
73
|
+
| `uncheck <ref>` | Uncheck checkbox |
|
|
74
|
+
| `upload <file>` | Upload file(s) |
|
|
75
|
+
|
|
76
|
+
#### Keyboard
|
|
77
|
+
|
|
78
|
+
| Command | Description |
|
|
79
|
+
| --------------- | -------------------------------------- |
|
|
80
|
+
| `press <key>` | Press key (e.g., `Enter`, `ArrowLeft`) |
|
|
81
|
+
| `keydown <key>` | Hold key down |
|
|
82
|
+
| `keyup <key>` | Release key |
|
|
48
83
|
|
|
49
|
-
|
|
84
|
+
#### Screenshots & PDF
|
|
50
85
|
|
|
86
|
+
| Command | Description |
|
|
87
|
+
| ------------------------------------ | -------------------------- |
|
|
88
|
+
| `screenshot [ref]` | Screenshot page or element |
|
|
89
|
+
| `screenshot --filename=/tmp/out.png` | Save to specific path |
|
|
90
|
+
| `pdf` | Save page as PDF |
|
|
91
|
+
| `pdf --filename=page.pdf` | Save PDF to specific path |
|
|
92
|
+
|
|
93
|
+
#### Tabs
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
| -------------------- | -------------- |
|
|
97
|
+
| `tab-list` | List all tabs |
|
|
98
|
+
| `tab-new [url]` | Create new tab |
|
|
99
|
+
| `tab-close [index]` | Close tab |
|
|
100
|
+
| `tab-select <index>` | Switch to tab |
|
|
101
|
+
|
|
102
|
+
#### Storage
|
|
103
|
+
|
|
104
|
+
| Command | Description |
|
|
105
|
+
| -------------------------------- | ------------------------------- |
|
|
106
|
+
| `cookie-list` | List cookies |
|
|
107
|
+
| `cookie-get <name>` | Get cookie value |
|
|
108
|
+
| `cookie-set <name> <value>` | Set cookie |
|
|
109
|
+
| `cookie-delete <name>` | Delete cookie |
|
|
110
|
+
| `cookie-clear` | Clear all cookies |
|
|
111
|
+
| `localstorage-list` | List localStorage |
|
|
112
|
+
| `localstorage-get <key>` | Get localStorage value |
|
|
113
|
+
| `localstorage-set <key> <value>` | Set localStorage |
|
|
114
|
+
| `sessionstorage-*` | Same pattern for sessionStorage |
|
|
115
|
+
|
|
116
|
+
#### Network
|
|
117
|
+
|
|
118
|
+
| Command | Description |
|
|
119
|
+
| ------------------- | --------------------- |
|
|
120
|
+
| `route <pattern>` | Mock network requests |
|
|
121
|
+
| `route-list` | List active routes |
|
|
122
|
+
| `unroute [pattern]` | Remove route(s) |
|
|
123
|
+
|
|
124
|
+
#### DevTools
|
|
125
|
+
|
|
126
|
+
| Command | Description |
|
|
127
|
+
| ----------------------- | ----------------------------------- |
|
|
128
|
+
| `console [min-level]` | List console messages |
|
|
129
|
+
| `network` | List network requests |
|
|
130
|
+
| `eval <func> [ref]` | Evaluate JavaScript on page/element |
|
|
131
|
+
| `run-code <code>` | Run Playwright code snippet |
|
|
132
|
+
| `tracing-start` | Start trace recording |
|
|
133
|
+
| `tracing-stop` | Stop trace recording |
|
|
134
|
+
| `video-start` | Start video recording |
|
|
135
|
+
| `video-stop [filename]` | Stop video recording |
|
|
136
|
+
|
|
137
|
+
### CLI Options
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Headless mode
|
|
141
|
+
playwright-cli open https://example.com --headless
|
|
142
|
+
|
|
143
|
+
# Choose browser
|
|
144
|
+
playwright-cli open https://example.com --browser=firefox
|
|
145
|
+
|
|
146
|
+
# Emulate device
|
|
147
|
+
playwright-cli open https://example.com --device="iPhone 14"
|
|
148
|
+
|
|
149
|
+
# Named session (isolated browser)
|
|
150
|
+
playwright-cli -s=project1 open https://example.com
|
|
151
|
+
|
|
152
|
+
# See all options
|
|
153
|
+
playwright-cli --help
|
|
51
154
|
```
|
|
52
|
-
# Navigate
|
|
53
|
-
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "http://localhost:3000"}')
|
|
54
155
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
156
|
+
### CLI Examples
|
|
157
|
+
|
|
158
|
+
#### Test Responsive Design
|
|
58
159
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
// Desktop
|
|
162
|
+
bash({ command: "playwright-cli open http://localhost:3000" });
|
|
163
|
+
bash({ command: "playwright-cli resize 1920 1080" });
|
|
164
|
+
bash({ command: "playwright-cli screenshot --filename=/tmp/desktop.png" });
|
|
165
|
+
|
|
166
|
+
// Mobile
|
|
167
|
+
bash({ command: "playwright-cli resize 390 844" }); // iPhone 14
|
|
168
|
+
bash({ command: "playwright-cli screenshot --filename=/tmp/mobile.png" });
|
|
62
169
|
```
|
|
63
170
|
|
|
64
|
-
|
|
171
|
+
#### Fill a Form
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
bash({ command: "playwright-cli open http://localhost:3000/contact" });
|
|
175
|
+
|
|
176
|
+
// Get snapshot to see element refs
|
|
177
|
+
bash({ command: "playwright-cli snapshot" });
|
|
178
|
+
|
|
179
|
+
// Fill using refs from snapshot output
|
|
180
|
+
bash({ command: "playwright-cli fill e12 'John Doe'" });
|
|
181
|
+
bash({ command: "playwright-cli fill e34 'john@example.com'" });
|
|
182
|
+
bash({ command: "playwright-cli click e56" }); // Submit button
|
|
65
183
|
|
|
184
|
+
// Wait for confirmation
|
|
185
|
+
bash({ command: "playwright-cli eval 'document.body.innerText.includes(\"Thank you\")'" });
|
|
66
186
|
```
|
|
67
|
-
# Navigate to form
|
|
68
|
-
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "http://localhost:3000/contact"}')
|
|
69
187
|
|
|
70
|
-
|
|
71
|
-
skill_mcp(skill_name="playwright", tool_name="browser_snapshot")
|
|
188
|
+
#### Multi-Step Login Flow
|
|
72
189
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
190
|
+
```typescript
|
|
191
|
+
bash({ command: "playwright-cli open https://app.example.com/login" });
|
|
192
|
+
bash({ command: "playwright-cli snapshot" });
|
|
76
193
|
|
|
77
|
-
|
|
78
|
-
|
|
194
|
+
bash({ command: "playwright-cli fill e10 'username'" });
|
|
195
|
+
bash({ command: "playwright-cli fill e12 'password'" });
|
|
196
|
+
bash({ command: "playwright-cli click e15" });
|
|
79
197
|
|
|
80
|
-
|
|
81
|
-
|
|
198
|
+
// Verify logged in
|
|
199
|
+
bash({ command: "playwright-cli eval 'document.querySelector(\".dashboard\") !== null'" });
|
|
200
|
+
bash({ command: "playwright-cli screenshot --filename=/tmp/logged-in.png" });
|
|
82
201
|
```
|
|
83
202
|
|
|
84
|
-
|
|
203
|
+
#### Session Management
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// List all browser sessions
|
|
207
|
+
bash({ command: "playwright-cli list" });
|
|
208
|
+
|
|
209
|
+
// Use named session
|
|
210
|
+
bash({ command: "playwright-cli -s=project1 open https://example.com" });
|
|
211
|
+
|
|
212
|
+
// Close specific session
|
|
213
|
+
bash({ command: "playwright-cli -s=project1 close" });
|
|
214
|
+
|
|
215
|
+
// Close all browsers
|
|
216
|
+
bash({ command: "playwright-cli close-all" });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## MCP Mode (Fallback)
|
|
222
|
+
|
|
223
|
+
Use MCP for complex exploratory workflows or when you need persistent browser state with rich introspection.
|
|
224
|
+
|
|
225
|
+
### Tools (8 Essential)
|
|
226
|
+
|
|
227
|
+
- `browser_navigate` - Navigate to URL
|
|
228
|
+
- `browser_snapshot` - Get accessibility snapshot with element refs
|
|
229
|
+
- `browser_take_screenshot` - Capture screenshot
|
|
230
|
+
- `browser_click` - Click element by ref
|
|
231
|
+
- `browser_type` - Type text (appends)
|
|
232
|
+
- `browser_fill` - Fill input (clears first, then types)
|
|
233
|
+
- `browser_wait_for` - Wait for text or selector
|
|
234
|
+
- `browser_resize` - Resize viewport or emulate device
|
|
85
235
|
|
|
86
|
-
|
|
87
|
-
- **Use descriptive element names** in click/fill for clarity
|
|
88
|
-
- **Save screenshots to /tmp** for easy access
|
|
89
|
-
- **Use device presets** for accurate mobile emulation
|
|
90
|
-
- **Chain wait_for** after navigation for dynamic pages
|
|
236
|
+
### MCP Workflow
|
|
91
237
|
|
|
92
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
// Navigate
|
|
240
|
+
skill_mcp(
|
|
241
|
+
(skill_name = "playwright"),
|
|
242
|
+
(tool_name = "browser_navigate"),
|
|
243
|
+
(arguments = '{"url": "https://example.com"}'),
|
|
244
|
+
);
|
|
93
245
|
|
|
94
|
-
|
|
246
|
+
// Get element refs
|
|
247
|
+
skill_mcp((skill_name = "playwright"), (tool_name = "browser_snapshot"));
|
|
248
|
+
|
|
249
|
+
// Interact
|
|
250
|
+
skill_mcp(
|
|
251
|
+
(skill_name = "playwright"),
|
|
252
|
+
(tool_name = "browser_click"),
|
|
253
|
+
(arguments = '{"element": "Submit", "ref": "e12"}'),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Screenshot
|
|
257
|
+
skill_mcp(
|
|
258
|
+
(skill_name = "playwright"),
|
|
259
|
+
(tool_name = "browser_take_screenshot"),
|
|
260
|
+
(arguments = '{"filename": "/tmp/result.png"}'),
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### MCP Configuration
|
|
95
265
|
|
|
96
266
|
```json
|
|
97
267
|
{
|
|
98
268
|
"playwright": {
|
|
99
269
|
"command": "npx",
|
|
100
|
-
"args": ["@playwright/mcp@latest"
|
|
101
|
-
"includeTools": [
|
|
270
|
+
"args": ["@playwright/mcp@latest"],
|
|
271
|
+
"includeTools": [
|
|
272
|
+
"browser_navigate",
|
|
273
|
+
"browser_snapshot",
|
|
274
|
+
"browser_take_screenshot",
|
|
275
|
+
"browser_click",
|
|
276
|
+
"browser_type",
|
|
277
|
+
"browser_fill",
|
|
278
|
+
"browser_wait_for",
|
|
279
|
+
"browser_resize"
|
|
280
|
+
]
|
|
102
281
|
}
|
|
103
282
|
}
|
|
104
283
|
```
|
|
105
284
|
|
|
106
|
-
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Best Practices
|
|
288
|
+
|
|
289
|
+
1. **Default to CLI** for token efficiency
|
|
290
|
+
2. **Snapshot before interact** - always get element refs first
|
|
291
|
+
3. **Use named sessions** (`-s=`) for isolated browser contexts
|
|
292
|
+
4. **Save outputs to /tmp** for easy access and cleanup
|
|
293
|
+
5. **Check console/network** for debugging: `playwright-cli console error`
|
|
294
|
+
6. **Use eval for custom checks** when built-in commands aren't enough
|
|
295
|
+
|
|
296
|
+
## Troubleshooting
|
|
297
|
+
|
|
298
|
+
| Issue | Solution |
|
|
299
|
+
| ------------------- | ------------------------------------------------ |
|
|
300
|
+
| Element not found | Re-run snapshot to get fresh refs |
|
|
301
|
+
| Page not loading | Check network with `playwright-cli network` |
|
|
302
|
+
| Timing issues | Use `eval` to check conditions before proceeding |
|
|
303
|
+
| Session conflicts | Use named sessions (`-s=project1`) |
|
|
304
|
+
| Browser won't close | `playwright-cli kill-all` as last resort |
|
|
107
305
|
|
|
108
|
-
|
|
109
|
-
- `--browser=chrome|firefox|webkit` - Choose browser
|
|
110
|
-
- `--device="iPhone 13"` - Emulate device
|
|
306
|
+
## References
|
|
111
307
|
|
|
112
|
-
|
|
308
|
+
- [Playwright CLI GitHub](https://github.com/microsoft/playwright-cli)
|
|
309
|
+
- [Playwright MCP GitHub](https://github.com/microsoft/playwright-mcp)
|
|
310
|
+
- [Playwright Docs](https://playwright.dev)
|
package/package.json
CHANGED