@assertrx/dino 1.0.0
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/README.md +293 -0
- package/extensions/dino.ts +70 -0
- package/extensions/mabl-mcp.ts +306 -0
- package/extensions/playwright-mcp.ts +142 -0
- package/package.json +47 -0
- package/prompts/create-pr.md +110 -0
- package/prompts/generate-bdd-from-url.md +65 -0
- package/prompts/generate-pom-from-url-cli.md +48 -0
- package/prompts/generate-pom-from-url.md +45 -0
- package/prompts/generate-spec-from-feature.md +64 -0
- package/prompts/heal-playwright-failures.md +106 -0
- package/prompts/setup-playwright.md +81 -0
- package/scripts/start-dino.cmd +8 -0
- package/scripts/start-dino.mjs +226 -0
- package/scripts/start-dino.ps1 +9 -0
- package/scripts/start-dino.sh +10 -0
- package/skills/acli/SKILL.md +292 -0
- package/skills/bdd-gherkin/SKILL.md +552 -0
- package/skills/build-review-interface/SKILL.md +96 -0
- package/skills/error-analysis/SKILL.md +164 -0
- package/skills/eval-audit/SKILL.md +183 -0
- package/skills/evaluate-rag/SKILL.md +177 -0
- package/skills/generate-synthetic-data/SKILL.md +131 -0
- package/skills/mabl-mcp/SKILL.md +79 -0
- package/skills/playwright-cli/SKILL.md +246 -0
- package/skills/playwright-cli/advanced-workflows.md +679 -0
- package/skills/playwright-cli/core-commands.md +375 -0
- package/skills/playwright-cli/device-emulation.md +475 -0
- package/skills/playwright-cli/request-mocking.md +373 -0
- package/skills/playwright-cli/running-custom-code.md +553 -0
- package/skills/playwright-cli/screenshots-and-media.md +457 -0
- package/skills/playwright-cli/session-management.md +425 -0
- package/skills/playwright-cli/storage-and-auth.md +487 -0
- package/skills/playwright-cli/test-generation.md +336 -0
- package/skills/playwright-cli/tracing-and-debugging.md +501 -0
- package/skills/playwright-core/SKILL.md +101 -0
- package/skills/playwright-core/accessibility.md +1517 -0
- package/skills/playwright-core/angular.md +1086 -0
- package/skills/playwright-core/api-testing.md +1623 -0
- package/skills/playwright-core/assertions-and-waiting.md +703 -0
- package/skills/playwright-core/auth-flows.md +1081 -0
- package/skills/playwright-core/authentication.md +1409 -0
- package/skills/playwright-core/browser-apis.md +673 -0
- package/skills/playwright-core/browser-extensions.md +319 -0
- package/skills/playwright-core/canvas-and-webgl.md +494 -0
- package/skills/playwright-core/clock-and-time-mocking.md +427 -0
- package/skills/playwright-core/common-pitfalls.md +1318 -0
- package/skills/playwright-core/component-testing.md +1179 -0
- package/skills/playwright-core/configuration.md +731 -0
- package/skills/playwright-core/crud-testing.md +945 -0
- package/skills/playwright-core/debugging.md +827 -0
- package/skills/playwright-core/drag-and-drop.md +992 -0
- package/skills/playwright-core/electron-testing.md +622 -0
- package/skills/playwright-core/error-and-edge-cases.md +1181 -0
- package/skills/playwright-core/error-index.md +1903 -0
- package/skills/playwright-core/file-operations.md +749 -0
- package/skills/playwright-core/file-upload-download.md +1012 -0
- package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
- package/skills/playwright-core/flaky-tests.md +883 -0
- package/skills/playwright-core/forms-and-validation.md +1056 -0
- package/skills/playwright-core/i18n-and-localization.md +622 -0
- package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
- package/skills/playwright-core/locator-strategy.md +588 -0
- package/skills/playwright-core/locators.md +743 -0
- package/skills/playwright-core/mobile-and-responsive.md +1669 -0
- package/skills/playwright-core/multi-context-and-popups.md +581 -0
- package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
- package/skills/playwright-core/network-mocking.md +1395 -0
- package/skills/playwright-core/nextjs.md +1013 -0
- package/skills/playwright-core/performance-testing.md +608 -0
- package/skills/playwright-core/react.md +1082 -0
- package/skills/playwright-core/search-and-filter.md +1366 -0
- package/skills/playwright-core/security-testing.md +624 -0
- package/skills/playwright-core/service-workers-and-pwa.md +524 -0
- package/skills/playwright-core/test-architecture.md +569 -0
- package/skills/playwright-core/test-data-management.md +1209 -0
- package/skills/playwright-core/test-organization.md +947 -0
- package/skills/playwright-core/third-party-integrations.md +754 -0
- package/skills/playwright-core/trace-analysis.md +161 -0
- package/skills/playwright-core/visual-regression.md +1027 -0
- package/skills/playwright-core/vue.md +1162 -0
- package/skills/playwright-core/websockets-and-realtime.md +623 -0
- package/skills/playwright-core/when-to-mock.md +827 -0
- package/skills/playwright-pom/SKILL.md +94 -0
- package/skills/playwright-pom/page-object-model.md +921 -0
- package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
- package/skills/validate-evaluator/SKILL.md +215 -0
- package/skills/write-judge-prompt/SKILL.md +144 -0
- package/themes/dino.json +66 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
# Advanced Workflows
|
|
2
|
+
|
|
3
|
+
> **When to use**: Complex multi-step automation scenarios — multi-page scraping, popup and new window handling, accessibility auditing, file downloads, authentication flows with OAuth, infinite scroll extraction, form wizard automation, and combining multiple CLI features together.
|
|
4
|
+
> **Prerequisites**: [core-commands.md](core-commands.md), [running-custom-code.md](running-custom-code.md), [session-management.md](session-management.md)
|
|
5
|
+
|
|
6
|
+
## Quick Reference
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# Multi-page scraping
|
|
10
|
+
playwright-cli run-code "async page => {
|
|
11
|
+
const data = [];
|
|
12
|
+
for (let i = 1; i <= 5; i++) {
|
|
13
|
+
await page.goto(\`https://example.com/page/\${i}\`);
|
|
14
|
+
const items = await page.locator('.item').allTextContents();
|
|
15
|
+
data.push(...items);
|
|
16
|
+
}
|
|
17
|
+
return data;
|
|
18
|
+
}"
|
|
19
|
+
|
|
20
|
+
# Handle popup window
|
|
21
|
+
playwright-cli run-code "async page => {
|
|
22
|
+
const [popup] = await Promise.all([
|
|
23
|
+
page.waitForEvent('popup'),
|
|
24
|
+
page.click('a[target=_blank]')
|
|
25
|
+
]);
|
|
26
|
+
return await popup.title();
|
|
27
|
+
}"
|
|
28
|
+
|
|
29
|
+
# Accessibility snapshot
|
|
30
|
+
playwright-cli run-code "async page => {
|
|
31
|
+
return await page.accessibility.snapshot();
|
|
32
|
+
}"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Popup and New Window Handling
|
|
36
|
+
|
|
37
|
+
When clicking links that open new windows or popups:
|
|
38
|
+
|
|
39
|
+
### Capture Popup Content
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
playwright-cli run-code "async page => {
|
|
43
|
+
const [popup] = await Promise.all([
|
|
44
|
+
page.waitForEvent('popup'),
|
|
45
|
+
page.click('a[target=_blank]')
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
await popup.waitForLoadState();
|
|
49
|
+
const title = await popup.title();
|
|
50
|
+
const url = popup.url();
|
|
51
|
+
|
|
52
|
+
return { title, url };
|
|
53
|
+
}"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Interact with Popup
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
playwright-cli run-code "async page => {
|
|
60
|
+
const [popup] = await Promise.all([
|
|
61
|
+
page.waitForEvent('popup'),
|
|
62
|
+
page.click('#open-settings')
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
// Fill a form in the popup
|
|
66
|
+
await popup.fill('input[name=email]', 'user@example.com');
|
|
67
|
+
await popup.click('button:text(\"Save\")');
|
|
68
|
+
|
|
69
|
+
// Wait for popup to close
|
|
70
|
+
await popup.waitForEvent('close');
|
|
71
|
+
return 'Popup handled';
|
|
72
|
+
}"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### OAuth Login with Popup
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
playwright-cli run-code "async page => {
|
|
79
|
+
await page.goto('https://app.example.com/login');
|
|
80
|
+
|
|
81
|
+
const [popup] = await Promise.all([
|
|
82
|
+
page.waitForEvent('popup'),
|
|
83
|
+
page.click('button:text(\"Sign in with Google\")')
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
// Handle Google OAuth in the popup
|
|
87
|
+
await popup.fill('input[type=email]', 'user@gmail.com');
|
|
88
|
+
await popup.click('#identifierNext');
|
|
89
|
+
await popup.waitForSelector('input[type=password]', { state: 'visible' });
|
|
90
|
+
await popup.fill('input[type=password]', 'password');
|
|
91
|
+
await popup.click('#passwordNext');
|
|
92
|
+
|
|
93
|
+
// Popup closes after auth, main page redirects
|
|
94
|
+
await popup.waitForEvent('close');
|
|
95
|
+
await page.waitForURL('**/dashboard');
|
|
96
|
+
|
|
97
|
+
return 'OAuth login complete: ' + page.url();
|
|
98
|
+
}"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Multi-Page Navigation and Scraping
|
|
102
|
+
|
|
103
|
+
### Paginated Data Extraction
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
playwright-cli run-code "async page => {
|
|
107
|
+
await page.goto('https://example.com/products');
|
|
108
|
+
const allProducts = [];
|
|
109
|
+
|
|
110
|
+
while (true) {
|
|
111
|
+
// Extract data from current page
|
|
112
|
+
const products = await page.locator('.product-card').evaluateAll(cards =>
|
|
113
|
+
cards.map(card => ({
|
|
114
|
+
name: card.querySelector('.name')?.textContent?.trim(),
|
|
115
|
+
price: card.querySelector('.price')?.textContent?.trim(),
|
|
116
|
+
rating: card.querySelector('.rating')?.getAttribute('data-score')
|
|
117
|
+
}))
|
|
118
|
+
);
|
|
119
|
+
allProducts.push(...products);
|
|
120
|
+
|
|
121
|
+
// Check for next page
|
|
122
|
+
const nextBtn = page.locator('a.next-page');
|
|
123
|
+
if (await nextBtn.isVisible()) {
|
|
124
|
+
await nextBtn.click();
|
|
125
|
+
await page.waitForLoadState('networkidle');
|
|
126
|
+
} else {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { total: allProducts.length, products: allProducts };
|
|
132
|
+
}"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Infinite Scroll Extraction
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
playwright-cli run-code "async page => {
|
|
139
|
+
await page.goto('https://example.com/feed');
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
const items = [];
|
|
142
|
+
const maxItems = 100;
|
|
143
|
+
|
|
144
|
+
while (items.length < maxItems) {
|
|
145
|
+
// Collect visible items
|
|
146
|
+
const newItems = await page.locator('.feed-item').evaluateAll(
|
|
147
|
+
els => els.map(el => ({
|
|
148
|
+
id: el.getAttribute('data-id'),
|
|
149
|
+
text: el.textContent.trim()
|
|
150
|
+
}))
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
for (const item of newItems) {
|
|
154
|
+
if (item.id && !seen.has(item.id)) {
|
|
155
|
+
seen.add(item.id);
|
|
156
|
+
items.push(item);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Scroll to bottom
|
|
161
|
+
const previousHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
162
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
163
|
+
await page.waitForTimeout(2000);
|
|
164
|
+
|
|
165
|
+
// Check if we've reached the end
|
|
166
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
167
|
+
if (newHeight === previousHeight) break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { collected: items.length, items: items.slice(0, maxItems) };
|
|
171
|
+
}"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Multi-Site Data Aggregation
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
#!/bin/bash
|
|
178
|
+
# Use named sessions for concurrent site scraping
|
|
179
|
+
|
|
180
|
+
playwright-cli -s=site1 open https://news.example.com &
|
|
181
|
+
playwright-cli -s=site2 open https://blog.example.com &
|
|
182
|
+
playwright-cli -s=site3 open https://docs.example.com &
|
|
183
|
+
wait
|
|
184
|
+
|
|
185
|
+
# Extract headlines from each
|
|
186
|
+
playwright-cli -s=site1 run-code "async page => {
|
|
187
|
+
return await page.locator('h2.headline').allTextContents();
|
|
188
|
+
}"
|
|
189
|
+
|
|
190
|
+
playwright-cli -s=site2 run-code "async page => {
|
|
191
|
+
return await page.locator('.post-title').allTextContents();
|
|
192
|
+
}"
|
|
193
|
+
|
|
194
|
+
playwright-cli -s=site3 run-code "async page => {
|
|
195
|
+
return await page.locator('.doc-title').allTextContents();
|
|
196
|
+
}"
|
|
197
|
+
|
|
198
|
+
playwright-cli close-all
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## File Upload and Download
|
|
202
|
+
|
|
203
|
+
### Download Files
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
playwright-cli run-code "async page => {
|
|
207
|
+
const [download] = await Promise.all([
|
|
208
|
+
page.waitForEvent('download'),
|
|
209
|
+
page.click('a.download-link')
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
const filename = download.suggestedFilename();
|
|
213
|
+
await download.saveAs('./downloads/' + filename);
|
|
214
|
+
return {
|
|
215
|
+
filename,
|
|
216
|
+
url: download.url()
|
|
217
|
+
};
|
|
218
|
+
}"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Download Multiple Files
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
playwright-cli run-code "async page => {
|
|
225
|
+
const downloadLinks = await page.locator('a.download').all();
|
|
226
|
+
const files = [];
|
|
227
|
+
|
|
228
|
+
for (const link of downloadLinks) {
|
|
229
|
+
const [download] = await Promise.all([
|
|
230
|
+
page.waitForEvent('download'),
|
|
231
|
+
link.click()
|
|
232
|
+
]);
|
|
233
|
+
const name = download.suggestedFilename();
|
|
234
|
+
await download.saveAs('./downloads/' + name);
|
|
235
|
+
files.push(name);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return files;
|
|
239
|
+
}"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Upload Files
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Simple upload via ref
|
|
246
|
+
playwright-cli snapshot
|
|
247
|
+
playwright-cli upload e5 ./document.pdf
|
|
248
|
+
|
|
249
|
+
# Upload multiple files
|
|
250
|
+
playwright-cli upload e5 ./photo1.jpg ./photo2.jpg ./photo3.jpg
|
|
251
|
+
|
|
252
|
+
# Programmatic upload (for hidden file inputs)
|
|
253
|
+
playwright-cli run-code "async page => {
|
|
254
|
+
const fileInput = page.locator('input[type=file]');
|
|
255
|
+
await fileInput.setInputFiles('./report.pdf');
|
|
256
|
+
}"
|
|
257
|
+
|
|
258
|
+
# Upload multiple files programmatically
|
|
259
|
+
playwright-cli run-code "async page => {
|
|
260
|
+
const fileInput = page.locator('input[type=file]');
|
|
261
|
+
await fileInput.setInputFiles([
|
|
262
|
+
'./document1.pdf',
|
|
263
|
+
'./document2.pdf',
|
|
264
|
+
'./image.png'
|
|
265
|
+
]);
|
|
266
|
+
}"
|
|
267
|
+
|
|
268
|
+
# Clear file input
|
|
269
|
+
playwright-cli run-code "async page => {
|
|
270
|
+
await page.locator('input[type=file]').setInputFiles([]);
|
|
271
|
+
}"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Drag-and-Drop File Upload
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
playwright-cli run-code "async page => {
|
|
278
|
+
// Create a fake file for drag-and-drop zones
|
|
279
|
+
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
|
|
280
|
+
await page.dispatchEvent('.dropzone', 'drop', { dataTransfer });
|
|
281
|
+
}"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Accessibility Auditing
|
|
285
|
+
|
|
286
|
+
### Accessibility Tree Snapshot
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
playwright-cli run-code "async page => {
|
|
290
|
+
const snapshot = await page.accessibility.snapshot();
|
|
291
|
+
return JSON.stringify(snapshot, null, 2);
|
|
292
|
+
}"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Check for Missing ARIA Labels
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
playwright-cli run-code "async page => {
|
|
299
|
+
return await page.evaluate(() => {
|
|
300
|
+
const issues = [];
|
|
301
|
+
|
|
302
|
+
// Images without alt text
|
|
303
|
+
document.querySelectorAll('img:not([alt])').forEach(img => {
|
|
304
|
+
issues.push({ type: 'img-no-alt', src: img.src.substring(0, 50) });
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Buttons without accessible names
|
|
308
|
+
document.querySelectorAll('button').forEach(btn => {
|
|
309
|
+
if (!btn.textContent.trim() && !btn.getAttribute('aria-label')) {
|
|
310
|
+
issues.push({ type: 'button-no-name', html: btn.outerHTML.substring(0, 80) });
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Form inputs without labels
|
|
315
|
+
document.querySelectorAll('input:not([type=hidden])').forEach(input => {
|
|
316
|
+
const id = input.id;
|
|
317
|
+
const hasLabel = id && document.querySelector(\`label[for=\${id}]\`);
|
|
318
|
+
const hasAriaLabel = input.getAttribute('aria-label') || input.getAttribute('aria-labelledby');
|
|
319
|
+
if (!hasLabel && !hasAriaLabel) {
|
|
320
|
+
issues.push({ type: 'input-no-label', name: input.name, type: input.type });
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Links without text
|
|
325
|
+
document.querySelectorAll('a').forEach(link => {
|
|
326
|
+
if (!link.textContent.trim() && !link.getAttribute('aria-label')) {
|
|
327
|
+
issues.push({ type: 'link-no-text', href: link.href.substring(0, 50) });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return { issueCount: issues.length, issues };
|
|
332
|
+
});
|
|
333
|
+
}"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Color Contrast Check
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
playwright-cli run-code "async page => {
|
|
340
|
+
return await page.evaluate(() => {
|
|
341
|
+
const getContrast = (rgb1, rgb2) => {
|
|
342
|
+
const luminance = (r, g, b) => {
|
|
343
|
+
const [rs, gs, bs] = [r, g, b].map(c => {
|
|
344
|
+
c = c / 255;
|
|
345
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
346
|
+
});
|
|
347
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
348
|
+
};
|
|
349
|
+
const l1 = luminance(...rgb1);
|
|
350
|
+
const l2 = luminance(...rgb2);
|
|
351
|
+
const lighter = Math.max(l1, l2);
|
|
352
|
+
const darker = Math.min(l1, l2);
|
|
353
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const parseRGB = (str) => {
|
|
357
|
+
const match = str.match(/\d+/g);
|
|
358
|
+
return match ? match.slice(0, 3).map(Number) : [0, 0, 0];
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const lowContrast = [];
|
|
362
|
+
document.querySelectorAll('p, span, a, button, label, h1, h2, h3, h4, h5, h6').forEach(el => {
|
|
363
|
+
const style = window.getComputedStyle(el);
|
|
364
|
+
const fg = parseRGB(style.color);
|
|
365
|
+
const bg = parseRGB(style.backgroundColor);
|
|
366
|
+
const ratio = getContrast(fg, bg);
|
|
367
|
+
if (ratio < 4.5) {
|
|
368
|
+
lowContrast.push({
|
|
369
|
+
text: el.textContent.trim().substring(0, 30),
|
|
370
|
+
ratio: ratio.toFixed(2),
|
|
371
|
+
fg: style.color,
|
|
372
|
+
bg: style.backgroundColor
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return { lowContrastCount: lowContrast.length, elements: lowContrast.slice(0, 20) };
|
|
378
|
+
});
|
|
379
|
+
}"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Tab Order Verification
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
playwright-cli run-code "async page => {
|
|
386
|
+
return await page.evaluate(() => {
|
|
387
|
+
const focusable = Array.from(document.querySelectorAll(
|
|
388
|
+
'a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'
|
|
389
|
+
));
|
|
390
|
+
return focusable.map((el, i) => ({
|
|
391
|
+
order: i + 1,
|
|
392
|
+
tag: el.tagName.toLowerCase(),
|
|
393
|
+
text: (el.textContent || el.getAttribute('aria-label') || el.name || '').trim().substring(0, 40),
|
|
394
|
+
tabIndex: el.tabIndex
|
|
395
|
+
}));
|
|
396
|
+
});
|
|
397
|
+
}"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Form Wizard Automation
|
|
401
|
+
|
|
402
|
+
### Multi-Step Form
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
# Step 1: Personal Info
|
|
406
|
+
playwright-cli open https://example.com/apply
|
|
407
|
+
playwright-cli snapshot
|
|
408
|
+
playwright-cli fill e1 "Jane" # First name
|
|
409
|
+
playwright-cli fill e2 "Doe" # Last name
|
|
410
|
+
playwright-cli fill e3 "jane@example.com" # Email
|
|
411
|
+
playwright-cli fill e4 "+1-555-0123" # Phone
|
|
412
|
+
playwright-cli click e5 # Next
|
|
413
|
+
|
|
414
|
+
# Step 2: Address
|
|
415
|
+
playwright-cli snapshot
|
|
416
|
+
playwright-cli fill e1 "123 Main Street"
|
|
417
|
+
playwright-cli fill e2 "Apt 4B"
|
|
418
|
+
playwright-cli fill e3 "Springfield"
|
|
419
|
+
playwright-cli select e4 "IL"
|
|
420
|
+
playwright-cli fill e5 "62701"
|
|
421
|
+
playwright-cli click e6 # Next
|
|
422
|
+
|
|
423
|
+
# Step 3: Upload Documents
|
|
424
|
+
playwright-cli snapshot
|
|
425
|
+
playwright-cli upload e1 ./resume.pdf
|
|
426
|
+
playwright-cli upload e2 ./cover-letter.pdf
|
|
427
|
+
playwright-cli click e3 # Next
|
|
428
|
+
|
|
429
|
+
# Step 4: Review and Submit
|
|
430
|
+
playwright-cli snapshot
|
|
431
|
+
playwright-cli screenshot --filename=review-page.png
|
|
432
|
+
playwright-cli check e1 # Accept terms
|
|
433
|
+
playwright-cli click e2 # Submit
|
|
434
|
+
playwright-cli snapshot # Confirmation page
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Dynamic Form with Conditional Fields
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
playwright-cli run-code "async page => {
|
|
441
|
+
await page.goto('https://example.com/registration');
|
|
442
|
+
|
|
443
|
+
// Select account type — this shows/hides fields
|
|
444
|
+
await page.selectOption('#account-type', 'business');
|
|
445
|
+
|
|
446
|
+
// Wait for business fields to appear
|
|
447
|
+
await page.waitForSelector('#company-name', { state: 'visible' });
|
|
448
|
+
|
|
449
|
+
// Fill business-specific fields
|
|
450
|
+
await page.fill('#company-name', 'Acme Corp');
|
|
451
|
+
await page.fill('#tax-id', '12-3456789');
|
|
452
|
+
await page.fill('#company-size', '50-100');
|
|
453
|
+
|
|
454
|
+
// Fill common fields
|
|
455
|
+
await page.fill('#contact-name', 'Jane Doe');
|
|
456
|
+
await page.fill('#contact-email', 'jane@acme.com');
|
|
457
|
+
|
|
458
|
+
await page.click('button:text(\"Register\")');
|
|
459
|
+
await page.waitForURL('**/welcome');
|
|
460
|
+
|
|
461
|
+
return 'Registration complete';
|
|
462
|
+
}"
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Data Extraction Patterns
|
|
466
|
+
|
|
467
|
+
### Table Data Extraction
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
playwright-cli run-code "async page => {
|
|
471
|
+
await page.goto('https://example.com/reports');
|
|
472
|
+
|
|
473
|
+
const data = await page.evaluate(() => {
|
|
474
|
+
const table = document.querySelector('table.data-table');
|
|
475
|
+
const headers = Array.from(table.querySelectorAll('thead th'))
|
|
476
|
+
.map(th => th.textContent.trim());
|
|
477
|
+
const rows = Array.from(table.querySelectorAll('tbody tr'))
|
|
478
|
+
.map(tr => {
|
|
479
|
+
const cells = Array.from(tr.querySelectorAll('td'))
|
|
480
|
+
.map(td => td.textContent.trim());
|
|
481
|
+
return Object.fromEntries(headers.map((h, i) => [h, cells[i]]));
|
|
482
|
+
});
|
|
483
|
+
return { headers, rowCount: rows.length, rows };
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return JSON.stringify(data, null, 2);
|
|
487
|
+
}"
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Extract Structured Data (JSON-LD, Meta Tags)
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
playwright-cli run-code "async page => {
|
|
494
|
+
return await page.evaluate(() => {
|
|
495
|
+
// JSON-LD structured data
|
|
496
|
+
const jsonLd = Array.from(document.querySelectorAll('script[type=\"application/ld+json\"]'))
|
|
497
|
+
.map(s => JSON.parse(s.textContent));
|
|
498
|
+
|
|
499
|
+
// Open Graph meta tags
|
|
500
|
+
const og = {};
|
|
501
|
+
document.querySelectorAll('meta[property^=\"og:\"]').forEach(m => {
|
|
502
|
+
og[m.getAttribute('property')] = m.content;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Standard meta tags
|
|
506
|
+
const meta = {};
|
|
507
|
+
document.querySelectorAll('meta[name]').forEach(m => {
|
|
508
|
+
meta[m.name] = m.content;
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return { jsonLd, openGraph: og, meta };
|
|
512
|
+
});
|
|
513
|
+
}"
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Screenshot Every Link Target
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
playwright-cli run-code "async page => {
|
|
520
|
+
await page.goto('https://example.com');
|
|
521
|
+
const links = await page.locator('nav a').evaluateAll(
|
|
522
|
+
anchors => anchors.map(a => ({ text: a.textContent.trim(), href: a.href }))
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
for (const link of links) {
|
|
526
|
+
const safeName = link.text.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
527
|
+
await page.goto(link.href);
|
|
528
|
+
await page.waitForLoadState('networkidle');
|
|
529
|
+
await page.screenshot({ path: \`screenshots/\${safeName}.png\` });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return links.map(l => l.text);
|
|
533
|
+
}"
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## Error Recovery Patterns
|
|
537
|
+
|
|
538
|
+
### Retry on Failure
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
playwright-cli run-code "async page => {
|
|
542
|
+
const maxRetries = 3;
|
|
543
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
544
|
+
try {
|
|
545
|
+
await page.goto('https://flaky-site.example.com', { timeout: 15000 });
|
|
546
|
+
await page.waitForSelector('.content', { timeout: 5000 });
|
|
547
|
+
return 'Success on attempt ' + attempt;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
if (attempt === maxRetries) throw error;
|
|
550
|
+
console.log(\`Attempt \${attempt} failed, retrying...\`);
|
|
551
|
+
await page.waitForTimeout(2000 * attempt); // Exponential backoff
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}"
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Dismiss Cookie Banners Automatically
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
playwright-cli run-code "async page => {
|
|
561
|
+
// Try common cookie consent selectors
|
|
562
|
+
const selectors = [
|
|
563
|
+
'button:text(\"Accept\")',
|
|
564
|
+
'button:text(\"Accept All\")',
|
|
565
|
+
'button:text(\"Accept Cookies\")',
|
|
566
|
+
'button:text(\"I Agree\")',
|
|
567
|
+
'#cookie-accept',
|
|
568
|
+
'.cookie-consent-accept',
|
|
569
|
+
'[data-testid=cookie-accept]'
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
for (const sel of selectors) {
|
|
573
|
+
try {
|
|
574
|
+
const btn = page.locator(sel).first();
|
|
575
|
+
if (await btn.isVisible({ timeout: 1000 })) {
|
|
576
|
+
await btn.click();
|
|
577
|
+
return 'Dismissed cookie banner with: ' + sel;
|
|
578
|
+
}
|
|
579
|
+
} catch (e) {
|
|
580
|
+
// Try next selector
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return 'No cookie banner found';
|
|
584
|
+
}"
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Handle Unexpected Dialogs
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
# Set up auto-dismiss for any dialogs
|
|
591
|
+
playwright-cli run-code "async page => {
|
|
592
|
+
page.on('dialog', async dialog => {
|
|
593
|
+
console.log(\`Auto-handled \${dialog.type()} dialog: \${dialog.message()}\`);
|
|
594
|
+
await dialog.dismiss();
|
|
595
|
+
});
|
|
596
|
+
}"
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Combining CLI Features
|
|
600
|
+
|
|
601
|
+
### Full E2E Test Workflow
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
#!/bin/bash
|
|
605
|
+
set -e
|
|
606
|
+
|
|
607
|
+
# 1. Start fresh session
|
|
608
|
+
playwright-cli open https://app.example.com --persistent
|
|
609
|
+
|
|
610
|
+
# 2. Set up monitoring
|
|
611
|
+
playwright-cli run-code "async page => {
|
|
612
|
+
page.on('console', msg => {
|
|
613
|
+
if (msg.type() === 'error') console.log('[ERROR]', msg.text());
|
|
614
|
+
});
|
|
615
|
+
}"
|
|
616
|
+
|
|
617
|
+
# 3. Start tracing
|
|
618
|
+
playwright-cli tracing-start
|
|
619
|
+
|
|
620
|
+
# 4. Log in
|
|
621
|
+
playwright-cli goto https://app.example.com/login
|
|
622
|
+
playwright-cli snapshot
|
|
623
|
+
playwright-cli fill e1 "test@example.com"
|
|
624
|
+
playwright-cli fill e2 "password123"
|
|
625
|
+
playwright-cli click e3
|
|
626
|
+
playwright-cli state-save auth-state.json
|
|
627
|
+
|
|
628
|
+
# 5. Verify dashboard
|
|
629
|
+
playwright-cli snapshot
|
|
630
|
+
playwright-cli screenshot --filename=dashboard.png
|
|
631
|
+
|
|
632
|
+
# 6. Test a feature
|
|
633
|
+
playwright-cli click e5 # Navigate to feature
|
|
634
|
+
playwright-cli snapshot
|
|
635
|
+
playwright-cli fill e1 "test data"
|
|
636
|
+
playwright-cli click e3 # Submit
|
|
637
|
+
playwright-cli screenshot --filename=feature-result.png
|
|
638
|
+
|
|
639
|
+
# 7. Stop tracing and clean up
|
|
640
|
+
playwright-cli tracing-stop
|
|
641
|
+
playwright-cli close
|
|
642
|
+
|
|
643
|
+
echo "Test complete. Check screenshots/ and traces/"
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Comparison Testing Script
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
#!/bin/bash
|
|
650
|
+
# Compare two environments
|
|
651
|
+
|
|
652
|
+
STAGING="https://staging.example.com"
|
|
653
|
+
PROD="https://www.example.com"
|
|
654
|
+
PAGES=("/" "/pricing" "/about" "/features")
|
|
655
|
+
|
|
656
|
+
for path in "${PAGES[@]}"; do
|
|
657
|
+
safePath=$(echo "$path" | tr '/' '-' | sed 's/^-//')
|
|
658
|
+
[ -z "$safePath" ] && safePath="home"
|
|
659
|
+
|
|
660
|
+
playwright-cli -s=staging open "${STAGING}${path}"
|
|
661
|
+
playwright-cli -s=staging screenshot --filename="compare-staging-${safePath}.png"
|
|
662
|
+
|
|
663
|
+
playwright-cli -s=prod open "${PROD}${path}"
|
|
664
|
+
playwright-cli -s=prod screenshot --filename="compare-prod-${safePath}.png"
|
|
665
|
+
done
|
|
666
|
+
|
|
667
|
+
playwright-cli close-all
|
|
668
|
+
echo "Screenshots saved. Compare staging vs prod visually."
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## Tips
|
|
672
|
+
|
|
673
|
+
- **Start simple, add complexity** — begin with basic CLI commands, drop into `run-code` only when needed
|
|
674
|
+
- **Save state checkpoints** — `state-save checkpoint.json` at key points so you can restore if something goes wrong
|
|
675
|
+
- **Use named sessions for isolation** — never mix concerns in a single session
|
|
676
|
+
- **Monitor console and network** — set up listeners early to catch errors as they happen
|
|
677
|
+
- **Trace complex flows** — always trace when debugging multi-step workflows
|
|
678
|
+
- **Error recovery is essential** — websites are flaky; build retry logic into complex scripts
|
|
679
|
+
- **Clean up resources** — `close-all` at the end of every script; `kill-all` if things go wrong
|