Sistema completo per automatizzare submit di form su siti web usando Playwright con debugging intelligente via page dumps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

10 KiB

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:

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

await page.fill('input[name="username"]', 'myuser');
await page.fill('input[name="email"]', 'user@example.com');

B. CSRF Token (Django/Rails/Laravel)

// Automaticamente gestito dal browser, ma visibile nei dumps:
// { "type": "hidden", "name": "csrfmiddlewaretoken", "value": "..." }
// Non serve compilarlo manualmente - submit button lo include

C. Submit Button

// 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:

// 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:

{
  "tag": "select",
  "name": "field_name",
  "id": "id_field_name",
  "class": "select2-hidden-accessible"  // ← INDICATORE
}

E. React-Select / Vue-Select

// 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

await page.check('input[name="agree"]');
await page.uncheck('input[name="newsletter"]');

G. Radio Buttons

await page.check('input[name="gender"][value="male"]');

H. File Upload

await page.setInputFiles('input[type="file"]', '/path/to/file.png');

4. Login Automation con Fallback

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

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

// ❌ 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

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

{
  "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:

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

const frame = page.frameLocator('iframe#myframe');
await frame.locator('input[name="field"]').fill('value');

Shadow DOM

const shadow = await page.locator('my-component')
  .evaluateHandle(el => el.shadowRoot);
await shadow.locator('button').click();

Intercept Network Requests

await page.route('**/api/endpoint', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ success: true })
  });
});

Custom Cookies/Headers

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