kodingup.camp

Beranda/Kualitas Web: Aksesibilitas, Performa, Keamanan, Testing & Tooling

Test End-to-End

Modul 6 dari 85 menit bacaEnd-to-End Testing

Isi modul

    Kenapa ini penting

    Unit test seperti memeriksa setiap komponen mobil di bengkel: mesin dites di meja, rem dites terpisah, lampu dites terpisah. Semuanya lulus. Tapi apakah mobilnya bisa dibawa jalan dari rumah ke kantor? Belum tentu. Bisa saja kabel lampu rem tidak tersambung ke pedal. Test end-to-end (E2E) adalah test drive: aplikasi dijalankan utuh di browser sungguhan, lalu robot berperan sebagai pengguna yang membuka halaman, mengetik, mengklik, dan memeriksa hasilnya.

    E2E menangkap masalah yang tidak terlihat di unit test atau jsdom: CSS yang menutupi tombol sehingga tidak bisa diklik, file JavaScript yang gagal dimuat setelah build, perbedaan perilaku antar-browser, dan alur yang melibatkan banyak halaman. Di modul ini kita memakai Playwright (versi 1.64), alat E2E dari Microsoft yang bisa mengendalikan tiga mesin browser utama, yaitu Chromium (Chrome, Edge), Firefox, dan WebKit (mesin Safari), dengan satu API. Ini langsung menjawab kebutuhan compatibility testing di MDN Curriculum.

    Konsep inti

    Instalasi

    Cara resmi adalah npm init playwright@latest. Perintah ini menanyakan bahasa (JavaScript/TypeScript), nama folder test, apakah perlu workflow GitHub Actions, dan apakah browser langsung diunduh. Hasilnya: file playwright.config (.js atau .ts), folder test dengan satu contoh, dan dependensi @playwright/test. Playwright mendukung Node.js 22.x, 24.x, dan 26.x terbaru.

    Cara manual (yang kita pakai, agar setiap langkah terlihat):

    npm install -D @playwright/test
    npx playwright install            # unduh Chromium, Firefox, WebKit (beberapa ratus MB)
    # atau hanya satu: npx playwright install chromium
    

    Konsep kunci Playwright

    Konsep Penjelasan
    page Satu tab browser. Disediakan otomatis sebagai fixture di setiap test
    Locator Cara menemukan elemen, misalnya page.getByRole('button', { name: 'Daftar' }), page.getByLabel('Email'), page.getByText('Berhasil'). Mirip Testing Library, dan juga mengutamakan role & label
    Aksi click(), fill(), press('Enter'), check(), selectOption()
    Auto-waiting Sebelum mengklik, Playwright otomatis menunggu elemen muncul, terlihat, stabil, dan bisa diterima. Kamu hampir tidak pernah perlu sleep
    Web-first assertion await expect(locator).toBeVisible() mengulang pengecekan sampai lulus atau timeout (default 5 detik). Cocok untuk UI yang berubah asinkron
    Project Satu konfigurasi browser/perangkat. Satu file test bisa dijalankan di banyak project sekaligus
    webServer Playwright menyalakan server aplikasimu sebelum test dan mematikannya setelah selesai

    Daftar assertion yang sering dipakai: toBeVisible(), toHaveText(), toContainText(), toHaveValue(), toBeFocused(), toHaveTitle(), toHaveURL(), toHaveScreenshot().

    Cross-browser dan perangkat

    Di konfigurasi, objek devices menyediakan preset siap pakai, seperti devices['Desktop Chrome'], devices['Desktop Safari'], devices['Pixel 7'], dan devices['iPhone 15'], lengkap dengan ukuran layar, user agent, dan dukungan sentuh. Saat kami menjalankan test keyboard di tiga browser, Chromium dan Firefox lulus tapi WebKit gagal: di Safari, secara bawaan tombol Tab tidak berhenti di link (pengguna harus menekan Option+Tab atau mengubah pengaturan). Itulah contoh perbedaan yang hanya bisa ditemukan dengan menguji di mesin browser sungguhan.

    Screenshot

    Ada dua jenis:

    Debugging

    Contoh kode

    Konfigurasi yang membangun aplikasi, menyalakan vite preview, lalu menjalankan test di empat project:

    // playwright.config.js
    import { defineConfig, devices } from '@playwright/test';
    
    export default defineConfig({
      testDir: './e2e',
      use: {
        baseURL: 'http://localhost:4173',  // jadi cukup page.goto('/')
        screenshot: 'only-on-failure',
        trace: 'on-first-retry',
      },
      projects: [
        { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
        { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
        { name: 'webkit', use: { ...devices['Desktop Safari'] } },
        { name: 'hp', use: { ...devices['Pixel 7'] } },
      ],
      webServer: {
        // Uji hasil build (yang akan di-deploy), bukan dev server
        command: 'npm run build && npm run preview -- --port 4173 --strictPort',
        url: 'http://localhost:4173',
        reuseExistingServer: !process.env.CI,
      },
    });
    

    Test alur pengguna, termasuk keyboard dan audit aksesibilitas otomatis dengan axe:

    npm install -D @axe-core/playwright
    
    // e2e/newsletter.spec.js
    import { test, expect } from '@playwright/test';
    import AxeBuilder from '@axe-core/playwright';
    
    test.beforeEach(async ({ page }) => {
      await page.goto('/');
    });
    
    test('judul halaman benar', async ({ page }) => {
      await expect(page).toHaveTitle('Daftar Newsletter');
      await expect(page.getByRole('heading', { level: 1 })).toHaveText('Daftar Newsletter');
    });
    
    test('email salah memunculkan pesan error', async ({ page }) => {
      await page.getByLabel('Email').fill('budi@');
      await page.getByRole('button', { name: 'Daftar' }).click();
    
      await expect(page.getByText('Format email belum benar')).toBeVisible();
      await expect(page.getByLabel('Email')).toBeFocused();
    });
    
    test('pendaftaran sukses', async ({ page }) => {
      await page.getByLabel('Email').fill('budi@contoh.id');
      await page.getByLabel('Email').press('Enter');
    
      await expect(page.getByRole('status')).toContainText('budi@contoh.id sudah terdaftar');
      await expect(page.getByLabel('Email')).toHaveValue(''); // form di-reset
    });
    
    test('bisa dipakai dengan keyboard saja', async ({ page, browserName }) => {
      // Safari bawaan: Tab melewati link, kecuali ditekan bersama Option/Alt
      const tab = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
      await page.keyboard.press(tab);
      await expect(page.getByRole('link', { name: 'Lewati ke konten utama' })).toBeFocused();
      await page.keyboard.press(tab);
      await expect(page.getByLabel('Email')).toBeFocused();
    });
    
    test('tidak ada pelanggaran aksesibilitas otomatis', async ({ page }) => {
      const hasil = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
        .analyze();
      expect(hasil.violations).toEqual([]);
    });
    
    test('screenshot halaman', async ({ page }, testInfo) => {
      await page.screenshot({
        path: `screenshots/beranda-${testInfo.project.name}.png`,
        fullPage: true,
      });
    });
    

    Visual regression dan pengecekan prefers-reduced-motion:

    // e2e/visual.spec.js
    import { test, expect } from '@playwright/test';
    
    test('tampilan beranda tidak berubah', async ({ page }) => {
      await page.goto('/');
      await expect(page).toHaveScreenshot('beranda.png');
      // Run pertama: GAGAL & membuat acuan di e2e/visual.spec.js-snapshots/
      // Run berikutnya: dibandingkan dengan acuan
    });
    
    test('animasi mati saat pengguna minta reduced motion', async ({ page }) => {
      await page.emulateMedia({ reducedMotion: 'reduce' });
      await page.goto('/');
      const transisi = await page
        .getByRole('button', { name: 'Daftar' })
        .evaluate((el) => getComputedStyle(el).transitionDuration);
      expect(transisi).toBe('0s'); // CSS opt-in dari modul Aksesibilitas bekerja
    });
    

    Menjalankan:

    npx playwright test
    # → Running 24 tests using … workers   (6 test × 4 project)
    # →   24 passed (9.3s)
    
    npx playwright test --project=chromium        # satu browser saja
    npx playwright test e2e/newsletter.spec.js     # satu file saja
    npx playwright test --ui                       # mode interaktif
    npx playwright show-report                     # buka laporan HTML
    npx playwright test --update-snapshots         # perbarui acuan screenshot
    

    Tambahkan folder hasil ke .gitignore agar tidak ikut di-commit:

    printf 'playwright-report/\ntest-results/\nscreenshots/\n' >> .gitignore
    

    Kesalahan umum

    1. Memakai page.waitForTimeout(3000) untuk "menunggu loading". Lambat dan tetap rapuh. Andalkan auto-waiting dan web-first assertion seperti await expect(...).toBeVisible().
    2. Lupa await. Hampir semua API Playwright asinkron. Tanpa await, test bisa selesai sebelum aksinya terjadi dan lulus palsu.
    3. Locator rapuh seperti page.locator('div > div:nth-child(3) > button'). Pakai getByRole, getByLabel, getByText.
    4. Hanya menguji di Chromium. Bug Safari/WebKit justru yang paling sering lolos. Jalankan minimal tiga mesin browser untuk alur penting.
    5. Menulis E2E untuk semua hal. E2E lambat. Logika seperti validasi email cukup dengan unit test. E2E untuk alur utama: daftar, login, checkout.

    Latihan

    1. Mudah, Tulis test Playwright yang membuka /, memastikan ada tombol bernama "Daftar", dan atribut lang pada <html> bernilai id (petunjuk: page.locator('html') dan toHaveAttribute).
    2. Sedang, Tulis test yang mengisi email valid, menekan Daftar, lalu memastikan tidak ada pesan error yang terlihat dan status berisi kata "Terima kasih".
    3. Menantang, Tambahkan project iphone dengan devices['iPhone 15'] ke konfigurasi, lalu tulis test yang hanya berjalan di project mobile (petunjuk: fixture isMobile dan test.skip) untuk memastikan tinggi tombol Daftar minimal 44 piksel.
    Lihat solusi

    1.

    import { test, expect } from '@playwright/test';
    
    test('struktur dasar halaman', async ({ page }) => {
      await page.goto('/');
      await expect(page.getByRole('button', { name: 'Daftar' })).toBeVisible();
      await expect(page.locator('html')).toHaveAttribute('lang', 'id');
    });
    

    2.

    test('email valid tidak memunculkan error', async ({ page }) => {
      await page.goto('/');
      await page.getByLabel('Email').fill('sari@contoh.id');
      await page.getByRole('button', { name: 'Daftar' }).click();
    
      await expect(page.getByRole('status')).toContainText('Terima kasih');
      await expect(page.getByText('Format email belum benar')).toBeHidden();
    });
    

    3.

    // playwright.config.js → tambahkan ke projects:
    // { name: 'iphone', use: { ...devices['iPhone 15'] } },
    
    test('target sentuh cukup besar di HP', async ({ page, isMobile }) => {
      test.skip(!isMobile, 'Hanya relevan di perangkat sentuh');
      await page.goto('/');
      const kotak = await page.getByRole('button', { name: 'Daftar' }).boundingBox();
      expect(kotak.height).toBeGreaterThanOrEqual(44);
    });
    

    Ringkasan

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    Get Started with Playwright and VS Code (2025 edition)Playwright · Bahasa InggrisBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.