Prof. Dr.-Ing. Oliver Radfelder
Informatik / Wirtschaftsinformatik
Hochschule Bremerhaven
Kurz und Knapp: Playwright

Loggen Sie sich auf hopper ein. Gehen Sie in Ihr Homeverzeichnis, erzeugen Sie ein Verzeichnis myplaywright, wechseln Sie hinein und installieren Sie das playwright-Modul und die entsprechenden Abhängigkeiten.

mkdir myplaywright
cd myplaywright
npm init -y
npm install playwright@latest
npx playwright install
Öffnen Sie den Editor vim mit dem Dateinamen first.js und geben Sie den folgenden Code ein:

const playwright = require("playwright-core");
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
  const browser = await playwright.chromium.launch({});
  const context = await browser.newContext({});
  const page = await context.newPage();
  page.on("console", (msg) => {
    console.log(msg.text());
  });

  page.on("load", () => console.log("page loaded"));
  await page.goto(
    "https://informatik.hs-bremerhaven.de/docker-oradfelder-web/pd.html",
  );
  await delay(1000)

  await page.screenshot({ path: "playwright.png", fullPage: true });
  await browser.close();
}
main();

Starten Sie dann das Skript mit:

node first.js

Dann sollte ein Screenshot im aktuellen Pfad als png existieren.

Sie können auf das Erscheinen eines Elementes warten und Javascript-Code in den Ausführungskontext des Browsers injizieren:

  await page.waitForSelector('#header')
  await page.evaluate(()=>{
    document.querySelector('#header').innerHTML='neu'
  })

Mit setViewportSize können Sie die Größe des Browserfensters verändern:

  await page.setViewportSize ({width: 1920,height: 1080});

Mit dem Dollar-Operator verkürzt man die evaluate/querySelector-Schachtelung. Mit der Funktion type können Sie direkt in ein selektiertes Feld tippen - mit keyboard.press drücken Sie einzelne Tasten.

  const input = await page.$("input[name='thename']")
  await input.focus()
  await input.type('MOIN ',{ delay:100 })

  //vierfach-Klick zum Markieren
  await input.click({ clickCount: 4 })
  await new Promise(e => setTimeout(e, 1000));
  
  await page.type('input[name=thename]', 'moin', {delay: 100})
  await page.keyboard.press('Enter')

Üblicherweise wird man auch im headless-Modus die Ausgaben auf der Konsole im Browser abfangen wollen. Wieder ist der Kontext unterschiedlich und es muss das jeweilige Ereignis von der einen Konsole abgefangen und weitergeleitet werden:

  page.on('console', msg => {
    console.log("console:"+msg.text())
  });

Bisweilen ist es notwendig, bestimmten Code beim Laden des Dokumentes, jedoch vor dem Ausführen von Script-Tags auszuführen:

    await page.addInitScript(() => {
      Object.defineProperty(navigator, "languages", {
          get: function() {
              return ["en-US", "en"];
          }
      })
    });

Mit exposeFunction lässt sich eine Funktion nachträglich in den Javascript-Kontext des Browser injizieren:

    let outer='outerValue'
    function outerFunction(){
      console.log("called outerFunction from Browser Context")
    }


    await page.exposeFunction('myFunction', (arg) => {
      console.log(`Called with: ${arg} outer:${outer}`);
      outerFunction();
    });
    await page.evaluate(()=>{ myFunction('myArg');});

Mit Selektoren lässt sich fokussieren, anklicken, etc

    await page.focus('input[name=submitter]')
    await new Promise(e => setTimeout(e, 1000));
    await page.locator('input[name=submitter]',(e)=>e.click())
    // oder
    const submitter = await page.locator('input[name=submitter]')
    await submitter.click()

Einzelne Ereignisse lassen sich abfangen:

  page.on('load', () => console.log('page loaded'));
  page.on('frameattached',() => console.log('attached'));
  page.on('framenavigated', () => console.info('frame navigated'));
  page.on('dialog', async dialog => {
    console.info(`dialog: ${dialog.message()}`);
    await dialog.dismiss();
  });
  page.on('request', request => console.info(`request: ${request.url()}`));
  page.on('requestfinished', request => console.info(`request finished: ${request.url()}`));
  page.on('response', response => console.info(`response: ${response.url()}`));
  page.on('workercreated', worker => console.info(`worker created: ${worker.url()}`));
  page.on('workerdestroyed', worker => console.info(`worker destroyed: ${worker.url()}`));
  page.once('close', () => console.info('page closed'));

Der Datei-Download ist bei Playwright eher einfach:

  const downloadPromise = page.waitForEvent("download");
  await page.getByText("download").click();
  // Wait for the download process to complete
  const download = await downloadPromise;
  console.log(await download.path());
  // Save downloaded file somewhere
  await download.saveAs("download.png");

Der Upload - typischerweise per Ajax - funktioniert einfach , wenn sich die Seite entsprechend verhält: Wenn der Upload fertig ist, sollte irgendetwas an der Seite sich ändern.

  await page.waitForSelector('input[type=file]');
  await page.locator('input[type=file]').setInputFiles("playwright.png")
  await page.locator("#uploader").click();

  await page.waitForSelector('#uploadedlink');

Einige weitere hilfreiche Funktionen:

  // warte bis der Ausdruck wahr wird
  await page.waitForFunction('counter>3');

  // injiziere Script-Tag vom Server
  await page.addScriptTag({url:"pd-script.js"})

  // ... oder lokal
  await page.addScriptTag({path:"pd-localscript.js"})
  // globale Variablen müssen dort über window. ...
  // gesetzt werden

  // click und warte auf Navigation
  const [response] = await Promise.all([
    page.waitForNavigation(),
    page.click("a.mylink"),
  ]);
  // bzw:
  const elements = await Promise.all([
    page.waitForNavigation(),
    page.click("a.mylink"),
  ]);
  console.log(elements[0])

  // den Inhalt der Seite abholen
  let content=await page.content()

  // cookies lesen
  let cookies=await page.context().cookies()


Im Gegensatz zu Puppeteer lässt sich Playwright auch gut aus Java und Python heraus nutzen. Insgesamt lohnt es sich aber sicher, aus Javascript heraus zu arbeiten - das ist schließlich die originäre Sprache des Webs.

Wenn playwright im eigenen Docker oder in der eigenen Ubuntu-VM installiert werden soll, fehlen vermutlich noch einige Abhängigkeiten im System. Das wird am besten mit npx playwright install-deps nachgeholt - dann natürlich als root-User.

Weiterführendes