Tartalomjegyzék

Playwright

Mi az a Playwright?

Playwright egy nyílt forráskódú (open-source) eszköz, amely:

  • automatizálja a webes alkalmazások tesztelését,
  • támogatja a több böngészőt (Chromium, Firefox, WebKit),
  • több nyelven is használható: JavaScript, TypeScript, Python, Java, C#.

Fő előnye a Seleniumhoz képest: Gyorsabb, stabilabb és könnyebb párhuzamosan futtatni a teszteket.

Videós kurzusok

Playwright Beginner Tutorials

Playwright TypeScript Tutorial for Beginners

Playwright Javascript Tutorial

Telepítés

Visual Studio Code

Playwright Test for VSCode

Node.js

Git

Verziókezelő program - Git Fork

Engedélyezni kell, hogy a rendszer ne tiltsa le a szkriptek futtatását ezzel: Powershellben rendszergazdaként: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Terminálba: npx playwright install

Ellenőrzés terminálban: node -v npm -v

Ha kiírja mindkettő után a verzió számokat, akkor sikeres volt a telepítés.

Projekt létrehozása + workflow nélkül

Lépj bele a terminálban a mappába ahova a tesztet szeretnéd.

A mappa egyben a projekt neve lesz. pl.: for-test A verzió követést, úgy tudod beállítani, hogy beírod a terminálba, miután beléptél a mappába,hogy "git init".

Playwright projekt létrehozása: Terminálba: npm init playwright@latest

A zárójelben lévőket válaszold a terminál kérdéseire. Do you want to use TypeScript or JavaScript? Typescript (enter), Where to put your end-to-end tests? Marad a test mappa az alap értelmezett (tab és enter). Fontos, hogy csak egy test mappád legyen. Add a GitHub Actions workflow? (n) // Ha szeretéd, hogy automatikusan lefussanak a tesztek, amikor commitolsz, akkor y-t válaszd. Install Playwright browsers? (y)

Példa: https://github.com/Streptopelia-risoria/restful_booker_platform_demo

Ha nyilvános projekt, akkor ezt érdemes beállítani: https://app.codacy.com/ -n adjuk hozzá a repot, hogy elemezze a kódot, amit írunk. Figyelmeztet és kb tutorként segít, hogy ne legyen spagetti kód.

Workflow

Ha kéne workflow, hogy a GitHub Actions automatikusan elindítsa a teszteket, amikor commitolsz, akkor a lentieket állítsd be.

GitHub Actions

Ennek meg kell lennie: .github/workflows/playwright.yml

A szóközöre és tabokra nagyon figyelj.

name: Playwright Tests
on:
  push:
    branches: [main, master] # Ha más a branch neve, akkor írd át.
  pull_request:
    branches: [main, master] # Ha más a branch neve, akkor írd át.
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      - name: Run Playwright tests
        run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Ez az útmutató, hogy mi alapján futtassa a GitHub a teszteket.

Amikor a terminálban telepítetted a playwright-ot (npm init playwright@latest), akkor yes-t kellett nyomni a GitHub Action-s kérdésre, de a fenti workflow-al ezt tudod bepótolni.

A workflow eredményét, hogy passed/failed megtudod jeleníteni a README-dben. Felülre ezt másold be és persze írd át a felhasználónevet és a reponevét.

![Workflow neve](https://github.com/FELHASZNALONEV/REPO_NEVE/actions/workflows/playwright.yml/badge.svg)

ESLint beállítása

Terminálban futtasd ezt: npm install -D eslint-plugin-playwright npm install -D typescript-eslint

Konfigurációs fájl létrehozása:eslint.config.js.

Hozz létre egy eslint.config.js nevű fájlt a projekt gyökérmappájában, és illeszd bele ezt a kódot:

import playwright from "eslint-plugin-playwright";
import tseslint from "typescript-eslint";

export default [
  ...tseslint.configs.recommended,
  {
    files: ["tests/**/*.ts", "tests/**/*.js"],
    ...playwright.configs["flat/recommended"],
    rules: {
      "playwright/no-wait-for-timeout": "warn",
    },
  },
];

Bővítmény letöltése. Ctrl+Shift+X és keress rá az ESLint-re. ESLint bővítmény letöltése

settings.json megnyitása: A szerkesztő beállításai.Nyomd meg a Ctrl + Shift + P gombkombinációt a parancssor megnyitásához. Kezd el gépelni: Preferences: Open User Settings (JSON) (vagy ha csak a projektre szeretnéd alkalmazni, akkor Open Workspace Settings (JSON)).Nyomd meg az Enter-t

Automatikus javítás beállítása: Fájlmentési szabályok.Lásd el a megnyílt settings.json fájlt az alábbi beállításokkal (vagy illeszd be őket a meglévő JSON objektumon belülre):

{
  // Mentéskor automatikusan lefutnak az ESLint auto-fix szabályai
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "always"
  },
  // Jelzi az ESLint-nek, hogy a TypeScript és JavaScript fájlokat is ellenőrizze
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}

Futtatás: npx eslint tests/

Saját weboldal tesztelése

A lenti beállításokat, akkor kell megcsinálnod, ha a saját weboldaladat hostolod és azt akarod tesztelni.

playwright.config.js

A GitHub Actions egy üres szerveren fut, ahol nincs elindítva a Live Server, ezért a playwright.config.js fájlba a lentit írd bele, amivel megmondjuk a Playwright-nak, hogy indítsa el a webszervert a tesztek előtt.

export default defineConfig({
  use: {
    //Fontos, hogy ne duplikáld a use részt!
    //... korábbi sorok
    baseURL: "http://127.0.0.1:8080", // Így nem kell mindig beírni.
    actionTimeout: 10000, // 10 másodperc minden kattintásra/gépelésre
    navigationTimeout: 15000,

    // ... többi sorok
    trace: "on-first-retry",
  },

  //... korábbi sorok
  /* Run your local dev server before starting the tests */
  webServer: {
    command: "npm run start",
    url: "http://127.0.0.1:8080",
    reuseExistingServer: !process.env.CI,
    stdout: "ignore",
    stderr: "pipe",
  },
  // ... többi sor
});

A "projects: [..]" részen kommenteld ki a safarit és a firefox-ot, ha nem akarod a gépedre is telepíteni azokat.

package.json

A gyökér könyvtárban lévő package.json-t ki kell egészíteni a playwright függőséggel:

{
{
  }
 //... korábbi sorok
  ,
  "devDependencies": {
    "@playwright/test": "^1.44.0"
  }
// ... többi sor
}

Valamint ennél:

  "scripts": {},

Javítsd ki erre:

"scripts": {
  "test": "npx playwright test",
  "start": "servor . 8080 --reload"
} //Duplikálni nem szabad.

Fontos , hogy a port számnak mindenhol egyeznie kell.

Terminálba: npm install --save-dev servor

Ezek után még csekkold le, hogy tuti nem a live servert akarja használni playwright. Terminálba írd be ezt: npx playwright test

Ha hiba van írd be ezt: npx playwright show-report Meg tudod nézni részletesen a hibát.

Ezután jöhet a commitolás a master/main-be. "Add GitHub Actions workflow" címmel.

Ezután a GitHub-on az adott reponál az Actions lapfülön láthatod, hogy sikerült-e a teszt.

Teljes teszthez

Terminálban: Ezzel csak a chromium típusú böngészőben futtatod a tesztet. npx playwright test --project=chromium

npm run start

Miután elindult minden a leállításhoz a terminálban nyomd meg a ctrl+C-t. npx playwright test

Többi hasznos terminál parancs a teszteléshez: npx playwright test --ui npx playwright test --debug

Első teszt

Record and play

Bal oldalt válaszd ki a lombik ikont.

TOOLS (Eszközök)

Pick locator: Ez a legpraktikusabb. Kattints rá, menj át a böngészőbe, és mutass rá az egérrel egy elemre (pl. a gombra vagy combo boxra). A VS Code-ban azonnal megjelenik a kód, amivel az adott elemet eléred.
Record new: Elindít egy üres tesztet és egy böngészőt. Amit a böngészőben csinálsz, azt élőben kódként rögzíti egy új fájlba. Látni fogod, hogy az Inspector ablakban automatikusan generálódik a kód.
Ha végeztél, csak zárd be a böngészőt, és a VS Code-ban ott lesz az új tesztfájl a kész kóddal! Bal oldalt frissítsd a test explorert és nyisd le a test részt ott láthatod az imént felvitt tesztedet.

Record at cursor: Ugyanaz, mint az előző, de nem új fájlt nyit, hanem a meglévő kódodba, a kurzor pozíciójához szúrja be az új lépéseket.

A fájlnevének mindig , így kell kinéznie: valami.spec.ts

Terminálba: npx playwright codegen

Részletesebben elmagyarázva az első teszt

my_first_test.spec.ts:

import { test, expect } from "@playwright/test";

/*
import { hello, helloworld } from './demo/hello';


console.log(hello());
console.log(helloworld());

// Futtatás terminálban:
// npx playwright test tests/my_first_test.spec.ts   

*/

/* async hozzáadása:
A Playwright tesztekben a böngésző automatizálásához (pl. kattintások, gépelés, oldalak betöltése) elengedhetetlen, hogy a tesztfüggvény aszinkron legyen.
*/
//Ha egy függvény elé odateszed az async szót, azzal jelzed a programnak, hogy ez a függvény "időigényes" feladatot fog végezni (pl. letölt egy weboldalt, vár a böngészőre). Az await-et, csak async metódusban lehet használni.
test("My First Test", async ({ page }) => {
  // Egy függvény (vagy művelet) elé írt await kulcsszó arra kényszeríti a kódot, hogy megvárja a Promise (ígéret) teljesülését.
  await page.goto("https://google.com");

  await expect(page).toHaveTitle("Google");
});

hello.js:

exports.hello = function f1() {
  return "hello";
};

exports.helloworld = function f2() {
  return "hello word";
};

Futtatás

Bal oldalt lombik ikon, aztán Play vagy Debug Test.

Vagy

Utána megjelenik a report automatikusan: npx playwright test

npx playwright test --ui npx playwright test --debug npx playwright test --project=galaxy-tab --headed

Pl.: galaxy-tab -on, csak akkor tudsz tesztelni, ha be van állítva az eszköz a playwright.config.ts fájlban.

Ezzel csak a chromium típusú böngészőben futtatod a tesztet. npx playwright test --project=chromium

Ez a parancs pontosan 3 párhuzamos worker folyamatot (Node.js processzt) indít el a Playwright tesztek futtatásához, felülbírálva a konfigurációs fájlban megadott alapértelmezett értéket:

npx playwright test --workers 3

Így látszik a böngésző is: npx playwright test --headed

Ezzel megjelenik a Playwright Inspector ablak is: npx playwright test --debug

Egy teszt futtatása (Nem kell végig írni a fájlt nevet elég a tabot és a jobbra nyilat nyomni.): npx playwright test tests/my_first_test.spec.ts --headed

npx playwright test tests/my_first_test.spec.ts

Record and playhez (ellenőrzéseket is könyebb vele létrehoz az 'ab' gombbal): npx playwright codegen Utána másold ki és mentesd el egy fájlba.

Ezzel már rögtön meg is tudsz nyitni konkrét weboldalt: npx playwright codegen google.com

Előre létre kell hozni a fájlt: npx playwright codegen --target typescript -o .\test\new_test.ts

A fentieket egy sorban is lehet használni pl.: npx playwright test tests/my_first_test.spec.ts --headed --debug --project=chromium

Jelszó beállítása környezeti változóként

.env fájlbe írd be e felhasználónevet és jelszót.

A gitignore-ba meg, hogy ".env" be kell írni, hogy ezt nem kell verziókezelés alávonni. GitHub esetén érdemes a GitHub-on secret-et létrehozni és nem feltölteni a .env fájlt.

A .env fájlt a package.json mellé rakd.

.env fájlt tartalma

BASE_URL=https://example.com/
USERNAME=Admin
PASSWORD=admin

Gitignore fájl tartalma:

# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
.env

Terminálba: npm install dotenv --save-dev

playwright.config.ts fájlba:

// Felülre.
// Env fájl használatához kellenek.
import dotenv from "dotenv";
import path from "path";


// .env fájl betöltése
dotenv.config({ path: path.resolve(__dirname, ".env") });

// A use részre másold be ezt:

// Használhatod a BASE_URL-t közvetlenül a configban:
        baseURL: process.env.BASE_URL,

Minta

import { test, expect } from "../fixtures/BaseTest";

test("Successful login test", async ({ page, loginPage, dashBoardPage }) => {
  await loginPage.gotoLoginPage();
  await loginPage.login(process.env.USERNAME!, process.env.PASSWORD!);
  await expect(dashBoardPage.fullNameLabel).toBeVisible();
  await dashBoardPage.logout();
});

Jelszó beállítása JSON-ként

A gyökér könyvtárban hozz létre egy data mappát benne egy loginData.json fájllal.

data/loginData.json fájl tartalma:

{
  "valid_user": {
    "username": "standard_user",
    "password": "secret_sauce"
  },
  "invalid_user": {
    "username": "locked_out_user",
    "password": "secret_sauce"
  }
}

A tesztekben így használt a felhasználónevet és a jelszót:

import { expect, test } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
import loginData from "../data/loginData.json";

test("valid login test", async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.gotoLoginPage();
  await loginPage.login(
    loginData.valid_user.username,
    loginData.valid_user.password,
  );

  await expect(page).toHaveURL("https://www.saucedemo.com/inventory.html");
});

test("invalid login test", async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.gotoLoginPage();
  await loginPage.login(
    loginData.invalid_user.username,
    loginData.invalid_user.password,
  );

  await expect(loginPage.errorMessage).toBeVisible();
});

Tracer View

Fail esetén készít egy trace.zip-et

A playwright.config.ts-ben írd át a trace: on-first-retry, -t trace: retain-on-failure, -ra és így a test-results mappában lesznek trace.zip-ek.

Az automatikusan megnyitott report alján is láthatod a trace.zip-edet, katt rá. A lap füleken meg nézheted az előtte és az utána képet és a forráskódot is láthatod.

Később így is megnyithatod a tracer view-t: npx playwright show-trace ./test-results/my_first_test-My-First-Test-chromium/trace.zip

Egy konkrét teszt esetén készít egy trace.zip-et

A playwright.config.ts-ben alapértelmezés szerint legyen beállítva a trace, vagyis trace: 'on-first-retry',.

import { test, expect } from "@playwright/test";

// 1. Csak a 'context' fixture-t kérjük el
test("SauceDemo", async ({ context }) => {
  // 2. Elindítjuk a tracinget a kontextuson
  await context.tracing.start({
    snapshots: true, // Pillanatképek rögzítése az akciókhoz
    screenshots: true, // Képernyőképek készítése
  });

  // 3. A kontextusból hozzuk létre a page objektumot
  const page = await context.newPage();

  await page.goto("https://www.saucedemo.com/");

  await page.locator('[data-test="username"]').click();
  await page.locator('[data-test="username"]').fill("standard_user");

  await page.locator('[data-test="password"]').click();
  await page.locator('[data-test="password"]').fill("secret_sauce");

  await page.locator('[data-test="login-button"]').click();

  await page.getByRole("button", { name: "Open Menu" }).click();

  await page.locator('[data-test="logout-sidebar-link"]').click();

  await expect(page).toHaveURL("https://www.saucedemo.com/");

  // 4. Trace leállítás
  await context.tracing.stop({ path: "test-trace.zip" });
});

Így tudod megnézni a Tracernpx playwright show-trace ./test-trace.zip

Általában ezeket egy közös BaseTest.ts-be szokták rakni, így:

let context;
let page;

test.beforeAll(async ({ browser }) => {
  context = await browser.newContext();

  await context.tracing.start({
    snapshots: true, // Pillanatképek rögzítése az akciókhoz
    screenshots: true, // Képernyőképek készítése
  });
  page = await context.newPage();
});

test.afterAll(async ({ browser }) => {
  await context.tracing.stop({ path: "test-trace.zip" });
});

Inspector használata

Így lehet szüneteltetni a tesztet.

await page.pause();

Terminálba:

npx playwright test ./tests/login_demo.spec.ts --project chromium --headed

Assert-t, vagyis ellenőrzéseket is létre lehet hozni, valamint a 'Pick locator'-al lehet lokátort is keresni.

'Record' gombbal lehet a felvételt elindítani és utána ki kell másolni a generált kódot a testbe.

selectors.spec.ts:

import { test, expect } from "@playwright/test";

test("Selectors Demo", async ({ page }) => {
  await page.goto("https://www.saucedemo.com/");
  await page.pause();
  // npx playwright test ./tests/selectors.spec.ts --project chromium --headed

  // Jobb klikk az adott komponensen és Inspect
  // F12-n jobb klikk az adott kompenensen és copy -> Copy selector

  // using any object property
  await page.click("id=user-name");
  await page.locator("id=user-name").fill("Edison");
  await page.locator("[id=user-name]").fill("E");

  // using CSS Selector
  await page.locator("#login-button").click();

  //using Xpath
  await page.locator('xpath=//input[@name="password"]').fill("Freddy");
  await page.locator('//input[@name="password"]').fill("Raman");

  // using Text
  await page.locator("text=LOGIN").click();
  await page.locator('input:has-text("Login")').click();
});

Névnélküli függvény lambda kifejezéssel

A lambda kifejezés (más néven nyíl függvény vagy arrow function) nem más, mint egy gyorsítósáv a függvények írásához.

Ahelyett, hogy hosszan gépelnél, egy () => szimbólummal azonnal létrehozhatsz egy függvényt.

  1. Mit jelent az, hogy „Névnélküli”?

A hagyományos függvényeknek szokott lenni neve, hogy később újra elő tudjuk őket hívni (mint egy receptet a szakácskönyvből):

// Hagyományos, nevesített függvény:
function udvozles() {
  console.log("Szia!");
}

A névnélküli (anonim) függvénynek nincs neve. Azért nincs rá szükség, mert ott helyben, azonnal felhasználjuk, és soha többé nem akarunk rá hivatkozni máshol. Olyan, mint egy eldobható papírpohár.

  1. Hogyan jön ide a nyíl => (lambda)?

A modern JavaScriptben a function szócska helyett bevezették a nyíl (=>) jelölést, mert sokkal rövidebb.Nézzük meg ugyanazt a névnélküli függvényt a régi és az új (lambda) módszerrel:

Régi módszer (név nélkül):

function() { ... kód ... }

Új, lambdás módszer (név nélkül):

javascript() => { ... kód ... }
  1. Hogyan működik ez a tesztünkben?

A Playwright teszted valójában így néz ki:

test("Demo Login Test 1", async ({ page }) => { ... });

Ez a sor azt mondja a Playwrightnak: „Szia! Kérlek futtasd el ezt a tesztet 'Demo Login Test 1' néven. És hogy mit csináljon a teszt? Itt van ez a névnélküli függvény (a nyíllal), ebbe csomagoltam bele a lépéseket, ezt hajtsd végre!”

  • A () zárójelbe mennek a bemeneti adatok (esetedben a { page }).

  • A => nyíl mutatja, hogy „ebből az adatból ez a kód fog lefutni”.

  • A { } kapcsos zárójelek közé pedig magát a tesztet írod.

3 db egyszerűbb Login Test

login_demo.spec.ts:

import { test, expect } from "@playwright/test";

test("Demo Login Test 1", async ({ page }) => {
  await page.goto("https://demo.applitools.com/");

  await page.pause();

  /* Terminálba:
     npx playwright test ./tests/login_demo.spec.ts --project chromium --headed
     */

  await page.getByRole("textbox", { name: "Enter your username" }).fill("Jane");
  await page
    .getByRole("textbox", { name: "Enter your password" })
    .fill("Secret password");

  // 5 másodpercet várunk a gombra.
  await page.waitForSelector("text=Sign in", { timeout: 5000 });

  // Pontosan egy darab ilyen gombnak kell lennie.
  await expect(page.locator("text=Sign in")).toHaveCount(1);
});

test("Demo Login Test 2", async ({ page }) => {
  await page.goto(
    "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login",
  );

  await page.pause();

  await page.getByRole("textbox", { name: "Username" }).fill("Admin");

  await page.getByRole("textbox", { name: "Password" }).fill("admin123");
  await page.getByRole("button", { name: "Login" }).click();

  await page.locator("span").filter({ hasText: "John Doe" }).click();
  await page.getByRole("menuitem", { name: "Logout" }).click();
});

// only: Csak ez a teszt fog lefutni.
test.only("Demo Login Test 3", async ({ page }) => {
  await page.goto("https://admin-demo.nopcommerce.com/admin/");

  await page.pause();

  await page
    .getByRole("textbox", { name: "Email:" })
    .fill("admin@yourstore.com");

  await page.getByRole("textbox", { name: "Password:" }).fill("admin");
  await page.getByRole("button", { name: "Log in" }).click();

  // Utána Cloudflare botvédelem jelenik meg.
  await page.close();
});

Lokátorok

A Playwright lokátorok (locators) olyan objektumok, amelyek megmondják a Playwrightnak, hogy pontosan melyik HTML elemmel (gomb, beviteli mező, szöveg stb.) szeretne interakcióba lépni a weboldalon.

A Playwright lokátorok egyik legnagyobb előnye, hogy automatikusan bevárják (auto-wait) az elemet. Ez azt jelenti, hogy a Playwright ellenőrzi, hogy az elem látható és kattintható-e, mielőtt bármit is csinálna vele.

Íme a legfontosabb lokátortípusok és azok magyarázata a könnyebb megértéshez.

Stabilitási sorrend: id > cssSelector > xpath


1. Felhasználó-központú lokátorok (Ajánlott kezdetnek!)

A Playwright arra ösztönöz, hogy úgy keressünk elemeket, ahogyan egy ember látja azokat a képernyőn, nem pedig bonyolult technikai leírások alapján.

getByText()

Szöveg alapján keresi az elemeket a képernyőn.

  • Példa: await page.getByText('Bejelentkezés')

getByRole()

Szerepkör (gomb, jelölőnégyzet, űrlap, fejléc) és a hozzá tartozó felirat alapján keres. Ez a legbiztonságosabb és leginkább hozzáférhető (accessibility) módszer.

  • Példa: await page.getByRole('button', { name: 'Mentés' })

getByLabel()

Címke (label) alapján keres beviteli mezőket.

  • Példa: await page.getByLabel('Jelszó')

getByPlaceholder()

A beviteli mezőkben halványan látható, kitöltést segítő szöveg alapján keres.

  • Példa: await page.getByPlaceholder('Email cím')

2. Hagyományos lokátorok

Ha a fenti, felhasználó-központú módszerekkel nem boldogulunk (pl. mert az elemnek nincs felirata), használhatjuk a webfejlesztésben megszokott hagyományos módszereket.

CSS Selector

Szelekciós nyelvet alkalmazó lokátorok. A page.locator('css-szelektor') metódussal használjuk (osztálynév ., vagy azonosító #). Ha egy elemnek több osztálya van és a HTML-ben szóköz választja el őket, a szelektorban pontot kell tenni a szóközök helyére (pl. class="btn primary" -> .btn.primary).

  • Példa: await page.locator('.high-light-text')

Ugyanazt a nyelvet használja, mint a CSS az elemek formázására:

  • # $\rightarrow$ ID alapján
  • . $\rightarrow$ osztály (class) alapján
  • [attribútum='érték'] $\rightarrow$ attribútum alapján
  • tag.class $\rightarrow$ tag + osztály kombinációja
  • <szóköz> $\rightarrow$ leszármazott (bármilyen mélységben)
  • > $\rightarrow$ közvetlen leszármazott (gyermek elem)
  • *='<érték>' $\rightarrow$ attribútumot tartalmazza (részleges egyezés)
  • ^= $\rightarrow$ az attribútum értéke ezzel kezdődik
  • $= $\rightarrow$ az attribútum értéke ezzel végződik

Hátránya: Nem tud a DOM struktúrában visszafelé (felfelé, a szülő irányába) l

XPath

Abszolút XPath

Egyetlen / -el kezdődik.

Működése: A dokumentum legfelső gyökerétől (a gyökérelemtől) indul, és pontosan leköveti a teljes hierarchikus utat a célelemig.

Hátránya: Nagyon törékeny. Ha a HTML/XML struktúrában bárhol beszúrnak vagy eltávolítanak egy köztes elemet (pl. egy új <div>-et), a teljes útvonal érvénytelen lesz, és a teszt vagy lekérdezés elromlik.

Relatív XPath

A page.locator('xpath=//...') vagy egyszerűen page.locator('//...') kifejezésekkel használjuk. Ezt akkor érdemes alkalmazni, ha egyedi logikára van szükség, de a bonyolultsága miatt érdemes kerülni, ha van egyszerűbb megoldás.

  • Példa: await page.locator('//button[@id="submit-btn"]')

Útvonal alapú lekérdező nyelv, segítségével a DOM-ban adhatunk meg logikai útvonalat a HTML hierarchikus szerkezetét kihasználva. A dokumentum bármely pontján képes keresni az elemet, függetlenül attól, hogy az hol helyezkedik a DOM fában. Közvetlenül megcélozhatjuk a keresett elemet attribútumok (pl. id, class) vagy szöveg alapján.

  • Erőssége: Ha nincs egyedi azonosító, akkor is stabil útvonalat biztosít az elemek egymáshoz képesti kapcsolatain keresztül.
  • Képes a dokumentum struktúrájában minden irányban mozogni (felfelé és lefelé is).

Kulcsjelei:

  • / $\rightarrow$ közvetlen útvonal (gyermek)
  • // $\rightarrow$ bárhol a dokumentumban (leszármazott)
  • @ $\rightarrow$ attribútum jelölése
  • [] $\rightarrow$ feltételek, szűrések helye
  • . $\rightarrow$ aktuális elem
  • .. $\rightarrow$ szülő elem
  • * $\rightarrow$ bármely elem (wildcard)
  • text() $\rightarrow$ csak a közvetlen szöveges tartalom. (Van, amikor a . jobb, mert az a teljes elem beágyazott szövegeire is keres).
  • contains() $\rightarrow$ részleges egyezés vizsgálata
  • = $\rightarrow$ pontos egyezés vizsgálata
  • starts-with() $\rightarrow$ az érték valamilyen szöveggel kezdődik
  • normalize-space() $\rightarrow$ eltávolítja a szöveg elejéről és végéről a felesleges szóközöket, tabulátorokat és újsorokat, a szövegen belüli halmozott szóközöket pedig egyetlen szóközzé alakítja. (Különösen hasznos rendezetlen forráskódú weboldalak kaparásánál a pontos egyezések vizsgálatához).
  • Megjegyzés: Az ends-with() az XPath 2.0+ része, a böngészők által használt.

XPath Tengelyek (Axes)

Tengely (Axis)JelentésMire jó?
parent::szülő elemegy szintet feljebb lép
ancestor::bármely ős elempl.: egy nagyobb blokk keresése
child::közvetlen leszármazottLefelé történő navigáció egy szinttel
descendant::bármely mélységű leszármazottLefelé történő bármely mélységű navigáció
following-sibling::következő testvér elemformok
preceding-sibling::előző testvér elemformok

Gyakorló oldal

https://testsmith-io.github.io/locator-game/

3. A lokátorok szűkítése és láncolása (Chaining & Filtering)

Gyakran előfordul, hogy egy weboldal több azonos tulajdonságú elemet is tartalmaz (például egy lista elemei vagy egy táblázat sorai). Ilyenkor a lokátorokat tovább lehet szűkíteni a megfelelő metódusok egymás után fűzésével.

Alapvető láncolás (Chaining)

A lokátorokat egyszerűen egymás után kötheted, így a Playwright a második elemet már csak az első elemen belül fogja keresni.

  • Példa: await page.locator('#nav-bar').getByRole('button', { name: 'Kijelentkezés' }).click();

Lista elemeinek kiválasztása index alapján

.first()

Csak a találati lista legelső elemét választja ki.

  • Példa: await page.locator('.product-item').first().click();

.last()

Csak a találati lista legutolsó elemét választja ki.

  • Példa: await page.locator('.product-item').last().click();

.nth(index)

Sorszám alapján választja ki az elemet. Fontos: A számozás 0-tól indul, így a .nth(0) az első elemet, a .nth(1) a másodikat jelenti.

  • Példa: await page.locator('.product-item').nth(2).click(); (A 3. termékre kattint)

Haladó szűrés: .filter()

A .filter() metódus segítségével egy meglévő lokátor-listát szűkíthetsz tovább a belső tulajdonságaik alapján.

Szűrés szöveg alapján (hasText)

Csak azokat az elemeket tartja meg, amelyek (vagy amelyek gyermekelemei) tartalmazzák a megadott szöveget.

  • Példa: await page.locator('.row').filter({ hasText: 'Aktív' }).click();

Szűrés belső elem alapján (has)

Csak azokat az elemeket tartja meg, amelyek belsejében megtalálható egy másik, megadott lokátor (pl. egy gomb, ikon vagy jelölőnégyzet).

  • Példa: await page.locator('.card').filter({ has: page.getByRole('button', { name: 'Törlés' }) }).click(); (Csak azt a kártyát választja ki, amin van Törlés gomb)

4. Példa a gyakorlatban (Kódminta)

Így néz ki a lokátorok használata a tesztkódban, beágyazva a megfelelő műveletekkel:

javascriptimport { test, expect } from '@playwright/test';


test("Lokátorok használata", async ({ page }) => {
  await page.goto("https://pelda-oldal.hu");


  // 1. Kattintás a 'Regisztráció' szöveget tartalmazó linkre/gombra
  await page.getByText("Regisztráció").click();


  // 2. Beviteli mező kitöltése a placeholder alapján
  await page.getByPlaceholder("Felhasználónév").fill("Almafa12");


  // 3. Kattintás a 'Mentés' feliratú gombra
  await page.getByRole("button", { name: "Mentés" }).click();


  // 4. Ellenőrzés: A sikeres üzenet látható-e
  await expect(page.locator(".success-message")).toHaveText("Sikeres mentés!");
});

Billentyűzet események

import { test, expect } from "@playwright/test";

test("Billentyűzet események és kombinációk tesztelése", async ({ page }) => {
  // 1. Oldal betöltése és fókuszálás egy beviteli mezőre
  await page.goto("https://example.com");
  const input = page.locator("#username");
  await input.focus();

  // 2. Egyszerű gombnyomás (Alfanumerikus gombok)
  await page.keyboard.press("a");
  await page.keyboard.press("1");

  // 3. Navigációs és Szerkesztő billentyűk
  await page.keyboard.press("Tab"); // Fókusz a következő elemre
  await page.keyboard.press("Backspace"); // Utolsó karakter törlése
  await page.keyboard.press("Enter"); // Űrlap elküldése / Nyugtázás
  await page.keyboard.press("Escape"); // Modális ablak bezárása

  // 4. Nyíl billentyűk (Pl. legördülő menükhöz vagy egyéni komponensekhez)
  await page.keyboard.press("ArrowDown");
  await page.keyboard.press("ArrowUp");

  // 5. Oldalnavigáció
  await page.keyboard.press("PageDown"); // Görgetés lefelé
  await page.keyboard.press("End"); // Oldal legaljára ugrás

  // 6. Módosító billentyűk és kombinációk (Ctrl + A, majd Ctrl + C)
  // macOS esetén a "Control" helyett a "Meta" (Command) billentyűt kell használni
  const modifier = process.platform === "darwin" ? "Meta" : "Control";

  await page.keyboard.down(modifier); // Lenyomva tartás
  await page.keyboard.press("a"); // Mindent kijelöl
  await page.keyboard.press("c"); // Másolás vágólapra
  await page.keyboard.up(modifier); // Felengedés

  // 7. Kombináció egyetlen lépésben (Rövidített Playwright szintaxis)
  // A Playwright támogatja a gombok plusszjellel (+) való összefűzését is
  await page.keyboard.press(`${modifier}+v`); // Beillesztés vágólapról

  // Shift+Tab példa (visszafelé navigálás)
  await page.keyboard.press("Shift+Tab");
});

Egér események

import { test, expect } from "@playwright/test";

test("Egér események tesztelése", async ({ page }) => {
  await page.goto("https://example.com");
  const gomb = page.locator("#submit-btn");

  // 1. Alapvető kattintások (Locator alapon)
  await gomb.click(); // Sima bal klikk
  await gomb.dblclick(); // Dupla kattintás
  await gomb.click({ button: "right" }); // Jobb klikk (helyi menü)
  await gomb.click({ button: "middle" }); // Középső klikk (görgő)

  // 2. Billentyűvel kombinált kattintások
  await gomb.click({ modifiers: ["Control"] }); // Ctrl + Klikk (kijelöléshez)
  await gomb.click({ modifiers: ["Shift"] }); // Shift + Klikk

  // 3. Egér rámutatás (Hover) és elmozdítás
  await gomb.hover(); // Ráviszi az egeret (pl. lenyíló menükhöz)

  // 4. Vonszolás (Drag and Drop) - Egyszerű verzió
  // Forrás elem elhúzása a célelemre
  await page.locator("#source-item").dragTo(page.locator("#target-zone"));

  // 5. Precíz egérmozgások koordinátákkal (page.mouse)
  // Az egér mozgatása a képernyő egy adott X, Y pontjára
  await page.mouse.move(100, 200);

  // Manuális vonszolás koordinátákkal (Klikk -> Mozgatás -> Felengedés)
  await page.mouse.down(); // Bal egérgomb lenyomása és nyomva tartása
  await page.mouse.move(300, 400, { steps: 10 }); // Elmozdítás 10 lépésben (folyamatosabb)
  await page.mouse.up(); // Egérgomb felengedése

  // 6. Egérgörgő használata (Görgetés / Wheel)
  // deltaX: vízszintes, deltaY: függőleges görgetés képpontban (pixel)
  await page.mouse.wheel(0, 500); // 500 pixelt görget lefelé
});

Assertions + soft (Ellenőrzések)

Ellenőrzések és hitelesítések. Az expect könyvtárat kell ehhez használni a a playwright-ból.

import { test, expect } from "@playwright/test";

test("Assertions Demo", async ({ page }) => {
  await page.goto("https://kitchen.applitools.com/");

  await page.pause();
  /*Terminálban:
    npx playwright test .\tests\assertions.spec.ts --project chromium --headed
    */

  // ASSERTIONS
  // Check element present or not

  await page.getByRole("heading", { name: "The Kitchen" });
  await expect(page.getByRole("heading", { name: "The Kitchen" })).toHaveCount(
    1,
  );

  // $ -> All page.
  if (await page.$("text=The Kitchen")) {
    await page.locator("text=The Kitchen").click();
  }

  // check element hidden or visible
  await expect(page.locator("text=The Kitchen")).toBeVisible();
  // soft assertion
  // await expect.soft(page.locator('text=The Kitchen')).toBeHidden();

  // check element enable or disabled
  await expect(page.locator("text=The Kitchen")).toBeEnabled();

  // soft assertion
  // Nem áll meg közben a teszt, hanem lefut az egész, a végén kiírja, hogy mi nem sikerült.
  //await expect.soft(page.locator('text=The Kitchen')).toBeDisabled();

  // text matches value or not
  await expect(page.locator("text=The Kitchen")).toHaveText("The Kitchen");
  await expect(page.locator("text=The Kitchen")).not.toHaveText("ABCD");

  // element attribute
  await expect(page.locator("text=The Kitchen")).toHaveAttribute(
    "class",
    "chakra-heading css-dpmy2a",
  );
  // Ezzel: /.* megmondjuk azt, hogy bármi lehet előtte.
  await expect(page.locator("text=The Kitchen")).toHaveAttribute(
    "class",
    /.*css-dpmy2a/,
  );

  await expect(page.locator("text=The Kitchen")).toHaveClass(/.*css-dpmy2a/);

  // check page url and title
  await expect(page).toHaveURL("https://kitchen.applitools.com/");
  await expect(page).toHaveTitle(/.*The Kitchen/);

  // visual validation with screenshot
  await expect(page).toHaveScreenshot();
  /*
    Első futtaáskor készít egy képet ide: test-results\assertions-Assertions-Demo-chromium\Assertions-Demo-1-actual.png
    */
  // Második futtatáskor már összehasonlítja ez előzőleg készített képet az újal.
});

Videó rögzítés (lassított felvétel) és képernyőkép

Csak akkor érdemes felvételt készíteni, ha elbukik a teszt.

playwright.config.ts-ben a use részbe:

    browserName: "chromium",
    screenshot: "only-on-failure",
    video: "retain-on-failure",
    // Slow Motion and Video Recording
    video: 'on-first-retry',

    launchOptions: {
      slowMo: 300 // millisecond
    },

A test-results mappába menti a videót és az összes tesztnél készít felvételt.

Konkrét tesztnél készít csak videót

import { test, expect, chromium } from "@playwright/test";

test("Slow motion and video recording demo", async () => {
  /*
    Terminálban: 
    npx playwright test ./tests/slow_motion_videorecording_demo.spec.ts --project chromium
    */
  /*
       Step 1 - Create a test and create browser context
       Step 2 - Add options for Slow Motion in browser
       Step 3 - Add options for video recording in new context
       Step 4 - Close context
       */

  // Launch browser
  // const browser = await chromium.launch();
  const browser = await chromium.launch({
    headless: false, // Látható a böngésző.
    slowMo: 1000,
  });

  // Create a new incognito browser context
  // const context = await browser.newContext();
  const context = await browser.newContext({
    recordVideo: {
      dir: "videos/", // Új mappa létrehozása.
      size: { width: 800, height: 600 },
    },
  });

  // Create a new page inside context.
  const page = await context.newPage();

  await page.goto("https://admin-demo.nopcommerce.com/admin/");

  await page.pause();

  await page
    .getByRole("textbox", { name: "Email:" })
    .fill("admin@yourstore.com");

  await page.getByRole("textbox", { name: "Password:" }).fill("admin");
  await page.getByRole("button", { name: "Log in" }).click();

  // Dispose context once it's no longer needed.
  await context.close();
});

Step

Átláthatóbb lesz a teszt és a terminálban is jobban látszik, hogy hol tart épp.

// 1. Lépés: Navigáció és bejelentkezés
await test.step("Bejelentkezés az áruházba", async () => {
  await page.goto("https://example.com");
  await page.fill("#username", "teszt_felhasznalo");
  await page.fill("#password", "TitkosJelszo123");
  await page.click("#login-button");

  // Ellenőrzés a lépésen belül
  await expect(page).toHaveURL("https://example.com");
});

Hooks and Groups

Tesztszervezési és csoportosítási funkciók.

beforeAll A fájlban található összes teszt előtt lefut. Ha egy describe blokkon belül használod, akkor a csoportban lévő összes teszt előtt fut le. Ha több beforeAll függvényt adsz meg, a regisztrációjuk (létrehozásuk) sorrendjében fognak végrehajtódni.

beforeEach A fájlban található minden egyes teszt előtt lefut. Ha egy describe blokkon belül használod, akkor a csoportban lévő minden egyes teszt előtt lefut. Ha több beforeEach függvényt adsz meg, a regisztrációjuk sorrendjében fognak végrehajtódni.

afterAll A fájlban található összes teszt után lefut. Ha egy describe blokkon belül használod, akkor a csoportban lévő összes teszt után fut le. Ha több afterAll függvényt adsz meg, a regisztrációjuk sorrendjében fognak végrehajtódni.

afterEach A fájlban található minden egyes teszt után lefut. Ha egy describe blokkon belül használod, akkor a csoportban lévő minden egyes teszt után lefut. Ha több afterEach függvényt adsz meg, a regisztrációjuk sorrendjében fognak végrehajtódni.

import { test, expect } from "@playwright/test";

/*
Terminálban:
 npx playwright test ./tests/hooks_and_groups.spec.ts --project chromium --headed
 */

test.describe("All My Tests", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("https://www.saucedemo.com/");

    await page.locator('[data-test="username"]').fill("standard_user");
    await page.locator('[data-test="password"]').fill("secret_sauce");

    await page.locator('[data-test="login-button"]').click();
  });

  test.afterAll(async ({ page }) => {
    // Nem is kell, mert a playwright automatikusan bezárja a böngészőt.
    //await page.close();
  });

  test("HomePage", async ({ page }) => {
    await page.locator('[data-test="add-to-cart-sauce-labs-backpack"]').click();
    await page
      .locator('[data-test="add-to-cart-sauce-labs-bike-light"]')
      .click();
    await page.locator('[data-test="item-1-title-link"]').click();
    await page.locator('[data-test="add-to-cart"]').click();
  });

  test("Logout", async ({ page }) => {
    await page.getByRole("button", { name: "Open Menu" }).click();
    await page.locator('[data-test="logout-sidebar-link"]').click();
    await page.waitForURL("https://www.saucedemo.com/");
  });
});

Annotation & Tags

Annotációk

Az annotációk olyan kulcsszavak, amelyek logikai vagy feltételes funkciókat hordoznak. Tesztblokkokkal együtt használhatók a tesztek végrehajtásának igény szerinti vezérlésére. Például: only, fail, fix, slow.

import { test, expect } from "@playwright/test";

/*
Terminálban futtatáshoz:
npx playwright test ./tests/annotation_and_tags.spec.ts --project chromium --headed
*/

test.skip("Test One", async ({ page }) => {
  // Ez a teszt teljesen kimarad a futásból.
});

// Hibát fog dobni, ha a teszt nem bukik el (pl. ha nincs benne assert, ami elhasalna).
test("not yet ready", async ({ page }) => {
  test.fail();
  // Ide kell valami, ami elbukik, különösen ha éles tesztről van szó.
  expect(true).toBe(false);
});

test.fixme("test to be fixed", async ({ page }) => {
  // Ezt a tesztet a Playwright megjelöli, de meg sem próbálja lefuttatni.
});

test("slow test", async ({ page }) => {
  // Megháromszorozza az alapértelmezett timeout időt ehhez a teszthez.
  test.slow();
});

test.only("focus this test", async ({ page }) => {
  // Ha jelen van a .only, a Playwright KIZÁRÓLAG ezt a tesztet futtatja le a fájlban.
});

Saját annotációk

import { test, expect, Locator } from "@playwright/test";

test.describe("Successful login test for saucedemo.com", () => {
    test("login test", async ({ page }) => {
        test.info().annotations.push({
            type: "test-case",
            description: "TC-LOGIN-01",
        });

        await page.goto("https://www.saucedemo.com/");

        const username: Locator = page.locator("#user-name");
        const password: Locator = page.locator("#password");
        const loginButton: Locator = page.locator("#login-button");

        await username.fill("standard_user");
        await password.fill("secret_sauce");
        await loginButton.click();

        await expect(page).toHaveURL(
            "https://www.saucedemo.com/inventory.html",
        );
    });
});

Tagek

/*
A teszteket olyan címkékkel címkézheted meg, mint például:
@smoke
@sanity
@fast @slow 
és csak azokat a teszteket futtasd, amelyek rendelkeznek az adott címkével.
*/
test("Test full report @smoke", async ({ page }) => {
  // ...
});
/*
npx playwright test--grep “@smoke”

A grep ellentéte: Bizonyos címkékkel rendelkező tesztek kihagyása
npx playwright test--grep - invert “@smoke”
*/

POM (Új projekt)

A POM az automata tesztelésben a Page Object Model (Oldalobjektum-modell) rövidítése. Ez a legnépszerűbb és legelterjedtebb tervezési minta (design pattern) a UI-tesztelésben (Playwright, Selenium, Cypress).

Lényege, hogy a weboldal egyes oldalait (vagy nagyobb komponenseit, pl. fejléc, menü) külön JavaScript/TypeScript osztályokként (Class) modellezzük le.

Előnyei: ✅ Egységbe zárás – Elkülöníti a tesztlogikát a felhasználói felülettel (UI) való interakcióktól.
✅ Újrafelhasználhatóság – Ugyanazon oldalobjektumok használata több tesztben is.
✅ Skálázhatóság – A tesztkészlet egyszerű, problémamentes bővítése.
✅ Karbantarthatóság – Ha változik a felhasználói felület, elég egyetlen fájlt frissíteni a tucatnyi helyett!

Hozzunk létre egy mappát az új projektünknek és nyissuk meg azt Visual Studio Code-ban.

Új Node.js projekt inicializálása, terminálba ezt írd be: npm init -y

Playwright projekt létrehozása: Terminálba: npm init playwright@latest

A zárójelben lévőket válaszold a terminál kérdéseire. Do you want to use TypeScript or JavaScript? Typescript (enter), Where to put your end-to-end tests? Marad a test mappa az alap értelmezett (tab és enter). Fontos, hogy csak egy test mappád legyen. Add a GitHub Actions workflow? (n) // Ha szeretéd, hogy automatikusan lefussanak a tesztek, amikor commitolsz, akkor y-t válaszd. Install Playwright browsers? (y)

Hozz létre egy login.spec.ts fájlt a tests mappába. Utána ezt írd be a terminálba: npx playwright codegen

Manuálisan be kell írni a webcímet és kattintgatni kell, eközben a playwrigth elkészíti a kódot.

login.spec.ts tartalma:

import { test, expect } from "@playwright/test";

test("test", async ({ page }) => {
  await page.goto("https://the-internet.herokuapp.com/login");
  await page.getByRole("textbox", { name: "Username" }).click();
  await page.getByRole("textbox", { name: "Username" }).fill("tomsmith");
  await page.getByRole("textbox", { name: "Password" }).click();
  await page
    .getByRole("textbox", { name: "Password" })
    .fill("SuperSecretPassword");
  await page.getByRole("button", { name: " Login" }).click();
});

Futtatáshoz írd be ezt npx playwright test --project=chromium --headed a terminálba.

Hozz létre egy pages nevű mappát ás abba mentsd el a LoginPage.ts-t, melynek ez a tartalma:

import { Page, Locator } from "@playwright/test";

// export kulcs szóva lehet a teszt fájlokba importálni.
export class LoginPage {
  // private: OOP egységbe zárás alapelv.
  // Típusok deklarálása a TypeScript számára.
  private readonly page: Page;
  private readonly usernameInput: Locator;
  private readonly passwordInput: Locator;
  private readonly loginButton: Locator;

  constructor(page: Page) {
    /*
        A sima page (a jobb oldalon) a konstruktornak kívülről átadott paraméter (azaz a lokális változó).
        A this.page (a bal oldalon) az osztály saját tulajdonsága (property), amit feljebb deklaráltál.
        A this mondja meg a fordítónak: "A kívülről kapott page értéket mentsd el az osztályom saját page változójába!"
        */
    this.page = page;
    this.usernameInput = page.getByRole("textbox", { name: "Username" });
    this.passwordInput = page.getByRole("textbox", { name: "Password" });
    this.loginButton = page.getByRole("button", { name: " Login" });
  }

  async gotoLoginPage() {
    await this.page.goto("https://the-internet.herokuapp.com/login");
  }

  /**
   * Bejelentkezési folyamat végrehajtása
   * @param username Felhasználónév
   * @param password Jelszó
   */
  async login(username: string, password: string): Promise<void> {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

A login.spec.ts tartalmát módosítsd erre:

import { test, expect } from "@playwright/test";
import { LoginPage } from "../../pages/LoginPage";

test("test", async ({ page }) => {
  // Példányosítás, jobb oldalon a konstruktort hívjuk meg.
  const loginPage = new LoginPage(page);

  await loginPage.gotoLoginPage();
  await loginPage.login("tomsmith", "SuperSecretPassword!");
});

Teszt futtatása, írd be azt a terminálba: npx playwright test --ui

Fixtures és oldalváltás

// Playwright Fixture mintát követve mindent kitakarít a háttérben!
// fixtures/BaseTest.ts
// Az "as" felülírást jelenti, az eredetit felülírjuk. 
import { test as base, devices, BrowserContext, Page } from "@playwright/test";
import { LoginPage as LoginPage } from "../pages/LoginPage";

// 1. Lépés: Definiáljuk a fixture-ök típusait (milyen Page Objectjeink lesznek)
type MyFixtures = {
  // Első felhasználó környezete:
  context: BrowserContext; // Oldalváltáshoz kell.
  page: Page;
  loginPage: LoginPage;

  // Második felhasználó környezete:
  page2: Page;
};

// 2. Lépés: Kiterjesztjük az alap 'test' objektumot
export const test = base.extend<MyFixtures>({
  // Minden fixture egy aszinkron függvény, ami megkapja a 'page' és a 'use' paramétert
  // Példányosítjuk az osztályt
  // Első lapfül
  loginPage: async ({ page }, use) => {
    // Átadjuk a tesztnek használatra
    await use(new LoginPage(page));
  },

  // Oldalváltáshoz kell.
  // Másik lapfül.
  page2: async ({ context }, use) => {
    // 1. Megkeressük az ELSŐ lapfület, ami a teszt indulásakor már létezik
    const firstTab = context.pages()[0];

    // 2. Megnyitjuk a MÁSODIK lapfület (ez ekkor előtérbe ugrik)
    const newTab = await context.newPage();

    // 3. Ha az első fül már létezik, AZONNAL visszaváltunk rá
    if (firstTab) {
      await firstTab.bringToFront();
    }

    // 4. Átadjuk a másodlagos lapot a tesztnek
    await use(newTab);

    // 5. A teszt végén automatikusan bezárjuk a második fület
    await newTab.close();
  },

  /*
  loginPage2: async ({ page2 }, use) => {
    await use(new LoginPage(page2));
  },
  */
});

// 3. Lépés: Újraexportáljuk az 'expect' funkciót is, a kényelmesebb importálásért
export { expect } from "@playwright/test";

// Minden teszt után lefut.
test.afterEach(async ({ context }) => {
  await context.close();
});

Egy konkrét tesztben így kell váltogatni a lapfülek között

await context.pages()[1].bringToFront();

// Váltás/Interakció: Egyszerűen az adott változót használod
// Visszavált az első fülre (előtérbe hozza)
await page.bringToFront();

Canvas

// Térképre történő kattintás.
await page.locator("canvas.ol-fixedoverlay").click({
  position: {
    x: 400,
    y: 180,
  },
});

Page Chaining (Oldal láncolás)

A Page Chaining egy tervezési minta, amelyet általában a Page Object Model (POM) mellett használunk Playwright teszteknél.

A lényege:

Egy oldalon végrehajtott művelet után a metódus visszaadja a következő oldal objektumát, így a teszt folytatható a következő oldalon.

A teszt folyamatot oldalról oldalra lehet vezetni:

LoginPage ↓ DashboardPage ↓ ProfilePage

Ha nincsen oldalváltó művelet, akkor az adott oldal objektumát adja vissza.

Report feltöltésének automatizálása Azure DevOps-al

Statikus html oldalként fog megjelenni.

Útmutatók a beállításához: https://ultimateqa.com/playwright-reporters-how-to-integrate-with-azure-devops-pipelines/

https://bogdanbujdea.dev/publishing-playwright-report-as-an-artifact-in-azure-devops?source=more_series_bottom_blogs

Best practices

  • Adatalapú tesztelést (Data-driven test)

  • Negatív tesztelés és összetett forgatókönyvek (Multiple scenarios)

  • Újrafelhasználható POM

  • Tiszta asszerciók (Clean Assertions):

    AAA (Arrange, Act, Assert) minta: Tagolja a teszteseteket három jól elkülöníthető részre. Az ellenőrzéseket mindig a teszt legvégén végezze el.

Valós skálázható projekt (Real World Scalable Project)

A lényege, hogy a projekt valós problémát modellezzen, bővíthető legyen, és ne csak egy egyszerű demo legyen.

JSON fájl használata

Ha JSON fájlban 10 felhasználó van akkor 10-szer futnak e a tesztek. A tesztek futtatása egy logikai kapcsolóval (flag) szabályozható: a teszteset csak akkor indul el, ha a mező értéke 'yes'.

Példa program: data/loginDataNew.json

[
  {
    "username": "standard_user",
    "password": "secret_sauce",
    "expected": "success",
    "run": "yes"
  },
  {
    "username": "locked_out_user",
    "password": "secret_sauce",
    "expected": "error",
    "run": "no"
  },
  {
    "username": "problem_user",
    "password": "secret_sauce",
    "expected": "success",
    "run": "no"
  }
]

A POM fejezetben lévő LoginPage osztály használjuk a lenti tesztnél. login.dynamicdata.spec.ts:

import { expect, test } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage.ts";
import loginDataNew from "../data/loginDataNew.json";

loginDataNew.forEach((data) => {
  if (!data.run) return; // Ha false az értéke, akkor ne csináljon semmit.

  // Backtick (Visszafelé dőlő ékezet)-t használj!
  test(`Login Test - ${data.username}`, async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.gotoLoginPage();
    await loginPage.login(data.username, data.password);

    if (data.expected === "success") {
      await expect(page).toHaveURL("https://www.saucedemo.com/inventory.html");
    } else {
      await expect(loginPage.errorMessage).toBeVisible();
    }
  });
});

CSV fájl használata

A csv használatához kell egy külön csomag, írd be azt a terminálba a letöltéséhez: npm install csv-parse

A POM fejezetben lévő LoginPage osztály használjuk a lenti tesztnél.

data/LoginData.csv:

username,password,expected,run
standard_user,secret_sauce,success,yes
locked_out_user,secret_sauce,error,no
problem_user,secret_sauce,success,yes

utils/csvReader.ts:

import fs from "fs";
import * as csvParseSync from "csv-parse/sync";

export function readCSV(filePath: string) {
  // Adj hozzá 'utf-8'-at, hogy biztosan szövegként olvassa be
  const fileContent = fs.readFileSync(filePath, "utf-8");

  const records = csvParseSync.parse(fileContent, {
    columns: true, // A fejlés a mezőneveket tartalmazza.
    skip_empty_lines: true,
  });
  return records;
}

login.csv.spec.ts

import { expect, test } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
import { readCSV } from "../utils/csvReader";

// Jobb klikk a fájlon és Copy Relative Path. /-re figyelj!
const loginData = readCSV("data/LoginData.csv");

// Soronként haladunk, az any-val elfogadunk minden típust.
loginData.forEach((data: any) => {

   // Ha nem true az értéke, akkor ne csináljon semmit.
  if (data.run !== "true") return; 

  // Backtick (Visszafelé dőlő ékezet)-t használj!
  test(`Login Test - ${data.username}`, async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.gotoLoginPage();
    await loginPage.login(data.username, data.password);

    if (data.expected === "success") {
      await expect(page).toHaveURL("https://www.saucedemo.com/inventory.html");
    } else {
      await expect(loginPage.errorMessage).toBeVisible();
    }
  });
});

XLSX fájl használata

A csv használatához kell egy külön csomag, írd be azt a terminálba a letöltéséhez: npm install xlsx npm install -D @types/xlsx

A POM fejezetben lévő LoginPage osztály használjuk a lenti tesztnél.

LoginData.xlsx

username|password|expected|run
standard_user|secret_sauce|success|yes
locked_out_user|secret_sauce|error|no
problem_user|secret_sauce|success|yes

utils/excelReader.ts:

import * as XLSX from "xlsx";
import path from "path";

// Egyedi típus.
export type LoginData = {
    username: string;
    password: string;
    expected: string;
    run: string;
};

export function readExcel(filePath: string, sheetName: string): LoginData[] {
    const fullPath = path.resolve(filePath);
    console.log("Full Path is ", fullPath);

    const workbook = XLSX.readFile(fullPath);
    const sheet = workbook.Sheets[sheetName];
    const data = XLSX.utils.sheet_to_json(sheet);
    return data;
}

login.excel.spec.ts:

import { expect, test } from "@playwright/test";
import { LoginPage } from "../../pages/LoginPage";
import { readExcel } from "../../utils/excelReader";

// Jobb klikk a fájlon és Copy Relative Path. /-re figyelj!
//Típus biztonsággal, elkelhetjük a fordításkori futás esetén bekövetkező hibákat.
const testData: LoginData[] = readExcel("./data/LoginData.xlsx", "Sheet1");

test.describe("Login Tests", () => {
    // Soronként haladunk, az any-val elfogadunk minden típust.
    for (const data of testData) {
        // Backtick (Visszafelé dőlő ékezet)-t használj!
        test(`Login test for - ${data.username}`, async ({ page }) => {
            // A skippelt tesztek láthatóak a reportban is, felül a lapfülek között.
            test.skip(
                // Ha nem yes az értéke, akkor ne csináljon semmit.
                data.run !== "yes",
                "Run flag is not yes.",
            );

            const loginPage = new LoginPage(page);

            await test.step("Go to login page", async () => {
                await loginPage.gotoLoginPage();
            });

            await test.step("Perform Login", async () => {
                await loginPage.login(data.username, data.password);
            });

            await test.step("Validate Result", async () => {
                if (data.expected === "success") {
                    await expect(page).toHaveURL(
                        "https://www.saucedemo.com/inventory.html",
                    );
                } else {
                    await expect(loginPage.errorMessage).toBeVisible();
                }
            });
        });
    }
});

Multi funkcionális adatbeolvasás (json, csv,excel) - Ezt használd!

Mindhárom típusú fájl be tudja olvasni. A fentebb található három fejezetben lévő kódot használjuk megint.

utils/dataReader.ts:

import path from "path";
import { readCSV } from "../utils/csvReader";
import { readExcel } from "../utils/excelReader";
import fs, { readFileSync } from "fs";

// A ? azt jelenti, hogy nem kötelező megadni.
export function readData(filePath: string, sheetName?: string) {
    const ext = path.extname(filePath).toLowerCase();

    switch (ext) {
        case ".csv":
            return readCSV(filePath);
        case ".xlsx":
            return readExcel(filePath, sheetName || "Sheet1");
        case ".json":
            const JSONData = fs.readFileSync(filePath, "utf-8");
            return JSON.parse(JSONData);
        default:
            // Backtick (Visszafelé dőlő ékezet)-t használj!
            throw new Error(`Unsupported file type - ${ext}`);
    }
}

login.unifiedReader.spec.ts:

import { expect, test } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
import { readData } from "../utils/dataReader";

// Jobb klikk a fájlon és Copy Relative Path. /-re figyelj!
//Típus biztonsággal, elkelhetjük a fordításkori futás esetén bekövetkező hibákat.
// const testData = readData('./data/loginDataNew.json');
// const testData = readData('./data/LoginData.csv');
const testData = readData("./data/LoginData.xlsx", "Sheet1");

test.describe("Login Tests", () => {
    // Soronként haladunk, az any-val elfogadunk minden típust.
    for (const data of testData) {
        // Backtick (Visszafelé dőlő ékezet)-t használj!
        test(`Login test for - ${data.username}`, async ({ page }) => {
            // A skippelt tesztek láthatóak a reportban is, felül a lapfülek között.
            test.skip(
                // Ha nem yes az értéke, akkor ne csináljon semmit.
                data.run !== "yes",
                "Run flag is not yes.",
            );

            const loginPage = new LoginPage(page);

            await test.step("Go to login page", async () => {
                await loginPage.gotoLoginPage();
            });

            await test.step("Perform Login", async () => {
                await loginPage.login(data.username, data.password);
            });

            await test.step("Validate Result", async () => {
                if (data.expected === "success") {
                    await expect(page).toHaveURL(
                        "https://www.saucedemo.com/inventory.html",
                    );
                } else {
                    await expect(loginPage.errorMessage).toBeVisible();
                }
            });
        });
    }
});