Playwright is Microsofts browser-automatiseringsframework: een Apache-2.0-library die eerst inzet op TypeScript, een echte browser opstart, die via één API aanstuurt en de pagina teruggeeft nadat JavaScript is uitgevoerd. Het wordt gepositioneerd als een end-to-end testframework, maar onder de motorkap grijpen veel mensen er stiekem naar zodra een HTTP-verzoek alleen een lege huls teruggeeft waar de data zou moeten staan. Qua opzet concurreert het met Puppeteer en Selenium — echte browsers die je scriptt, geen HTTP-clients die je parset.
Ik heb microsoft/playwright 1.56.0 door een vaste reeks scrapingtests gehaald — een statische catalogus met paginering, een artikel, een JavaScript-gerenderde catalogus, een JSON API, een kapotte 500, een kleine crawl-graph en twee publieke oefensites — op Node v22.22.3, macOS arm64, alleen Chromium. Het renderingdeel kwam netjes door. Het crawl-deel bestaat niet, en dat is precies het belangrijkste om te snappen aan deze tool vóór je eraan begint.
Wat opviel
Twee uitkomsten springen eruit, en die wijzen elk een beetje een andere kant op.
De eerste is het verwachte browserresultaat, binnen de waits die ik gebruikte. Na page.goto(..., { waitUntil: 'domcontentloaded' }) wachtte de dynamische fixturetest op #dynamic-products article.product-card, en de publieke test Quotes to Scrape wachtte op .quote. Die toepassingsspecifieke selectors verschenen, waarna de runs 8/8 fixture-items en 10 publieke quotes opleverden; het fixtureresultaat had een recall van 1.0 ten opzichte van de ground truth met acht items. Er was geen custom polling-loop nodig, maar Playwright nam het readiness-probleem niet weg — de test leverde zelf de voltooiingsvoorwaarde aan. Een volledige screenshot van de pagina werd ook meteen bij de eerste poging opgeslagen.
De tweede uitkomst gebruikte browserContext.request: specifiek ctx.request.get(...) nadat Chromium al was gestart en er al een browsercontext bestond. Daarmee werd het JSON-eindpunt van de fixture direct aangeroepen en kwam er 8/8 producten terug zonder een pagina te maken of te renderen. Dat slaat DOM-werk over, maar niet de kosten van het browserproces in deze testopzet. De request-client die aan een context hangt kan cookies delen met browserpagina’s; een losse playwright.request.newContext() vermijdt de noodzaak van een browsercontext, maar deelt die sessie niet automatisch. In deze test is alleen het eerste pad afgedekt.
Playwright levert ook geen crawl-queue, dataset-writer of automatische throttling. Mijn crawl-graphtest — interne links volgen, diepte bijhouden en revisits vermijden — kwam uit op 12 pagina’s over dieptes {0:1, 1:4, 2:7}, maar de breadth-first traversal was testcode die ik zelf schreef. Playwright opent en bekijkt pagina’s; frontier-opslag, URL-beleid, retries en planning horen bij een andere laag.
Wat Playwright eigenlijk is
De tool — microsoft/playwright op GitHub — is geschreven in TypeScript, gelicentieerd onder Apache-2.0 en wordt onderhouden door Microsoft. De build die hier is getest was 1.56.0 op 9 juli 2026. De resultaten gelden dus voor die build en moeten niet worden gelezen als een compatibiliteitsverklaring voor latere versies.
De officiële positionering is scherp: een framework voor webtesting en automatisering dat Chromium, Firefox en WebKit via één API aanstuurt. Het hoofdpad van Playwright is een test runner met fixtures, assertions en een trace viewer. Wie het als scraping-primitive gebruikt, volgt de gedocumenteerde Library mode: eerst chromium.launch(), dan een context, dan een page, buiten de test-harness om. Alles in deze review gebruikte die publieke API. Ik heb geen cross-version compatibiliteitstest uitgevoerd, dus dit is geen claim dat elk getest gedrag in alle releases stabiel blijft.
De gedocumenteerde breedte is elders het belangrijkste verkooppunt, dus ik formuleer het zorgvuldig. Playwright stuurt drie browser-engines aan — Chromium, Firefox en WebKit — via één API, en levert daarnaast eersteklas clients in Python, Java en .NET bovenop JavaScript. Dat is gedocumenteerd en het is ook echt het breedste onderscheidende kenmerk van de tool. Wat deze test daadwerkelijk heeft aangeraakt is kleiner:
| Mogelijkheid | Status in deze review |
|---|---|
| Chromium-engine | Getest — alle tests hier liepen op Chromium |
| Firefox-engine | Gedocumenteerd, hier niet geverifieerd |
| WebKit-engine | Gedocumenteerd, hier niet geverifieerd |
| Eén API over de drie engines | Gedocumenteerd, hier niet geverifieerd |
| Python-, Java- en .NET-clients | Gedocumenteerd, hier niet geverifieerd |
| Proxying | Niet getest |
| Schaal van parallelle contexts | Niet getest |
| Network interception voor API-first scraping | Niet getest |
Als een target er onder Safari’s WebKit anders uitziet, of als je team Python gebruikt, dan is juist die breedte Playwrights argument — maar zie mijn resultaten niet als bewijs voor parity met Firefox of WebKit, want die heb ik niet getest.
Hoe het onder de motorkap werkt
Het mentale model is een browser-engine die je scriptt. chromium.launch() start een browserproces. Een context is een geïsoleerde sessie met eigen cookies, storage en cache; een page is een tabblad binnen die context. Je roept page.goto(url) aan, wacht op de voorwaarde die applicatie-readiness weergeeft en leest daarna de resulterende DOM met helpers zoals page.$$eval. Dit ligt dichter bij een browser voor eindgebruikers dan bij het parsen van een HTTP-respons, maar het is niet hetzelfde als identieke omgevingscondities: headless-signalen, viewport, locale, fonts, profielstatus, TLS/netwerkpad en sitebeveiliging kunnen nog steeds bepalen wat er wordt teruggegeven. Deze review heeft anti-botgedrag of parity met een productiebrowser niet getest.
page.screenshot() legt de gerenderde pagina vast, volledig of uitgesneden, en dat werkte in mijn run al bij de eerste call. En de request-API die ik noemde — context.request.get — gebruikt de cookies van dezelfde context maar slaat het renderen over, zodat je in één script kunt mixen tussen “pagina laden en DOM uitlezen” en “gewoon het JSON-eindpunt aanroepen” zonder van tool te wisselen.
Wat er juist niet onder de motorkap zit, is crawl-infrastructuur. Er is geen request scheduler, geen blijvende visited-set, geen politeness policy en geen export-pijplijn. Een beperkte traversal is eenvoudig op te zetten, maar betrouwbaar frontier-werk vraagt ook om URL-normalisatie, redirect-afhandeling, retries, scope-regels, throttling en herstel bij fouten. Die laag bouw je zelf, of je gebruikt een framework dat de browser-engine voor je inpakt.
Installatie en setup in de praktijk
Installeren doe je in twee stappen, en de tweede draagt het meeste van de deployment-last. npm install playwright haalt de library binnen; een aparte npx playwright install downloadt de browserbuilds (in mijn geval Chromium). Reken dus op schijfruimte, downloadtijd, browser-caching in CI en procesbeheer, in plaats van de npm-package te zien als het complete draaiende systeem.
Als je Playwright installeert in de verwachting een scraper te krijgen en vervolgens de test-tutorial volgt, begin je met testbestanden en expect()-assertions. Scrapingcode gebruikt daarentegen rechtstreeks de library-API. Beide zijn gedocumenteerd, maar dat verschil is belangrijk als je voorbeelden zoekt en deployment-commando’s kiest.
In deze run waren de praktische voordelen concreet: browsercontexts isoleerden sessiestatus, async-calls liepen netjes in elkaar over, een screenshot kostte één call en een HTTP 500 bleef inspecteerbaar via het response-object. De hobbel zat vooral operationeel, niet syntactisch: de browserbuild moest apart geïnstalleerd worden en de levenscyclus ervan moest los van de library worden beheerd.
Resultaten in de praktijk

Alle lokale cijfers zijn gehaald op een fixture-server op 127.0.0.1, met de ground truth vooraf vastgelegd. De exacte harness staat in run_playwright_material_tests.mjs, en de gecommitte raw artifacts bevatten de ground truth en de output per test. Dit blijven observaties van één machine en één run; de links tonen de reproduceerbare ondergrond en maken er nog geen brede benchmark van.
| Test | Doel | Resultaat |
|---|---|---|
| Statische catalogus + paginering | lokale fixture | 12/12 producten, recall 1.0 |
| Artikel-extractie | lokale fixture | titel + 3/3 alinea’s, boilerplate apart gehouden |
| Dynamische JS-pagina (native render) | lokale fixture | 8/8, recall 1.0, full-page screenshot opgeslagen |
Dynamische JSON API (page.request) | lokale fixture | 8/8, recall 1.0, geen DOM gerenderd |
| HTTP 500-afhandeling | lokale fixture | status 500 inspecteerbaar, navigatie gooide geen fout |
| Crawl-graph (handgeschreven BFS) | lokale fixture | 12 pagina’s, dieptes {0:1, 1:4, 2:7} |
| Books to Scrape | publieke demo | 20 producten |
| Quotes JS (JS-gerenderd) | publieke demo | 10 quotes, native gerenderd |
De pagineringloop volgde expliciet de next-link; Playwright ontdekte de pagina’s niet zelf. De artikel-selector hield navigatie en footertekst buiten het body-resultaat. Op het foutpad gaf de navigatie een response-object terug met status 500 in plaats van een exception te gooien, zodat de caller zelf kon beslissen of er gelogd, opnieuw geprobeerd of doorgegaan moest worden. De twee publieke oefendoelen gaven de aantallen terug die in de tabel staan.
Een belangrijke grens moet je helder benoemen: alles hier draaide op Chromium, op één machine, één keer. De tabel met mogelijkheden maakt onderscheid tussen gedocumenteerde breedte en daadwerkelijk geteste functionaliteit. Ik heb de suite niet opnieuw gedraaid op een andere Playwright-build, dus daaruit volgt geen cross-version-conclusie. Ook per-test timings laat ik hier bewust weg als benchmark; een enkele stopwatch-run op één laptop zegt niets betrouwbaars over snelheid in het algemeen.
Readiness hoort bij de extractieafspraak
De dynamische resultaten waren afhankelijk van waits die de data zelf representeerden, niet alleen van browsernavigatie. Voor de lokale catalogus navigeerde de harness met waitUntil: 'domcontentloaded' en riep daarna waitForSelector('#dynamic-products article.product-card') aan met een timeout van 15 seconden. De publieke Quotes JS-run gebruikte dezelfde navigatiestatus en wachtte op .quote met een timeout van 20 seconden. Pas nadat die selectors verschenen, begon de extractie.
Dat onderscheid is belangrijk als je het script aanpast. domcontentloaded betekent dat het initiële document geparseerd is; het zegt niet dat een vertraagde API-response is aangekomen, dat hydration klaar is, dat een oneindige lijst is gestopt met groeien of dat een gevirtualiseerde rij in beeld is gekomen. Een selector is handig als de aanwezigheid van één passend element genoeg is. Als volledigheid juist afhangt van een bekende response, een aantal items, applicatiestatus of een stil netwerkvenster, wacht dan liever op die voorwaarde. De voorwaarde moet gekoppeld zijn aan het outputcontract: “er bestaat minstens één kaart” en “alle verwachte pagina’s zijn geladen” zijn niet dezelfde bewering.

Timeout-afhandeling hoort ook bij de caller. De test gebruikte eindige selector-timeouts, maar onderzocht geen retrybeleid en maakte geen onderscheid tussen een trage pagina en een selector die permanent veranderd is. Een production-wrapper zou moeten vastleggen welke readiness-voorwaarde faalde, genoeg paginastatus moeten bewaren om het probleem te diagnosticeren en moeten beslissen of een nieuwe navigatiepoging veilig is. Playwright geeft je de events en de DOM; het kan niet zelf afleiden wat “complete data” voor jouw klus betekent.
Die grens moet je naast elke extractor documenteren, niet als impliciete timeout laten bestaan.
Ook het API-pad heeft zo’n parallel contract. ctx.request.get was hier logisch omdat de browsercontext al bestond en sessiedeling nuttig kan zijn. Als een taak ontdekt dat het data-eindpunt prima werkt zonder browser-sessie, dan is een losse request-context een andere architectuur met een ander lifecycle- en cookiegedrag. Die twee heb ik in deze run niet vergeleken. Zie “geen DOM gerenderd” als het gemeten feit, en bepaal daarna apart of de bredere workflow überhaupt een browserproces nodig heeft.
De crawl-vraag
Het crawl-graph-resultaat is degene die bepaalt hoe je naar Playwright moet kijken. Twaalf pagina’s, drie dieptes, correct — en elke stap van het lopen was van mij. Playwright leverde het “open deze URL en lees hem uit”-deel; ik leverde de queue, de visited-set en de diepte-tracking.
Voor kleine, afgebakende jobs is dat geen probleem. Voor werk op crawlschaal betekent het dat je óf een crawler bouwt bovenop een browserlibrary, óf Playwright koppelt aan iets dat die laag al heeft. Het gedocumenteerde patroon is Crawlee, dat Playwright (en Puppeteer) omhult met een echte request queue, dataset-opslag en auto-throttling — je behoudt Playwrights rendering en leent de orchestratie. Als je liever een framework hebt waarin de queue zelf ingebouwd zit in plaats van achteraf aangeplakt, dan is Scrapy's ontwerp precies daarop gericht, al is Scrapy HTTP-first en rendert het zelf geen JavaScript. Het punt is niet dat Playwright tekortschiet; het punt is dat “browserautomatisering” en “crawlen” twee verschillende taken zijn, en Playwright claimt er maar één van.

Die BFS van twaalf pagina’s maakt de eigendomsgrens concreet. Hij leverde een queue, een visited-set en dieptetracking voor een gecontroleerde graph. Een production frontier moet nog steeds URL-canonicalisatie, redirect-afhandeling, toegestane hosts, duplicate keys, retries, concurrency, delay per host, persistentie en restart-semantiek definiëren. Export is opnieuw een keuze: de fixture schreef JSON en CSV omdat de harness dat deed, niet omdat Playwright een dataset-abstraction biedt.
Ook het sessieontwerp beïnvloedt de wrapper. Eén browser kan meerdere contexts bevatten met geïsoleerde cookies en storage, maar deze review heeft de schaal van parallelle contexts of foutisolatie niet gemeten. Een context hergebruiken kan een login behouden en setupwerk verminderen; aparte contexts kunnen state-lekkage tussen jobs voorkomen. Dat zijn beleidskeuzes op crawler-niveau, ook al levert Playwright de context-primitive. Benchmark de gekozen levenscyclus met de browserbuild en deploymentomgeving die je echt gaat gebruiken.
Plus- en minpunten
Pluspunten:
- JavaScript-uitvoering via een echte Chromium-engine; beide dynamische targets bereikten de selectors die als readiness-voorwaarde dienden.
- Full-page screenshot werd al bij de eerste call vastgelegd.
- Selectors haalden de verwachte velden uit de statische catalogus en het artikel in de gecontroleerde fixtures.
browserContext.requestbereikte het JSON-eindpunt zonder een pagina te renderen, terwijl het al gestarte browserproces deel bleef uitmaken van de harness.- Robuust bij een slechte response: HTTP 500 was inspecteerbaar en navigatie gooide geen fout.
- Gedocumenteerde ondersteuning voor drie engines (Chromium, Firefox, WebKit) via één API, plus Python-, Java- en .NET-clients (gedocumenteerd; hier alleen Chromium getest).
- Apache-2.0 en onderhouden door Microsoft.
- Fijne developer experience zodra je in library mode werkt: één API over engines, first-class async, simpele screenshots.
Minpunten:
- Geen ingebouwde crawl-queue, dataset of auto-throttle — crawl-werk op schaal is je eigen code of een wrapper zoals Crawlee.
- Zwaar browsergewicht: de binary-download en de kosten per pagina zijn de echte prijs ten opzichte van een HTTP-only tool.
- De standaardframing is de test runner; scrapen betekent dat je moet weten dat library mode bestaat en van het gemarketede pad af moet stappen.
- Alleen Playwright 1.56.0 en Chromium zijn getest; cross-version en cross-engine parity zijn niet onderzocht.
- Geen output in gestructureerde JSON op basis van schema’s uit zichzelf; je schrijft zelf de selectors en bepaalt de vorm van de data.
Voor wie het wel is, en wie beter kan doorlopen
Als je probleem is dat pagina’s pas data tonen nadat JavaScript is uitgevoerd, of als je screenshots samen met DOM-data wilt verzamelen, dan is Playwright een redelijke kandidaat om tegen je targets te reproduceren. Teams die al Playwright-tests gebruiken kunnen dezelfde concepten en selectorvaardigheden ook in library mode hergebruiken. Python-, Java- en .NET-clients zijn gedocumenteerde opties, maar deze review heeft alleen Node en Chromium getest.
Kijk in drie gevallen naar een andere laag. Als de benodigde data al in een HTTP-response zit, vermijd je met een HTTP-first tool de opstart- en rendering-overhead van een browser; Colly is een crawler-library in die categorie, terwijl Trafilatura zich richt op artikel-extractie. Als je queueing, persistentie en throttling nodig hebt, gebruik dan een crawlerframework of een Playwright-wrapper. Als je schema-gestuurde output wilt zonder selectors te onderhouden, vergelijk dan managed extraction services. Geen van die alternatieven is in deze review gebenchmarkt.
Als je specifiek moet kiezen tussen Playwright en Puppeteer, dan is dat een eigen head-to-head; onze side-by-side vergelijking draait beide door dezelfde fixtures en laat zien waar de keuze echt op uitkomt.
Alternatieven, en waar managed extraction past
Playwright is gratis, Apache-2.0 en self-hosted. Jij bent eigenaar van browserdeployment, selectors, readiness-voorwaarden, crawlcode, updates en foutafhandeling. Deze review heeft anti-botprestaties niet gemeten en ook de totale operationele kosten niet vergeleken met een managed service.
Binnen open source zijn de nuttige vergelijkingen functiegericht. Voor crawl-werk op schaal bovenop een browser voegt Crawlee de queue en dataset toe die Playwright mist. Als je outputdoel LLM-klare Markdown uit een echte browser is in plaats van handgevormde rijen, draait Crawl4AI een browser en produceert Markdown voor die pipeline. En als je meerdere van deze tools tegelijk afweegt, zet onze open-source scraper-roundup de categorieën naast elkaar.
Disclosure: Thunderbit is het product van de uitgever en is niet door deze Playwright-fixture gehaald. Het vertegenwoordigt de categorie managed extraction: de service verzorgt de rendering en levert pagina-tekst of schema-gestructureerde records terug, terwijl Playwright browserbediening en selectorlogica bij de ontwikkelaar laat. De vergelijking gaat dus over hostingmodel, outputvorm en kostenmodel — niet over een prestatiemetingsresultaat uit deze review.
Probeer Thunderbit voor webdata-extractie
Eindoordeel
Gebruik Playwright wanneer je target een browser-engine nodig heeft en je bereid bent om readiness-voorwaarden, selectors en crawl-orchestratie zelf te beheren. De resultatentabel laat zien dat de Chromium-librarymode in deze door de auteur uitgevoerde test netjes omging met de gecontroleerde statische, dynamische, API-, screenshot- en foutfixtures.
Bewaar de bewijsgrens wel intact: alleen Chromium en Node zijn getest, de wandeling van 12 pagina’s hing af van een handgeschreven BFS, browserContext.request sloeg het renderen van de pagina over maar niet het al draaiende browserproces, en elke dynamische extractie gebruikte een expliciete readiness-selector. Binnen deze review is Playwright daarmee een browser-primitive, geen gemeten end-to-end crawl-systeem.
Probeer Thunderbit voor webdata-extractie Get Started Free
Veelgestelde vragen
Heb ik nog steeds waits nodig als ik met Playwright scrape?
Ja. Het draaien van de browser vertelt je script niet wanneer de applicatiedata klaar is. In deze tests werd eerst genavigeerd naar domcontentloaded en daarna gewacht op een target-specifieke selector vóór extractie. Productiepagina’s kunnen een ander signaal nodig hebben, zoals een response, locator-state of applicatie-event.
Kan Playwright een hele website zelfstandig crawlen? Niet out of the box. Er is geen ingebouwde request queue, dataset-writer of auto-throttle — mijn crawl-graphtest kwam alleen uit op 12 pagina’s over dieptes {0:1, 1:4, 2:7} omdat ik de breadth-first search handmatig heb geschreven. Voor crawl-werk op schaal combineer je Playwright met Crawlee, dat er een echte crawllaag omheen zet, of gebruik je een crawlerframework.
Wanneer gebruik ik browserContext.request en wanneer een losse request-context?
Gebruik browserContext.request als HTTP-calls cookies moeten delen met pagina’s in een bestaande browsercontext. Gebruik playwright.request.newContext() als je een API-only context wilt zonder browser te starten en je geen automatische cookie-deling met browserpagina’s nodig hebt. Alleen het eerste pad is hier getest.
Zijn Firefox en WebKit hier getest? Nee. Alle tests draaiden op Chromium, op één machine, één keer. Playwrights ondersteuning voor drie engines (Chromium, Firefox, WebKit) en de Python-, Java- en .NET-clients zijn gedocumenteerde mogelijkheden die ik als zodanig rapporteer, niet geverifieerd — parity met Firefox en WebKit, proxying, parallelle schaal en network interception vallen allemaal buiten wat deze cijfers afdekken.
Welke omgeving dekte deze review? Playwright 1.56.0, Node v22.22.3, macOS arm64 en alleen Chromium. Firefox, WebKit, proxying, parallelle schaal, anti-botgedrag en latere Playwright-versies vielen buiten de run. Installatie vereiste de library plus een aparte download van browserbuilds.


