Initial commit: Playwright form automation system with page dumping debugging
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
# Playwright Form Automation Workflow
|
||||
|
||||
## Overview
|
||||
Sistema completo per automatizzare submit di form su siti web usando Playwright con debugging intelligente via page dumps.
|
||||
|
||||
## Architettura del Sistema
|
||||
|
||||
### 1. **Page Dumping System** (Core Feature)
|
||||
Funzione centrale che estrae informazioni strutturali dalla pagina per debugging:
|
||||
|
||||
```javascript
|
||||
async function dumpPageInfo(page, prefix) {
|
||||
const timestamp = Date.now();
|
||||
const dumpDir = './page-dumps';
|
||||
|
||||
// 1. Salva HTML completo
|
||||
const html = await page.content();
|
||||
fs.writeFileSync(`${dumpDir}/${prefix}-${timestamp}.html`, html);
|
||||
|
||||
// 2. Screenshot della pagina
|
||||
await page.screenshot({
|
||||
path: `${dumpDir}/${prefix}-${timestamp}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// 3. Estrai TUTTI i form elements (input, textarea, select)
|
||||
const elements = await page.$$eval('input, textarea, select', els =>
|
||||
els.map(el => ({
|
||||
tag: el.tagName.toLowerCase(),
|
||||
type: el.type || '',
|
||||
name: el.name || '',
|
||||
id: el.id || '',
|
||||
placeholder: el.placeholder || '',
|
||||
value: el.value || '',
|
||||
class: el.className || ''
|
||||
}))
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
`${dumpDir}/${prefix}-${timestamp}-selectors.txt`,
|
||||
JSON.stringify(elements, null, 2)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Quando usarlo:**
|
||||
- Prima di compilare un form (per capire la struttura)
|
||||
- Dopo errori di selezione/compilazione
|
||||
- Per analizzare dropdown dinamici (Select2, react-select, etc.)
|
||||
|
||||
### 2. **Workflow Iterativo di Debugging**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 1. ESEGUI SCRIPT │
|
||||
│ node automation-script.js │
|
||||
└────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 2. ERRORE? → DUMP AUTOMATICO │
|
||||
│ - HTML salvato │
|
||||
│ - Screenshot salvato │
|
||||
│ - Form selectors estratti │
|
||||
└────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 3. ANALISI DUMP │
|
||||
│ cat page-dumps/*-selectors.txt │
|
||||
│ xdg-open page-dumps/*.png │
|
||||
└────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 4. CORREGGI SELETTORI NELLO SCRIPT │
|
||||
│ - Usa i nomi/id reali dai selectors.txt │
|
||||
│ - Adatta logica per dropdown dinamici │
|
||||
└────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 5. REITERA (torna a step 1) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3. **Pattern per Form Elements Comuni**
|
||||
|
||||
#### A. Input Text Semplici
|
||||
```javascript
|
||||
await page.fill('input[name="username"]', 'myuser');
|
||||
await page.fill('input[name="email"]', 'user@example.com');
|
||||
```
|
||||
|
||||
#### B. CSRF Token (Django/Rails/Laravel)
|
||||
```javascript
|
||||
// Automaticamente gestito dal browser, ma visibile nei dumps:
|
||||
// { "type": "hidden", "name": "csrfmiddlewaretoken", "value": "..." }
|
||||
// Non serve compilarlo manualmente - submit button lo include
|
||||
```
|
||||
|
||||
#### C. Submit Button
|
||||
```javascript
|
||||
// PREFERISCI: click esplicito sul submit button
|
||||
await page.click('input[type="submit"]');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// EVITA: form.submit() non trigger eventi JavaScript
|
||||
```
|
||||
|
||||
#### D. Select2 Dropdown (jQuery plugin)
|
||||
Select2 nasconde il `<select>` originale e crea struttura dinamica.
|
||||
|
||||
**Problema:** `page.selectOption()` non funziona!
|
||||
|
||||
**Soluzione:**
|
||||
```javascript
|
||||
// 1. Click sul container Select2 per aprire dropdown
|
||||
await page.click('span.select2-selection--multiple');
|
||||
await page.waitForTimeout(1000); // Aspetta apertura
|
||||
|
||||
// 2. Click sull'opzione nel dropdown dinamico
|
||||
await page.click('li.select2-results__option:has-text("Option Name")');
|
||||
```
|
||||
|
||||
**Come identificarlo nei dumps:**
|
||||
```json
|
||||
{
|
||||
"tag": "select",
|
||||
"name": "field_name",
|
||||
"id": "id_field_name",
|
||||
"class": "select2-hidden-accessible" // ← INDICATORE
|
||||
}
|
||||
```
|
||||
|
||||
#### E. React-Select / Vue-Select
|
||||
```javascript
|
||||
// Click sul wrapper
|
||||
await page.click('[class*="select__control"]');
|
||||
// Type per filtrare
|
||||
await page.keyboard.type('Option');
|
||||
// Enter per selezionare
|
||||
await page.keyboard.press('Enter');
|
||||
```
|
||||
|
||||
#### F. Checkboxes
|
||||
```javascript
|
||||
await page.check('input[name="agree"]');
|
||||
await page.uncheck('input[name="newsletter"]');
|
||||
```
|
||||
|
||||
#### G. Radio Buttons
|
||||
```javascript
|
||||
await page.check('input[name="gender"][value="male"]');
|
||||
```
|
||||
|
||||
#### H. File Upload
|
||||
```javascript
|
||||
await page.setInputFiles('input[type="file"]', '/path/to/file.png');
|
||||
```
|
||||
|
||||
### 4. **Login Automation con Fallback**
|
||||
|
||||
```javascript
|
||||
async function handleLogin(page, credentials) {
|
||||
console.log('🔐 Logging in automatically...');
|
||||
|
||||
try {
|
||||
// Tenta login automatico
|
||||
await page.fill('input[name="username"]', credentials.username);
|
||||
await page.fill('input[name="password"]', credentials.password);
|
||||
await page.click('input[type="submit"]');
|
||||
|
||||
// Aspetta redirect post-login (URL cambia)
|
||||
await page.waitForURL(/.*\/(?!user\/login)/, { timeout: 30000 });
|
||||
console.log('✅ Login successful!\n');
|
||||
|
||||
} catch (loginError) {
|
||||
console.warn('⚠️ Auto-login failed, manual intervention needed');
|
||||
await dumpPageInfo(page, 'login-page');
|
||||
|
||||
// Fallback: attendi login manuale
|
||||
console.log('Please log in manually in the browser window...');
|
||||
await page.waitForURL(/.*\/(?!user\/login)/, { timeout: 300000 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. **Error Handling Best Practices**
|
||||
|
||||
```javascript
|
||||
try {
|
||||
// Operazioni rischiose
|
||||
await page.fill('input[name="field"]', 'value');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${error.message}`);
|
||||
|
||||
// SEMPRE dump in caso di errore
|
||||
await dumpPageInfo(page, 'error-state');
|
||||
|
||||
// Re-throw per fermare esecuzione
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. **Waiters Strategici**
|
||||
|
||||
```javascript
|
||||
// ❌ EVITA: Wait fissi generici
|
||||
await page.waitForTimeout(5000); // Troppo lento
|
||||
|
||||
// ✅ PREFERISCI: Wait condizionali
|
||||
await page.waitForLoadState('networkidle'); // Aspetta caricamento
|
||||
await page.waitForSelector('button[type="submit"]'); // Aspetta elemento
|
||||
await page.waitForURL(/expected-url/); // Aspetta navigazione
|
||||
```
|
||||
|
||||
### 7. **Struttura Script Template**
|
||||
|
||||
```javascript
|
||||
const { chromium } = require('playwright');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Config
|
||||
const SITE_URL = 'https://example.com';
|
||||
const LOGIN_CREDENTIALS = {
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
};
|
||||
|
||||
const FORM_DATA = {
|
||||
field1: 'value1',
|
||||
field2: 'value2'
|
||||
};
|
||||
|
||||
// Page dump function
|
||||
async function dumpPageInfo(page, prefix) {
|
||||
// [vedi sezione 1]
|
||||
}
|
||||
|
||||
// Main automation
|
||||
async function automateFormSubmit() {
|
||||
const browser = await chromium.launch({ headless: false });
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
// Step 1: Navigate
|
||||
await page.goto(SITE_URL);
|
||||
|
||||
// Step 2: Login (se necessario)
|
||||
if (await page.locator('a:has-text("Login")').count() > 0) {
|
||||
await handleLogin(page, LOGIN_CREDENTIALS);
|
||||
}
|
||||
|
||||
// Step 3: Navigate to form
|
||||
await page.goto(`${SITE_URL}/submit-form`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Step 4: Dump iniziale per capire struttura
|
||||
await dumpPageInfo(page, 'form-initial');
|
||||
|
||||
// Step 5: Fill form
|
||||
await page.fill('input[name="field1"]', FORM_DATA.field1);
|
||||
console.log('✓ Field1 filled');
|
||||
|
||||
await page.fill('input[name="field2"]', FORM_DATA.field2);
|
||||
console.log('✓ Field2 filled');
|
||||
|
||||
// Step 6: Submit
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/success-page/);
|
||||
console.log('✅ Form submitted successfully!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
await dumpPageInfo(page, 'error');
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run
|
||||
automateFormSubmit().catch(console.error);
|
||||
```
|
||||
|
||||
### 8. **Setup Progetto**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "form-automation",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"playwright": "^1.40.0"
|
||||
},
|
||||
"scripts": {
|
||||
"install-browsers": "npx playwright install chromium",
|
||||
"run": "node automation-script.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Comandi setup:**
|
||||
```bash
|
||||
npm install
|
||||
npm run install-browsers
|
||||
npm run
|
||||
```
|
||||
|
||||
### 9. **Debugging Checklist**
|
||||
|
||||
Quando uno script fallisce:
|
||||
|
||||
1. ✅ **Controlla selectors.txt** - I nomi/id sono corretti?
|
||||
2. ✅ **Guarda screenshot** - L'elemento è visibile? Serve scroll?
|
||||
3. ✅ **Verifica HTML** - Ci sono iframe? Shadow DOM?
|
||||
4. ✅ **Test manuale** - L'operazione funziona manualmente nel browser?
|
||||
5. ✅ **Console browser** - Ci sono errori JavaScript che bloccano?
|
||||
|
||||
### 10. **Tips Avanzati**
|
||||
|
||||
#### Gestione Iframe
|
||||
```javascript
|
||||
const frame = page.frameLocator('iframe#myframe');
|
||||
await frame.locator('input[name="field"]').fill('value');
|
||||
```
|
||||
|
||||
#### Shadow DOM
|
||||
```javascript
|
||||
const shadow = await page.locator('my-component')
|
||||
.evaluateHandle(el => el.shadowRoot);
|
||||
await shadow.locator('button').click();
|
||||
```
|
||||
|
||||
#### Intercept Network Requests
|
||||
```javascript
|
||||
await page.route('**/api/endpoint', route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Cookies/Headers
|
||||
```javascript
|
||||
await page.setExtraHTTPHeaders({
|
||||
'Authorization': 'Bearer token123'
|
||||
});
|
||||
|
||||
await browser.newContext({
|
||||
extraHTTPHeaders: { 'X-Custom': 'value' }
|
||||
});
|
||||
```
|
||||
|
||||
## Caso Studio: Lutris.net Game Submission
|
||||
|
||||
Vedi `publish-rats-lutris.js` nella parent folder per esempio completo con:
|
||||
- Login automation con fallback manuale
|
||||
- Select2 dropdown handling
|
||||
- File upload
|
||||
- Multi-step form navigation
|
||||
- Comprehensive error handling con dumps
|
||||
|
||||
---
|
||||
|
||||
**Creato**: 25 ottobre 2025
|
||||
**Testato su**: Debian GNU/Linux 13, Playwright 1.40.0, Node.js v20.19.2
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
page-dumps/*.html
|
||||
page-dumps/*.png
|
||||
page-dumps/*.txt
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,248 @@
|
||||
# Playwright Form Automation Toolkit
|
||||
|
||||
🤖 Sistema completo per automatizzare form submission su siti web con debugging intelligente.
|
||||
|
||||
## Features
|
||||
|
||||
✨ **Page Dumping System** - Estrazione automatica di HTML, screenshot e form selectors
|
||||
🔐 **Smart Login** - Auto-login con fallback manuale
|
||||
🎯 **Error Recovery** - Dump automatico ad ogni errore
|
||||
📸 **Visual Debugging** - Screenshot full-page ad ogni step
|
||||
🔄 **Iterative Workflow** - Ciclo rapido test → dump → fix → retest
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Installa dipendenze
|
||||
npm install
|
||||
|
||||
# 2. Installa browser Chromium
|
||||
npm run install-browsers
|
||||
|
||||
# 3. Modifica template-automation.js con i tuoi dati
|
||||
# - SITE_URL
|
||||
# - LOGIN_CREDENTIALS
|
||||
# - FORM_DATA
|
||||
# - Selettori specifici per il tuo sito
|
||||
|
||||
# 4. Esegui
|
||||
npm test
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
playwright-form-automation/
|
||||
├── .github/
|
||||
│ └── copilot-instructions.md # Guida completa workflow
|
||||
├── package.json # Dipendenze npm
|
||||
├── template-automation.js # Template script automation
|
||||
├── page-dumps/ # Output debug (auto-creata)
|
||||
│ ├── form-initial-*.html
|
||||
│ ├── form-initial-*.png
|
||||
│ ├── form-initial-*-selectors.txt
|
||||
│ └── ...
|
||||
└── README.md # Questo file
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Page Dumping
|
||||
|
||||
Ad ogni step importante (e ad ogni errore), lo script salva:
|
||||
|
||||
- **HTML completo** - Per analisi struttura DOM
|
||||
- **Screenshot** - Per vedere visivamente cosa è successo
|
||||
- **Form selectors** - JSON con tutti gli elementi form (input, select, textarea, button)
|
||||
|
||||
```javascript
|
||||
await dumpPageInfo(page, 'step-name');
|
||||
// Crea:
|
||||
// - page-dumps/step-name-<timestamp>.html
|
||||
// - page-dumps/step-name-<timestamp>.png
|
||||
// - page-dumps/step-name-<timestamp>-selectors.txt
|
||||
```
|
||||
|
||||
### 2. Iterative Debugging
|
||||
|
||||
```bash
|
||||
# Run 1: Script fallisce
|
||||
node template-automation.js
|
||||
# ❌ Error: Timeout waiting for 'input[name="username"]'
|
||||
|
||||
# Analizza dump
|
||||
cat page-dumps/login-error-*-selectors.txt
|
||||
# Scopri che il campo è 'input[name="user"]' non "username"
|
||||
|
||||
# Fix nello script
|
||||
# Cambia: await page.fill('input[name="username"]', ...)
|
||||
# In: await page.fill('input[name="user"]', ...)
|
||||
|
||||
# Run 2: Riprova
|
||||
node template-automation.js
|
||||
# ✅ Success!
|
||||
```
|
||||
|
||||
### 3. Select2 Dropdown Handling
|
||||
|
||||
Select2 (jQuery plugin usato da molti siti) nasconde i `<select>` standard.
|
||||
|
||||
**Identificazione** (in selectors.txt):
|
||||
```json
|
||||
{
|
||||
"tag": "select",
|
||||
"name": "category",
|
||||
"class": "select2-hidden-accessible" // ← Questo indica Select2
|
||||
}
|
||||
```
|
||||
|
||||
**Soluzione**:
|
||||
```javascript
|
||||
// ❌ NON funziona
|
||||
await page.selectOption('select[name="category"]', 'value');
|
||||
|
||||
// ✅ Funziona
|
||||
await page.click('span.select2-selection--multiple'); // Apre dropdown
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('li.select2-results__option:has-text("Option")'); // Seleziona
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Login con CSRF Token
|
||||
```javascript
|
||||
// Il token viene gestito automaticamente dal browser
|
||||
// Basta compilare username/password e cliccare submit
|
||||
await page.fill('input[name="username"]', 'user');
|
||||
await page.fill('input[name="password"]', 'pass');
|
||||
await page.click('input[type="submit"]'); // Click, NON form.submit()!
|
||||
```
|
||||
|
||||
### File Upload
|
||||
```javascript
|
||||
await page.setInputFiles('input[type="file"]', '/path/to/file.png');
|
||||
```
|
||||
|
||||
### Checkbox/Radio
|
||||
```javascript
|
||||
await page.check('input[name="agree"]');
|
||||
await page.check('input[name="gender"][value="male"]');
|
||||
```
|
||||
|
||||
### Wait for Navigation
|
||||
```javascript
|
||||
// Dopo submit, aspetta che URL cambi
|
||||
await page.waitForURL(/success|confirmation/);
|
||||
```
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
Script fallito? Segui questa lista:
|
||||
|
||||
1. ✅ **Leggi selectors.txt** - Nomi/ID corretti?
|
||||
2. ✅ **Guarda screenshot** - Elemento visibile? Serve scroll?
|
||||
3. ✅ **Controlla HTML** - Iframe? Shadow DOM? JavaScript che modifica DOM?
|
||||
4. ✅ **Prova manualmente** - L'azione funziona a mano nel browser?
|
||||
5. ✅ **Console errors** - Apri DevTools nel browser Playwright
|
||||
|
||||
## Examples
|
||||
|
||||
### Esempio 1: Form Semplice
|
||||
```javascript
|
||||
await page.fill('input[name="name"]', 'John Doe');
|
||||
await page.fill('input[name="email"]', 'john@example.com');
|
||||
await page.fill('textarea[name="message"]', 'Hello world!');
|
||||
await page.click('button[type="submit"]');
|
||||
```
|
||||
|
||||
### Esempio 2: Multi-step Form
|
||||
```javascript
|
||||
// Step 1
|
||||
await page.fill('input[name="email"]', 'user@mail.com');
|
||||
await page.click('button:has-text("Next")');
|
||||
await page.waitForURL(/step-2/);
|
||||
|
||||
// Step 2
|
||||
await page.fill('input[name="address"]', '123 Main St');
|
||||
await page.click('button:has-text("Submit")');
|
||||
```
|
||||
|
||||
### Esempio 3: Login + Form
|
||||
```javascript
|
||||
// Login
|
||||
await page.goto('https://site.com/login');
|
||||
await page.fill('input[name="username"]', 'user');
|
||||
await page.fill('input[name="password"]', 'pass');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/dashboard/);
|
||||
|
||||
// Navigate to form
|
||||
await page.goto('https://site.com/submit');
|
||||
|
||||
// Fill and submit
|
||||
await page.fill('input[name="title"]', 'My Title');
|
||||
await page.click('button[type="submit"]');
|
||||
```
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
### Slow Down for Visibility
|
||||
```javascript
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
slowMo: 100 // Rallenta di 100ms ogni azione
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Viewport
|
||||
```javascript
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 }
|
||||
});
|
||||
```
|
||||
|
||||
### Network Interception
|
||||
```javascript
|
||||
await page.route('**/api/spam', route => route.abort());
|
||||
```
|
||||
|
||||
### Take Extra Screenshots
|
||||
```javascript
|
||||
await page.screenshot({
|
||||
path: 'debug-step.png',
|
||||
fullPage: true
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Timeout waiting for selector"
|
||||
→ Elemento non esiste o ha nome diverso. Controlla `selectors.txt`.
|
||||
|
||||
### "Element is not visible"
|
||||
→ Serve scroll: `await page.locator('selector').scrollIntoViewIfNeeded();`
|
||||
|
||||
### "Element is covered"
|
||||
→ Modal/popup sopra. Chiudi: `await page.click('button.close-modal');`
|
||||
|
||||
### Select2 non funziona
|
||||
→ Usa pattern "click container → click option" (vedi sopra).
|
||||
|
||||
### Submit non fa niente
|
||||
→ Usa `page.click()` non `form.submit()`. JavaScript handlers servono click event.
|
||||
|
||||
## Real-World Example
|
||||
|
||||
Vedi nella parent folder:
|
||||
- `publish-rats-lutris.js` - Script completo per pubblicare gioco su Lutris.net
|
||||
- Include: login, Select2, file upload, multi-step form
|
||||
|
||||
## License
|
||||
|
||||
MIT - Free to use and modify
|
||||
|
||||
---
|
||||
|
||||
**Created**: October 25, 2025
|
||||
**Author**: enne2
|
||||
**Tested on**: Debian GNU/Linux 13, Node.js v20.19.2, Playwright 1.40.0
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "playwright-form-automation",
|
||||
"version": "1.0.0",
|
||||
"description": "Automated form submission with Playwright - intelligent page dumping for debugging",
|
||||
"main": "template-automation.js",
|
||||
"scripts": {
|
||||
"install-browsers": "npx playwright install chromium",
|
||||
"test": "node template-automation.js",
|
||||
"clean-dumps": "rm -rf page-dumps/*"
|
||||
},
|
||||
"keywords": [
|
||||
"playwright",
|
||||
"automation",
|
||||
"form",
|
||||
"web-scraping",
|
||||
"testing"
|
||||
],
|
||||
"author": "enne2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"playwright": "^1.40.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# Playwright Form Automation - Page Dumps
|
||||
|
||||
This directory is automatically created by the automation scripts.
|
||||
|
||||
## Contents
|
||||
|
||||
Each dump consists of 3 files with the same timestamp:
|
||||
|
||||
1. **`<prefix>-<timestamp>.html`** - Full HTML content of the page
|
||||
2. **`<prefix>-<timestamp>.png`** - Full-page screenshot
|
||||
3. **`<prefix>-<timestamp>-selectors.txt`** - JSON array of all form elements
|
||||
|
||||
## How to Use
|
||||
|
||||
When your script fails or you need to understand page structure:
|
||||
|
||||
1. **Check the screenshot** - Visual representation of what the page looked like
|
||||
2. **Read selectors.txt** - Find the correct selectors (name, id, class) for form elements
|
||||
3. **Search HTML** - Deep dive into DOM structure if needed
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# View latest selectors
|
||||
cat page-dumps/*-selectors.txt | tail -100
|
||||
|
||||
# Open latest screenshot
|
||||
xdg-open $(ls -t page-dumps/*.png | head -1)
|
||||
|
||||
# Search for specific field in HTML
|
||||
grep -i "username" page-dumps/login-*.html
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
Remove old dumps to save space:
|
||||
|
||||
```bash
|
||||
# From parent directory
|
||||
npm run clean-dumps
|
||||
|
||||
# Or manually
|
||||
rm -rf page-dumps/*
|
||||
```
|
||||
@@ -0,0 +1,270 @@
|
||||
const { chromium } = require('playwright');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// ========================================
|
||||
// CONFIGURATION - Customize for your site
|
||||
// ========================================
|
||||
|
||||
const SITE_URL = 'https://example.com';
|
||||
const LOGIN_REQUIRED = true;
|
||||
|
||||
const LOGIN_CREDENTIALS = {
|
||||
username: 'your_username',
|
||||
password: 'your_password'
|
||||
};
|
||||
|
||||
const FORM_DATA = {
|
||||
field1: 'Value 1',
|
||||
field2: 'Value 2',
|
||||
description: 'Long description text here...'
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// CORE UTILITY: Page Dumping System
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Dumps page information for debugging:
|
||||
* - Full HTML content
|
||||
* - Screenshot (full page)
|
||||
* - All form elements with their properties
|
||||
*
|
||||
* Use this BEFORE filling forms and AFTER errors
|
||||
*/
|
||||
async function dumpPageInfo(page, prefix) {
|
||||
const timestamp = Date.now();
|
||||
const dumpDir = path.join(__dirname, 'page-dumps');
|
||||
|
||||
// Create dumps directory if not exists
|
||||
if (!fs.existsSync(dumpDir)) {
|
||||
fs.mkdirSync(dumpDir, { recursive: true });
|
||||
}
|
||||
|
||||
console.log('🔍 Dumping page for debugging...');
|
||||
|
||||
try {
|
||||
// 1. Save full HTML
|
||||
const html = await page.content();
|
||||
const htmlPath = path.join(dumpDir, `${prefix}-${timestamp}.html`);
|
||||
fs.writeFileSync(htmlPath, html);
|
||||
console.log(` 📄 HTML saved: ${htmlPath}`);
|
||||
|
||||
// 2. Take screenshot
|
||||
const screenshotPath = path.join(dumpDir, `${prefix}-${timestamp}.png`);
|
||||
await page.screenshot({
|
||||
path: screenshotPath,
|
||||
fullPage: true
|
||||
});
|
||||
console.log(` 📸 Screenshot saved: ${screenshotPath}`);
|
||||
|
||||
// 3. Extract all form elements
|
||||
const elements = await page.$$eval('input, textarea, select, button', els =>
|
||||
els.map(el => ({
|
||||
tag: el.tagName.toLowerCase(),
|
||||
type: el.type || '',
|
||||
name: el.name || '',
|
||||
id: el.id || '',
|
||||
placeholder: el.placeholder || '',
|
||||
value: el.value || '',
|
||||
class: el.className || ''
|
||||
}))
|
||||
);
|
||||
|
||||
const selectorsPath = path.join(dumpDir, `${prefix}-${timestamp}-selectors.txt`);
|
||||
fs.writeFileSync(selectorsPath, JSON.stringify(elements, null, 2));
|
||||
console.log(` 📋 Form elements saved: ${selectorsPath}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error during page dump: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LOGIN HANDLER with Fallback
|
||||
// ========================================
|
||||
|
||||
async function handleLogin(page, credentials) {
|
||||
console.log('🔐 Attempting automatic login...');
|
||||
|
||||
try {
|
||||
// Dump login page first to understand structure
|
||||
await dumpPageInfo(page, 'login-page-initial');
|
||||
|
||||
// Fill login form
|
||||
await page.fill('input[name="username"]', credentials.username);
|
||||
await page.fill('input[name="password"]', credentials.password);
|
||||
|
||||
// Submit (click button, don't use form.submit())
|
||||
await page.click('input[type="submit"], button[type="submit"]');
|
||||
|
||||
// Wait for redirect after successful login
|
||||
// Adjust the URL pattern to match your site's post-login URL
|
||||
await page.waitForURL(/.*\/(?!login|signin)/, { timeout: 30000 });
|
||||
|
||||
console.log('✅ Login successful!\n');
|
||||
|
||||
} catch (loginError) {
|
||||
console.warn('⚠️ Auto-login failed, trying manual intervention...');
|
||||
await dumpPageInfo(page, 'login-error');
|
||||
|
||||
console.log('\n⏸️ Please log in manually in the browser window.');
|
||||
console.log(' Waiting up to 5 minutes for manual login...\n');
|
||||
|
||||
// Wait for user to login manually
|
||||
await page.waitForURL(/.*\/(?!login|signin)/, { timeout: 300000 });
|
||||
console.log('✅ Manual login detected!\n');
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MAIN AUTOMATION FUNCTION
|
||||
// ========================================
|
||||
|
||||
async function automateFormSubmission() {
|
||||
console.log('🚀 Starting form automation...\n');
|
||||
|
||||
// Launch browser (headless: false to see what's happening)
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
slowMo: 100 // Slow down operations for visibility
|
||||
});
|
||||
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 }
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// ========================================
|
||||
// STEP 1: Navigate to site
|
||||
// ========================================
|
||||
console.log('📋 Step 1: Navigating to site...');
|
||||
await page.goto(SITE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// ========================================
|
||||
// STEP 2: Handle login if required
|
||||
// ========================================
|
||||
if (LOGIN_REQUIRED) {
|
||||
// Check if login is needed (adjust selector to your site)
|
||||
const needsLogin = await page.locator('a:has-text("Login"), a:has-text("Sign in")').count() > 0;
|
||||
|
||||
if (needsLogin) {
|
||||
console.log('⚠️ Login required!\n');
|
||||
// Navigate to login page (adjust URL)
|
||||
await page.goto(`${SITE_URL}/login`);
|
||||
await handleLogin(page, LOGIN_CREDENTIALS);
|
||||
} else {
|
||||
console.log('✅ Already logged in!\n');
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STEP 3: Navigate to form page
|
||||
// ========================================
|
||||
console.log('📋 Step 2: Navigating to form page...');
|
||||
await page.goto(`${SITE_URL}/submit-form`); // Adjust URL
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// ========================================
|
||||
// STEP 4: Dump page to understand structure
|
||||
// ========================================
|
||||
console.log('📝 Step 3: Analyzing form structure...');
|
||||
await dumpPageInfo(page, 'form-initial');
|
||||
|
||||
// ========================================
|
||||
// STEP 5: Fill form fields
|
||||
// ========================================
|
||||
console.log('📝 Step 4: Filling form...');
|
||||
|
||||
try {
|
||||
// Simple text input
|
||||
await page.fill('input[name="field1"]', FORM_DATA.field1);
|
||||
console.log(' ✓ Field1 filled');
|
||||
|
||||
// Another text input
|
||||
await page.fill('input[name="field2"]', FORM_DATA.field2);
|
||||
console.log(' ✓ Field2 filled');
|
||||
|
||||
// Textarea
|
||||
await page.fill('textarea[name="description"]', FORM_DATA.description);
|
||||
console.log(' ✓ Description filled');
|
||||
|
||||
// Example: Select dropdown (standard)
|
||||
// await page.selectOption('select[name="category"]', 'option-value');
|
||||
// console.log(' ✓ Category selected');
|
||||
|
||||
// Example: Select2 dropdown (jQuery plugin)
|
||||
// await page.click('span.select2-selection--multiple');
|
||||
// await page.waitForTimeout(500);
|
||||
// await page.click('li.select2-results__option:has-text("Option Name")');
|
||||
// console.log(' ✓ Select2 option selected');
|
||||
|
||||
// Example: Checkbox
|
||||
// await page.check('input[name="agree"]');
|
||||
// console.log(' ✓ Checkbox checked');
|
||||
|
||||
// Example: File upload
|
||||
// await page.setInputFiles('input[type="file"]', '/path/to/file.png');
|
||||
// console.log(' ✓ File uploaded');
|
||||
|
||||
} catch (fillError) {
|
||||
console.error(`❌ Error filling form: ${fillError.message}`);
|
||||
await dumpPageInfo(page, 'form-fill-error');
|
||||
throw fillError;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STEP 6: Review before submit
|
||||
// ========================================
|
||||
console.log('\n⏸️ Form filled! Review in browser and press Enter to submit...');
|
||||
await new Promise(resolve => {
|
||||
process.stdin.once('data', resolve);
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// STEP 7: Submit form
|
||||
// ========================================
|
||||
console.log('📤 Step 5: Submitting form...');
|
||||
await page.click('button[type="submit"], input[type="submit"]');
|
||||
|
||||
// Wait for success page (adjust URL pattern)
|
||||
await page.waitForURL(/success|confirmation|thank-you/, { timeout: 30000 });
|
||||
console.log('✅ Form submitted successfully!\n');
|
||||
|
||||
// Take final screenshot
|
||||
await page.screenshot({
|
||||
path: path.join(__dirname, 'page-dumps', 'success.png'),
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`\n❌ Automation failed: ${error.message}\n`);
|
||||
await dumpPageInfo(page, 'fatal-error');
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
console.log('🏁 Closing browser...');
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RUN AUTOMATION
|
||||
// ========================================
|
||||
|
||||
if (require.main === module) {
|
||||
automateFormSubmission()
|
||||
.then(() => {
|
||||
console.log('✅ Automation completed successfully!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Automation failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { automateFormSubmission, dumpPageInfo };
|
||||
Reference in New Issue
Block a user