# ANALISI IMPLEMENTATIVA: OTTIMIZZAZIONE MCP PLAYWRIGHT E SKILLS

**Data:** 02/07/2026  
**Autore originale:** Antigravity (AI Coding Assistant)  
**Revisione tecnica:** ChatGPT  
**Target:**
- MCP Server: `D:\mcp-servers\playwright-node`
- Skill: `D:\mcp-servers\skills\mcp-browser-automation`

---

## Stato della Revisione

Il documento resta valido come analisi dei problemi e come piano di intervento incrementale. La revisione seguente e' stata riallineata al codice attuale di `playwright-node/index.js` e alla skill `skills/mcp-browser-automation/SKILL.md`.

Verifiche iniziali pre-fix:

- `playwright-node/package.json` dichiarava `playwright` `^1.58.2`.
- `browser_screenshot` usava `activePage.screenshot({ fullPage: false })`.
- `browser_debug_bundle` usava `activePage.screenshot({ path: screenshotPath, fullPage: false })` quando `include_screenshot=true`.
- `browser_annotate` produceva a sua volta uno screenshot dopo avere iniettato le etichette visive.
- Le action `list_pages`, `select_page`, `select_page_by_url`, `select_page_by_title`, `attach_cdp`, `list_frames`, `select_frame`, `click_by_id` e `click_uid` risultavano gia' disponibili.

Decisioni principali:

1. Correggere la gestione screenshot lato server con un helper centralizzato, senza CSS permanente iniettato nel DOM.
2. Applicare la stessa logica sia a `browser_screenshot` sia agli screenshot prodotti da `debug_bundle`.
3. Valutare se applicare lo stesso helper anche a `browser_annotate`, per evitare un comportamento diverso tra screenshot semplice e screenshot annotato.
4. Rafforzare la guida CDP usando `select_page_by_url`, `select_page_by_title` o `stable_page_id`, non solo `page_id` numerico.
5. Sostituire attese fisse su iframe con controlli condizionati via DOM/frame list.
6. Trattare il click JS come fallback esplicito e tracciato, non come prima scelta.

## Stato Implementazione Core

Implementato nello scope core:

- helper server `captureViewportScreenshot()` con `timeout`, `animations: "disabled"` e stile temporaneo opzionale;
- nuove variabili ambiente `SCREENSHOT_TIMEOUT_MS` e `SCREENSHOT_FORCE_SYSTEM_FONTS`;
- riuso dell'helper in `browser_screenshot`, `browser_annotate` e `browser_debug_bundle`;
- smoke test per `screenshot`, `annotate` e `debug_bundle` con screenshot su artifact;
- aggiornamento della skill e dei test pattern per CDP multi-tab, iframe modali asincroni e overlay/click intercettati.

Resta fuori scope in questa iterazione:

- nuova action `wait_for_js`;
- nuova action/configurazione `dismiss_overlay`.

Queste evoluzioni restano backlog opzionale per una milestone successiva, perche' richiedono nuova superficie API, schema e smoke dedicati.

---

## 1. Problema 1: Timeout Screenshot per Mancato Caricamento Web Fonts

### Descrizione del Problema

In ambienti intranet, di sviluppo locale o offline, le chiamate a font esterni (es. Google Fonts `fonts.googleapis.com` o `gstatic.com`) possono rallentare o bloccare la cattura degli screenshot quando l'host non ha accesso diretto a internet.

Nel server attuale il case `browser_screenshot` esegue:

```javascript
const buffer = await activePage.screenshot({ fullPage: false });
```

Questa chiamata non imposta:

- timeout specifico per la screenshot;
- disabilitazione animazioni/transizioni;
- stile temporaneo per rendere la cattura piu' deterministica;
- riuso della stessa logica nel ramo `debug_bundle`.

Nota: lo stesso pattern e' presente anche in `browser_annotate`, dove lo screenshot e' usato per mostrare le etichette numeriche. La correzione minima deve coprire `browser_screenshot` e `debug_bundle`; l'estensione ad `annotate` e' consigliata se non altera la leggibilita' delle etichette.

### Correzione Tecnica

Non usare `font-display` dentro un selettore generico come `*`: `font-display` e' un descriptor di `@font-face`, non una proprieta' applicabile agli elementi.

Preferire invece l'opzione `style` di `page.screenshot()`, supportata dalla versione Playwright dichiarata nel progetto (`^1.58.2`). Lo stile viene applicato solo durante la cattura e non modifica in modo persistente il DOM della pagina.

Usare anche le opzioni native `animations: "disabled"` e `timeout`, cosi' la cattura fallisce in modo controllato invece di restare appesa su pagine lente o con risorse esterne non raggiungibili.

### Patch Proposta nel Server MCP (`playwright-node/index.js`)

Aggiungere vicino alle costanti di timeout:

```javascript
const SCREENSHOT_TIMEOUT_MS = Number(process.env.SCREENSHOT_TIMEOUT_MS || 10000);
const SCREENSHOT_FORCE_SYSTEM_FONTS = process.env.SCREENSHOT_FORCE_SYSTEM_FONTS !== "false";

const SCREENSHOT_STYLE = SCREENSHOT_FORCE_SYSTEM_FONTS
  ? `
    *, *::before, *::after {
      font-family: Arial, "Segoe UI", sans-serif !important;
    }
  `
  : "";
```

Le animazioni vanno disabilitate con l'opzione Playwright `animations: "disabled"` dell'helper. Non serve duplicare la stessa logica nel CSS temporaneo, salvo casi specifici da documentare.

Aggiungere un helper centralizzato:

```javascript
async function captureViewportScreenshot(activePage, options = {}) {
  const screenshotOptions = {
    fullPage: Boolean(options.fullPage),
    animations: "disabled",
    timeout: SCREENSHOT_TIMEOUT_MS
  };

  if (options.path) {
    screenshotOptions.path = options.path;
  }

  if (SCREENSHOT_STYLE) {
    screenshotOptions.style = SCREENSHOT_STYLE;
  }

  return activePage.screenshot(screenshotOptions);
}
```

Sostituire nel case `browser_screenshot`:

```javascript
const buffer = await activePage.screenshot({ fullPage: false });
```

con:

```javascript
const buffer = await captureViewportScreenshot(activePage, { fullPage: false });
```

Mantenere poi la logica esistente di salvataggio su `savePath`, oppure passare direttamente `path: savePath` all'helper se si vuole evitare la doppia scrittura manuale.

Scelta consigliata: usare `path: savePath` dentro l'helper solo se si verifica che Playwright restituisca comunque il buffer necessario per la risposta inline. In caso contrario mantenere la scrittura manuale attuale dopo avere ricevuto il buffer, per non cambiare il contratto legacy di `browser_screenshot`.

Sostituire anche nel ramo `debug_bundle`:

```javascript
await activePage.screenshot({ path: screenshotPath, fullPage: false });
```

con:

```javascript
await captureViewportScreenshot(activePage, {
  path: screenshotPath,
  fullPage: false
});
```

### Note Operative

- `SCREENSHOT_TIMEOUT_MS` rende il timeout configurabile senza cambiare codice.
- `SCREENSHOT_FORCE_SYSTEM_FONTS=false` permette di disattivare il fallback forzato se una pagina usa icon font o web font indispensabili alla resa grafica.
- La modifica deve coprire sia screenshot richiesti dall'agente sia screenshot diagnostici prodotti da `debug_bundle`.

---

## 2. Problema 2: Disallineamento Tab Attive in CDP Attach

### Descrizione del Problema

Quando ci si collega ad un'istanza di Chrome esistente tramite `attach_cdp`, Playwright puo' selezionare una tab non coerente con il test case. Se l'utente lavora in parallelo su piu' schede (es. amministratore ColdFusion su una tab e portale fornitori su un'altra), l'agente rischia di eseguire azioni sulla tab errata o di ricevere errori di contesto distrutto.

Il server espone gia':

- `list_pages`
- `select_page`
- `select_page_by_url`
- `select_page_by_title`
- `stable_page_id`
- `bring_to_front`

### Linea Guida Implementativa nella Skill (`mcp-browser-automation`)

Aggiungere o rafforzare una sezione **"Sincronizzazione Multi-Tab dopo CDP"**.

Regole:

1. Dopo `attach_cdp`, verificare sempre la tab attiva con `list_pages`, oppure selezionare direttamente con `select_page_by_url` / `select_page_by_title` se il filtro e' noto.
2. Preferire `select_page_by_url` o `select_page_by_title` quando il dominio/titolo atteso e' chiaro.
3. Se si usa `select_page`, preferire `stable_page_id` ricavato da `list_pages` rispetto a `page_id` numerico.
4. Usare `bring_to_front: true` quando la tab selezionata deve essere anche resa visibile all'utente.
5. Usare `page_id` solo come fallback immediato dopo una `list_pages` appena eseguita.

### Esempio Preferito: Selezione per URL

```json
{ "action": "attach_cdp", "cdp_url": "http://127.0.0.1:9222" }
{ "action": "list_pages" }
{ "action": "select_page_by_url", "url_contains": "fornitori", "bring_to_front": true }
```

### Esempio Alternativo: Selezione con Stable Page ID

```json
{ "action": "attach_cdp", "cdp_url": "http://127.0.0.1:9222" }
{ "action": "list_pages" }
{ "action": "select_page", "stable_page_id": "p2", "bring_to_front": true }
```

### Nota di Implementazione

Il documento precedente suggeriva `page_id: 1`. La forma resta tecnicamente valida, ma e' meno robusta perche' l'ordine delle tab puo' cambiare. La skill deve guidare l'agente verso URL/title/stable id.

---

## 3. Problema 3: Dialog Modali con Caricamento Iframe Asincrono

### Descrizione del Problema

Molti applicativi legacy aprono dialog inserendo un iframe temporaneamente impostato su `about:blank`, poi aggiornato via JavaScript. Se l'agente interagisce subito tramite `select_frame`, rischia di lavorare su una pagina vuota.

Inoltre, se l'agente naviga direttamente all'URL dell'iframe tramite `navigate`, si interrompe il legame con `window.parent` e possono fallire funzioni JS di ritorno al parent, ad esempio `ricaricaElencoForn()`.

### Linea Guida Implementativa nella Skill (`mcp-browser-automation`)

Aggiungere o rafforzare una sezione **"Gestione Dialog e Modali basati su Iframe"**.

Regole:

1. **Mai** navigare direttamente all'URL di un iframe modale quando il frame deve comunicare con la pagina parent.
2. Lasciare che l'iframe si carichi nel suo contesto naturale.
3. Evitare attese fisse tipo 2-3 secondi come regola primaria.
4. Usare controlli condizionati su DOM e frame list.
5. Se il frame cambia dopo click o submit, rieseguire `list_frames` prima di interagire.
6. Terminata l'azione nel frame, tornare al main frame con `select_frame` e `index: 0`.

### Controllo Condizionato via `evaluate_js`

Esempio per verificare se l'iframe ha lasciato `about:blank`:

```json
{
  "action": "evaluate_js",
  "code": "(() => { const f = document.getElementById('framericercaBPInSAP'); return { exists: !!f, src: f?.src || '', ready: !!f && !!f.src && !f.src.includes('about:blank') }; })()"
}
```

Quando `ready` e' `true`:

```json
{ "action": "list_frames" }
{ "action": "select_frame", "index": 2 }
```

Dopo il click o submit dentro l'iframe:

```json
{ "action": "select_frame", "index": 0 }
```

### Miglioria Opzionale Server

Valutare una nuova action server, ad esempio `wait_for_js`, per evitare che la skill debba simulare retry manuali con piu' chiamate `evaluate_js`.

Esempio desiderato:

```json
{
  "action": "wait_for_js",
  "code": "(() => { const f = document.getElementById('framericercaBPInSAP'); return !!f && !!f.src && !f.src.includes('about:blank'); })()",
  "timeout_ms": 10000
}
```

---

## 4. Problema 4: Click Ostruiti da Overlay o Banner Cookie

### Descrizione del Problema

Banner cookie persistenti, overlay modali o vecchi fogli di stile con posizionamento assoluto possono coprire visivamente bottoni di menu o wizard. L'azione `click_uid`, basata su locator Playwright, puo' fallire se l'elemento e' coperto o intercettato da un overlay.

Il server dispone gia' di alternative utili:

- `click_uid`, che mantiene i controlli Playwright di visibilita' e actionability;
- `click_by_id`, che usa la mappa generata da `annotate` ed esegue un click programmatico;
- `evaluate_js`, che permette fallback manuali sul DOM.

### Linea Guida Implementativa nella Skill (`mcp-browser-automation`)

Aggiungere o rafforzare una sezione **"Overlay e Click Intercettati"**.

Sequenza consigliata:

1. Provare prima `click_uid` o un selettore CSS robusto.
2. Se il click fallisce per overlay/interception, raccogliere screenshot o `annotate` per identificare l'ostruzione.
3. Se l'overlay ha un pulsante di chiusura/accettazione, chiuderlo con un'azione normale.
4. Se si e' usato `annotate`, provare `click_by_id` come fallback controllato.
5. Usare `evaluate_js` solo come ultima istanza, esplicitando nel report che il click e' stato forzato via DOM.

### Fallback JS Esplicito

```javascript
(function() {
  const btn = document.getElementById('id_elemento') || document.querySelector('selettore_css');
  if (!btn) {
    return { status: 'not_found' };
  }

  btn.click();
  return {
    status: 'click_forced',
    tagName: btn.tagName,
    id: btn.id || null,
    text: (btn.innerText || btn.textContent || '').trim().slice(0, 120)
  };
})()
```

### Miglioria Opzionale Server

Valutare una action dedicata per overlay noti, ad esempio:

```json
{
  "action": "dismiss_overlay",
  "selector": ".cookie-banner button.accept"
}
```

Oppure una configurazione server con selettori noti per cookie banner/overlay legacy. La soluzione strutturata e' preferibile al click JS generico, perche' rende il workaround ripetibile e osservabile.

---

## 5. Task Implementativi Consigliati

### Server `playwright-node/index.js`

1. Aggiungere `SCREENSHOT_TIMEOUT_MS`.
2. Aggiungere `SCREENSHOT_FORCE_SYSTEM_FONTS`.
3. Aggiungere `SCREENSHOT_STYLE`.
4. Aggiungere helper `captureViewportScreenshot()`.
5. Usare l'helper in `browser_screenshot`.
6. Usare l'helper nello screenshot di `debug_bundle`.
7. Verificare che il salvataggio su `path` continui a funzionare.
8. Verificare che il buffer base64 continui a essere restituito nel case `browser_screenshot`.

### Skill `skills/mcp-browser-automation/SKILL.md`

1. Aggiornare il workflow CDP gia' presente con preferenza per `select_page_by_url`, `select_page_by_title` e `stable_page_id`.
2. Estendere la sezione iframe legacy con la gestione di iframe modali asincroni.
3. Aggiungere la sequenza fallback per overlay/click intercettati.
4. Specificare che il click via JS e' ultima istanza e va dichiarato nel risultato.

### Test Manuali Minimi

1. Pagina intranet/offline con riferimento a web font remoto non raggiungibile: `browser_screenshot` deve terminare entro `SCREENSHOT_TIMEOUT_MS`.
2. `debug_bundle` con `include_screenshot=true`: deve usare la stessa logica robusta.
3. `annotate`: se l'helper viene esteso anche a questo ramo, verificare che le etichette numeriche restino leggibili nello screenshot.
4. Chrome CDP con almeno due tab: dopo `attach_cdp`, selezionare tab corretta con `select_page_by_url` o `stable_page_id`.
5. Modale legacy con iframe iniziale `about:blank`: non navigare direttamente all'URL dell'iframe; usare `list_frames` e `select_frame` solo quando il frame e' pronto.
6. Pagina con overlay cookie: verificare fallback ordinato `click_uid` -> dismiss overlay -> `click_by_id` -> `evaluate_js`.

### Verifiche Automatiche Minime

Eseguire almeno:

```powershell
node scripts/check-tool-schemas.js
node tests/smoke/playwright-node.smoke.mjs
```

Esito implementazione core:

- `node scripts/check-tool-schemas.js`: eseguito, 39 tool verificati, 0 errori, 0 warning.
- `node tests/smoke/playwright-node.smoke.mjs`: eseguito, PASS su boot/handshake/tools list e scenari smoke inclusi.

Se la patch modifica solo documentazione e skill, e non `playwright-node/index.js`, e' sufficiente una review del Markdown. In questa iterazione e' stato modificato codice server MCP: dopo il merge serve riavviare il server `playwright-node` nel client MCP.

---

## 6. Priorita'

| Intervento | Tipo | Priorita' |
|---|---|---:|
| Helper screenshot centralizzato | Server | Alta |
| Applicazione helper a `debug_bundle` | Server | Alta |
| Valutazione helper su `annotate` | Server | Media |
| Rafforzamento CDP multi-tab nella skill | Skill | Alta |
| Gestione iframe modali asincroni | Skill | Media |
| Fallback overlay/click JS | Skill | Media |
| Action `wait_for_js` | Server, opzionale | Media |
| Action `dismiss_overlay` | Server, opzionale | Bassa/Media |

---

## Conclusione

La direzione corretta e' implementare prima una patch server piccola ma centralizzata per gli screenshot, poi aggiornare la skill per ridurre gli errori operativi dell'agente su CDP, iframe legacy e overlay.

La modifica screenshot deve essere trattata come bugfix tecnico. Le sezioni CDP/iframe/overlay sono invece linee guida operative per l'agente, con eventuali evoluzioni server successive (`wait_for_js`, `dismiss_overlay`) se i casi legacy diventano ricorrenti.
