Playwright Academy
0 dari 17
Fase 2 · Jurus IntiModul 7 dari 17

Test Runner

Struktur test, fixture dan hook, pola AAA, data-driven, tag, dan cara menjalankannya.

Test runner menyiapkan setiap test (setup atau fixture), menjalankannya, membereskannya (teardown), lalu memberi laporan lulus atau gagal. Kamu cukup fokus ke isi test-nya.

Di dunia QA

Test runner mirip test management tool (TestRail, Zephyr, atau spreadsheet test case). Ia tahu daftar test case, menyiapkan precondition, menjalankan semuanya, lalu menulis status passed atau failed untuk setiap baris.

Dua alat, satu konsep

pytest + pytest-playwrightPlaywright Test
Nama file testtest_*.py*.spec.ts
Bentuk testfungsi def test_…(page):test('judul', async ({ page }) => { … })
Persiapan bersamafixture (@pytest.fixture)hook (beforeEach) dan fixture (test.extend)
Mengelompokkan testper file atau classtest.describe()
Satu test, banyak data@pytest.mark.parametrizeperulangan for
Label atau tag@pytest.mark.smoke{ tag: '@smoke' }
Tempat pengaturanpyproject.toml + perintah terminalplaywright.config.ts

Fixture bawaan yang tinggal dipakai

Tulis namanya sebagai parameter test, dan test runner menyiapkannya:

FixtureIsinya
pageTab baru di browser context baru, bersih untuk setiap test
contextBrowser context milik test itu
browserBrowser yang dipakai bersama (jarang perlu dipakai langsung)
browser_name / browserName"chromium", "firefox", atau "webkit"
playwright (Py) / request (TS)Untuk API test (Modul 13)
base_url (Py)Base URL dari konfigurasi

Setiap test dapat context baru: cookies kosong, penyimpanan kosong, belum login. Kalau test A login, test B tidak ikut login, jadi test boleh jalan dalam urutan apa pun, bahkan paralel.

Pola AAA: siapkan, lakukan, periksa

Pola yang membuat test mudah dibaca:

  1. Arrange (siapkan): buka halaman, siapkan data, login.
  2. Act (lakukan): satu aksi utama yang mau diuji.
  3. Assert (periksa): pastikan hasilnya sesuai harapan.
def test_login_berhasil(page):  # fungsi berawalan test_ = satu test
    # Arrange
    page.goto("/login-toko.html")

    # Act
    page.get_by_label("Username").fill("customer")
    page.get_by_label("Password").fill("rahasia123")  # isi Password
    page.get_by_role("button", name="Masuk").click()  # klik Masuk

    # Assert
    expect(page).to_have_url(re.compile(r"shop\.html
Memuat materi…
quot;
)) expect(page.get_by_test_id("greeting")).to_have_text("Halo, Rina Wulandari") # sapaan menyebut nama customer
test('login berhasil', async ({ page }) => { // satu test
  // Arrange
  await page.goto('/login-toko.html');

  // Act
  await page.getByLabel('Username').fill('customer');
  await page.getByLabel('Password').fill('rahasia123'); // isi Password
  await page.getByRole('button', { name: 'Masuk' }).click(); // klik Masuk

  // Assert
  await expect(page).toHaveURL(/shop\.html$/);
  await expect(page.getByTestId('greeting')).toHaveText('Halo, Rina Wulandari'); // sapaan menyebut nama customer
});

Beri nama test sesuai perilaku yang diuji. test_login_gagal_saat_password_salah langsung memberi tahu apa yang rusak, test_login_2 tidak.

Persiapan yang bisa dipakai berulang

Di pytest, persiapan bersama ditulis sebagai fixture. Kode sebelum yield adalah setup, setelah yield adalah teardown.

Python
import pytest  # pustaka pytest

@pytest.fixture  # tandai fungsi di bawah sebagai fixture
def halaman_login(page):  # fixture: persiapan yang dipakai ulang
    page.goto("/login-toko.html")   # setup
    yield page                             # test berjalan di sini
    # teardown (opsional): misalnya hapus data yang dibuat test

def test_form_login_tampil(halaman_login):  # test meminta fixture lewat nama parameternya
    expect(halaman_login.get_by_role("button", name="Masuk")).to_be_visible()  # tombol Masuk terlihat
  • Fixture boleh memakai fixture lain (halaman_login memakai page).
  • Fixture yang dipakai banyak file taruh di conftest.py, pytest menemukannya tanpa import.
  • Scope mengatur seberapa sering fixture dibuat: function (bawaan), module, atau session.

Di Playwright Test, cara paling sederhana adalah memakai hook:

TypeScript
test.describe('Login TokoQA', () => { // kelompokkan test yang sejenis
  test.beforeEach(async ({ page }) => {
    await page.goto('/login-toko.html');  // jalan sebelum SETIAP test di grup ini
  });

  test.afterEach(async ({ page }, testInfo) => { // dijalankan setelah setiap test
    console.log(`${testInfo.title} → ${testInfo.status}`);
  });

  test('form login tampil', async ({ page }) => { // satu test di dalam kelompok
    await expect(page.getByRole('button', { name: 'Masuk' })).toBeVisible();
  });
});

beforeAll/afterAll jalan sekali per file atau grup. Untuk persiapan yang dipakai banyak file, buat fixture sendiri dengan test.extend() (Modul 10).

Satu test, banyak data

Tulis test login gagal sekali, lalu berikan datanya:

@pytest.mark.parametrize(  # jalankan test yang sama dengan banyak data
    ("username", "password", "pesan_error"),
    [
        ("customer", "salah", "Username atau password salah"),
        ("terkunci", "rahasia123", "Akun kamu terkunci. Hubungi admin."),
        ("", "", "Username dan password wajib diisi"),
    ],
    ids=["password-salah", "akun-terkunci", "form-kosong"],
)
def test_login_gagal(halaman_login, username, password, pesan_error):  # satu test, dijalankan sekali per baris data
    halaman_login.get_by_label("Username").fill(username)  # username dari data
    halaman_login.get_by_label("Password").fill(password)  # password dari data
    halaman_login.get_by_role("button", name="Masuk").click()  # klik Masuk
    expect(halaman_login.get_by_role("alert")).to_have_text(pesan_error)  # pesan error sesuai data
const kasusGagal = [ // tabel data: satu baris = satu test
  { judul: 'password salah', username: 'customer', password: 'salah', pesan: 'Username atau password salah' },
  { judul: 'akun terkunci', username: 'terkunci', password: 'rahasia123', pesan: 'Akun kamu terkunci. Hubungi admin.' },
  { judul: 'form kosong', username: '', password: '', pesan: 'Username dan password wajib diisi' },
];

for (const { judul, username, password, pesan } of kasusGagal) { // buat satu test untuk setiap baris
  test(`login gagal: ${judul}`, async ({ page }) => {
    await page.getByLabel('Username').fill(username);
    await page.getByLabel('Password').fill(password); // password dari data
    await page.getByRole('button', { name: 'Masuk' }).click(); // klik Masuk
    await expect(page.getByRole('alert')).toHaveText(pesan); // pesan error sesuai data
  });
}

Hasilnya tiga test terpisah di laporan, jadi data penyebab gagal langsung terlihat.

File asli Modul 7

tests/academy/test_m07_runner.pytests/academy/m07-runner.spec.tsLulus test
Memuat tests/academy/test_m07_runner.py…
uv run pytest tests/academy/test_m07_runner.py -v
Memuat tests/academy/m07-runner.spec.ts…
npx playwright test m07-runner --project=chromium

Menjalankan test sesuai kebutuhan

Menampilkan Python. Ganti bahasa di bagian atas untuk melihat TypeScript.Menampilkan TypeScript. Ganti bahasa di bagian atas untuk melihat Python.

Mau apapytestPlaywright Test
Jalankan semua testuv run pytestnpx playwright test
Satu file sajauv run pytest tests/academy/test_m07_runner.pynpx playwright test m07-runner
Cari berdasarkan nama test-k "login and gagal"-g "login gagal"
Hanya yang punya tag smoke-m smoke--grep @smoke
Semua kecuali tanda tertentu-m "not smoke"--grep-invert @smoke
Tampilkan hasil per test-v--reporter=list
Ulangi yang tadi gagal saja--lf (last failed)--last-failed
Jalankan paralel-n 4 (plugin pytest-xdist)--workers=4 (sudah bawaan)
Ulangi otomatis kalau gagal--reruns 2 (plugin pytest-rerunfailures)--retries=2
Berhenti di kegagalan pertama-x-x

Melewati test dan sejenisnya

@pytest.mark.skip(reason="Fitur voucher belum rilis")  # lewati test ini, alasannya tampil di laporan
def test_voucher(page): ...  # badan test belum ditulis

@pytest.mark.skipif(sys.platform == "win32", reason="Tidak relevan di Windows")  # lewati hanya kalau kondisinya benar
def test_khusus_linux(page): ...  # badan test

@pytest.mark.xfail(reason="Bug #123: total salah hitung")   # diharapkan gagal
def test_total_dengan_diskon(page): ...  # test yang diharapkan gagal (bug yang sudah diketahui)

def test_khusus_chromium(page, browser_name):  # browser_name dari pytest-playwright
    if browser_name != "chromium":  # bukan Chromium?
        pytest.skip("Hanya relevan di Chromium")  # lewati dari dalam test
test.skip('voucher', async ({ page }) => { /* fitur belum rilis */ }); // lewati test ini

test('khusus linux', async ({ page }) => { // test yang dilewati di kondisi tertentu
  test.skip(process.platform === 'win32', 'Tidak relevan di Windows');
});

test.fail('total dengan diskon', async ({ page }) => { /* Bug #123, diharapkan gagal */ }); // diharapkan gagal karena bug yang sudah diketahui

test('khusus chromium', async ({ page, browserName }) => { // browserName dari Playwright Test
  test.skip(browserName !== 'chromium', 'Hanya relevan di Chromium');
});

// test.only(...) → HANYA test ini yang jalan. Jangan sampai ter-commit!
Hati-hati dengan test.only yang terbawa

Kalau test.only lupa dihapus, CI hanya menjalankan satu test dan semuanya terlihat hijau. Pengaturan forbidOnly: !!process.env.CI (sudah ada di config buatan wizard) mencegahnya.

Lebih jauh (opsional)Trik komunitas
  • Menjalankan paralel di Python: pytest-xdist dengan -n auto memakai semua inti prosesor laptopmu. Mengulang test yang gagal: pytest-rerunfailures dengan --reruns 2.
  • test.step() (TypeScript) membagi test panjang menjadi langkah-langkah bernama di laporan. Di Python, pecah test menjadi fungsi kecil dengan nama yang jelas.
  • --only-changed (TypeScript, v1.46+) hanya menjalankan file test yang berubah sejak commit terakhir. Hemat waktu saat kamu sedang menulis test.
  • Fixture otomatis: autouse=True (pytest) atau { auto: true } (Playwright Test) untuk hal yang selalu dibutuhkan, misalnya mencatat error di setiap test.
  • Annotation menautkan test ke tiket bug, sehingga langsung terlihat di laporan.
uv add --dev pytest-xdist pytest-rerunfailures  # plugin untuk paralel dan ulang otomatis
uv run pytest -n auto --reruns 2  # paralel sesuai jumlah CPU, test gagal diulang 2 kali
test('checkout dengan kupon', { // test dengan tag dan anotasi
  tag: '@regression',
  annotation: { type: 'issue', description: 'https://github.com/org/repo/issues/123' },
}, async ({ page }) => { // lanjutan: badan test
  await test.step('login', async () => { /* ... */ });
  await test.step('isi keranjang dan bayar', async () => { /* ... */ }); // langkah bernama, tampil di laporan
});
// npx playwright test --only-changed

Latihan

Kerjakan di file test baru, jalankan, lalu bandingkan dengan jawaban.

Latihan 1

Tambah kasus login gagal tanpa menulis test baru

Salin test login gagal di atas, tambahkan satu kasus (username admin, password salah), lalu jalankan dengan -v (Python) atau reporter list (TypeScript).

Hasil4 test lulus, masing-masing dengan nama kasusnya

Latihan 2

Buat fixture atau hook yang membuka Arena

Buat fixture (Python) atau beforeEach (TypeScript) yang membuka Arena. Pakai di 2 test: satu mengklik "Klik aku", satu menekan Escape di kolom keyboard.

Hasilkedua test lulus tanpa goto di dalam test-nya

Latihan 3

Tulis test dengan pola AAA

Login sebagai customer (Arrange), tambahkan Mouse Wireless dan Lampu Meja LED ke keranjang (Act), lalu pastikan jumlah keranjang 2 dan totalnya Rp 305.000 (Assert). Beri komentar penanda di setiap bagian.

Hasiltest lulus dan mudah dibaca dari atas ke bawah

Latihan 4

Jalankan hanya test smoke

Jalankan hanya test dengan tag smoke di seluruh project.

Hasilhanya beberapa test yang jalan, sisanya dilewati