substrat.cat
Processos

Playwright: automatitzar un navegador sense escriure el codi a mà

Codegen grava el que fas i te'l torna en codi. Com aprofitar-lo bé, què has de corregir sempre, i com connectar Playwright a un model amb MCP.

·9 min de lectura

Hi ha processos que no tenen API. El proveïdor et dona un portal web, has d’entrar cada dilluns, filtrar per data, descarregar un Excel i pujar-lo a un altre lloc. Ningú et donarà mai un endpoint per fer això.

Playwright automatitza exactament aquesta feina: controla un navegador de veritat —Chromium, Firefox o WebKit— des de codi. Va néixer per fer proves, però la meitat del que en veig fer avui és automatitzar portals que no tenen cap altra porta d’entrada.

I la millor part: no cal escriure el codi. Es grava.

Instal·lar-lo

Amb Node:

npm init playwright@latest

Amb Python:

pip install pytest-playwright
playwright install

El segon ordre descarrega els navegadors, que van a part i pesen. És el pas que la gent s’oblida.

Codegen: la manera d’entrar-hi

Aquesta és l’ordre que hauries de provar primer, avui:

npx playwright codegen https://el-portal-que-sigui.com

S’obren dues finestres: un navegador i l’Inspector. Tot el que facis al navegador —clicar, escriure, seleccionar, navegar— apareix escrit com a codi a l’Inspector, en directe.

Fes el procés sencer una vegada, a mà, com el fas cada dilluns. Quan acabis, copia el codi. Ja tens l’esquelet.

Els botons de l’Inspector que importen

  • Record (⏺) para i reprèn la gravació. Útil per no gravar el fet de buscar on eres.
  • Pick locator et deixa clicar un element de la pàgina i et dona només el localitzador, sense generar cap acció. És el que faràs servir més quan ja tinguis codi i n’hagis d’arreglar un tros.
  • Assert visibility / text / value afegeixen una comprovació. Si el que estàs fent és una automatització i no un test, també les vols: són el que fa que el procés s’aturi amb un error clar en comptes de continuar contra una pàgina que ha canviat.
  • El desplegable de dalt canvia el llenguatge de sortida: JavaScript, TypeScript, Python, Java o .NET. Es pot fixar des de la línia d’ordres amb --target python.

Les opcions de codegen que et faran falta de veritat

Gravar ja identificat. Si el portal té login, no vols gravar l’inici de sessió cada vegada, i menys encara deixar la contrasenya escrita al codi. El patró és aquest:

# Un cop: entres a mà i es desa la sessió
npx playwright codegen --save-storage=sessio.json https://portal.com/login

# A partir d'ara: s'obre ja identificat
npx playwright codegen --load-storage=sessio.json https://portal.com/informes

sessio.json conté galetes i emmagatzematge local. És una credencial: fora del repositori, i al .gitignore.

Altres que fan servei:

--device="iPhone 15"          # emula un mòbil complet
--viewport-size="1280,900"    # mida exacta de finestra
--lang=ca-ES                  # idioma del navegador
--color-scheme=dark           # mode fosc
--save-har=trafic.har         # desa tot el trànsit de xarxa

L’últim és una troballa: amb el HAR obert veus quines crides fa la pàgina per sota. Més d’una vegada descobreixes que darrere del portal hi ha una API JSON perfectament utilitzable, i que et pots estalviar el navegador sencer.

Gravar des d’enmig d’un script

Quan ja tens codi i vols afegir els passos següents sense repetir tot el principi, posa aquesta línia on vulguis continuar:

await page.pause();

En executar-lo, el navegador s’atura allà i s’obre l’Inspector amb la gravació a punt. Continues clicant i el codi nou s’afegeix a partir d’aquell punt.

El que codegen fa malament i has de corregir sempre

Codegen és un punt de partida excel·lent i un resultat final dolent. Tres correccions, sempre.

1. Els localitzadors febles

Playwright intenta identificar els elements per la seva funció, i quan pot, ho fa bé:

await page.getByRole('button', { name: 'Descarregar' }).click();
await page.getByLabel('Data des de').fill('2026-08-01');

Això aguanta un redisseny. Però quan la pàgina no té etiquetes ni rols decents, et donarà coses com:

await page.locator('div:nth-child(3) > .btn-wrap > button').click();

Aquest localitzador es trencarà el dia que algú afegeixi un div. L’ordre de preferència, de més robust a menys:

  1. getByRole('button', { name: '…' }) — el que veu un usuari.
  2. getByLabel('…') per a camps de formulari.
  3. getByPlaceholder('…'), getByText('…').
  4. getByTestId('…') si controles el codi de la pàgina.
  5. CSS o XPath: només si no queda cap altra opció.

Si el portal és d’un tercer i no hi ha res millor, ancora’l al text estable més proper:

await page.getByRole('row', { name: /Informe mensual/ })
          .getByRole('button', { name: 'Descarregar' })
          .click();

2. Les esperes que no calen (i les que sí)

Codegen a vegades escampa waitForTimeout. Esborra’ls tots. Playwright ja espera sol: abans de cada clic comprova que l’element existeix, és visible, és estable i no està tapat. Un waitForTimeout(3000) només fa que el procés sigui tres segons més lent i igual de fràgil.

Quan realment has d’esperar una cosa concreta, digues quina:

await expect(page.getByText('Informe generat')).toBeVisible({ timeout: 60_000 });
await page.waitForURL('**/descarregues');

Les assercions amb expect reintenten fins que es compleixen o s’acaba el temps. Això sí que és esperar bé.

3. Les descàrregues i els fitxers

Codegen grava el clic, però no què fer amb el fitxer. Aquest tros s’escriu a mà i és el que sol importar:

const [descarrega] = await Promise.all([
  page.waitForEvent('download'),
  page.getByRole('button', { name: 'Exportar' }).click(),
]);
await descarrega.saveAs('./sortida/' + descarrega.suggestedFilename());

El Promise.all no és un caprici d’estil: has de començar a escoltar l’esdeveniment abans de clicar, o te’l perds.

Quan es trenca: el visor de traces

Un procés automatitzat que corre de matinada falla, i tu no hi eres. La traça és la solució:

npx playwright test --trace on
npx playwright show-trace trace.zip

S’obre una finestra amb la línia de temps sencera: una captura de pantalla abans i després de cada acció, el DOM d’aquell moment que pots inspeccionar com si fossis al navegador, les crides de xarxa i la consola. Trobes la causa en un minut en comptes d’intentar reproduir-ho.

Per a scripts que no són tests:

await context.tracing.start({ screenshots: true, snapshots: true });
// … el procés …
await context.tracing.stop({ path: 'traca.zip' });

Deixa-ho activat sempre en producció. El fitxer pesa poc i el dia que falli t’estalviarà una tarda.

Playwright amb MCP: donar-li el navegador a un model

El Model Context Protocol és l’estàndard que permet a un model fer servir eines externes. @playwright/mcp és el servidor oficial que exposa un navegador com a eina.

claude mcp add playwright npx @playwright/mcp@latest

A partir d’aquí li pots dir al model «obre aquest portal, entra amb aquestes credencials, ves a informes i digues-me quines columnes té la taula», i ho fa de veritat.

La part tècnicament interessant: no funciona amb captures de pantalla. Fa servir l’arbre d’accessibilitat de la pàgina, és a dir, l’estructura semàntica que fa servir un lector de pantalla. El model rep una llista d’elements amb el seu rol i el seu nom, no píxels. Això el fa molt més barat en tokens, molt més fiable i determinista, i explica per què els localitzadors que en surten són bons: treballa amb la mateixa informació que fa servir getByRole.

Opcions que val la pena conèixer:

npx @playwright/mcp@latest \
  --headless \                      # sense finestra
  --isolated \                      # perfil net, res persisteix
  --storage-state=sessio.json \     # arrenca ja identificat
  --allowed-origins="portal.com" \  # només pot navegar aquí
  --save-trace                      # traça de tot el que faci

--allowed-origins és la que no t’has de saltar si li dones credencials reals.

On encaixa cada cosa

  • MCP és per explorar i construir: entendre com funciona un portal, provar si un flux es pot automatitzar, escriure el primer esborrany del script.
  • El script de Playwright és per executar: cada dilluns a les 7, sense ningú al davant, amb reintents i traça.

Fer córrer el procés diari a través d’un model és car, lent i no determinista. Fer servir el model per escriure el procés i deixar que després l’executi codi normal és exactament al revés de car, lent i no determinista.

El flux complet

Posant-ho tot junt, així és com es passa d’un procés manual a un d’automàtic en una tarda:

  1. npx playwright codegen --save-storage=sessio.json <portal> i fas el procés a mà un cop.
  2. Copies el codi i reescrius els localitzadors que hagin sortit per CSS.
  3. Esborres els waitForTimeout i poses assercions als punts on el procés ha de fallar si alguna cosa ha canviat.
  4. Afegeixes la gestió de descàrregues i la traça.
  5. Ho programes: cron, GitHub Actions, el que ja facis servir.
  6. Fas que t’avisi quan falli. Un procés automàtic que falla en silenci és pitjor que un de manual.

El pas 6 és el que separa una automatització que aguanta d’una que un dia deixa de funcionar i no te n’assabentes fins que algú et pregunta per l’informe.

El següent pas

Tens un procés
que odies fer?

Explica-m'ho i et diré si es pot automatitzar — i si no es pot, també t'ho diré. La primera conversa no es cobra — però el cafè el poses tu.

hola@substrat.cat