Selenium #

Selenium adalah library otomasi browser yang memungkinkan kamu mengendalikan browser secara programatik — mengklik tombol, mengisi form, menunggu elemen muncul, mengambil screenshot, hingga mengekstrak data dari halaman yang di-render JavaScript. Di Go, Selenium diakses via binding github.com/tebeka/selenium yang mengimplementasikan protokol WebDriver W3C. Selenium sangat berguna untuk dua kebutuhan utama: end-to-end testing (memverifikasi bahwa aplikasi web bekerja benar dari sudut pandang pengguna) dan web scraping dari situs yang kontennya di-render oleh JavaScript sehingga tidak bisa diambil dengan request HTTP biasa. Artikel ini membahas instalasi lengkap, seluruh operasi dasar, strategi penanganan elemen dinamis, dan pola yang direkomendasikan untuk test suite yang stabil di production.

Instalasi #

Dependensi Go #

go get github.com/tebeka/selenium
go get github.com/tebeka/selenium/chrome

WebDriver dan Browser #

Selenium tidak mengendalikan browser secara langsung — ia berbicara ke WebDriver yang kemudian mengendalikan browser. Kamu perlu menginstal keduanya sesuai browser yang digunakan:

ChromeDriver (Chrome/Chromium):

# macOS
brew install chromedriver

# Ubuntu / Debian
apt-get install chromium-chromedriver

# Atau download manual dari: https://chromedriver.chromium.org/downloads
# Pastikan versi ChromeDriver cocok dengan versi Chrome yang terinstal
chromedriver --version
google-chrome --version

GeckoDriver (Firefox):

# macOS
brew install geckodriver

# Download manual dari: https://github.com/mozilla/geckodriver/releases
Versi ChromeDriver harus cocok dengan versi Chrome yang terinstal di sistem. Ketidakcocokan versi adalah penyebab paling umum kegagalan Selenium saat pertama kali setup. Gunakan chromedriver --version dan google-chrome --version untuk memverifikasi.

Arsitektur Selenium WebDriver #

Sebelum menulis kode, penting memahami bagaimana komponen Selenium berkomunikasi:

flowchart LR
    A["Kode Go\ngithub.com/tebeka/selenium"] -->|"HTTP (WebDriver Protocol W3C)"| B["ChromeDriver / GeckoDriver"]
    B -->|"CDP / Internal API"| C["Chrome / Firefox"]
    C -->|"Render halaman"| D["DOM"]
    D -->|"Response elemen, screenshot, dll"| B
    B -->|"JSON Response"| A

    style A fill:#00adb5,color:#fff
    style C fill:#f59e0b,color:#fff

Kode Go mengirim perintah sebagai HTTP request ke WebDriver server (ChromeDriver/GeckoDriver). WebDriver kemudian menerjemahkan perintah tersebut ke protokol internal browser. Ini berarti WebDriver harus berjalan sebagai proses terpisah sebelum program Go kamu dieksekusi.


Setup Dasar #

Ada dua cara menjalankan WebDriver: mengelolanya secara manual (kamu start sendiri) atau membiarkan library yang mengelolanya.

Cara 1: Library Mengelola WebDriver (Direkomendasikan untuk Testing) #

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/tebeka/selenium"
    "github.com/tebeka/selenium/chrome"
)

func main() {
    // Konfigurasi ChromeDriver
    opts := []selenium.ServiceOption{
        selenium.Output(nil), // arahkan output ke /dev/null
    }

    // Start ChromeDriver sebagai service
    service, err := selenium.NewChromeDriverService("chromedriver", 4444, opts...)
    if err != nil {
        log.Fatalf("gagal start ChromeDriver: %v", err)
    }
    defer service.Stop()

    // Konfigurasi Chrome
    caps := selenium.Capabilities{
        "browserName": "chrome",
    }
    chromeCaps := chrome.Capabilities{
        Args: []string{
            "--headless",           // jalankan tanpa UI (mode headless)
            "--no-sandbox",         // wajib di Docker/CI
            "--disable-dev-shm-usage", // mengurangi crash di container
            "--window-size=1920,1080",
        },
    }
    caps.AddChrome(chromeCaps)

    // Buat WebDriver session
    driver, err := selenium.NewRemote(caps, "http://localhost:4444/wd/hub")
    if err != nil {
        log.Fatalf("gagal membuat WebDriver session: %v", err)
    }
    defer driver.Quit()

    // Buka URL
    if err := driver.Get("https://example.com"); err != nil {
        log.Fatalf("gagal membuka URL: %v", err)
    }

    title, _ := driver.Title()
    fmt.Println("Judul halaman:", title)
}

Cara 2: Koneksi ke WebDriver yang Sudah Berjalan #

// Jalankan ChromeDriver secara manual di terminal:
// chromedriver --port=9515

caps := selenium.Capabilities{"browserName": "chrome"}
driver, err := selenium.NewRemote(caps, "http://localhost:9515/wd/hub")
if err != nil {
    log.Fatal(err)
}
defer driver.Quit()

Setelah session dibuat, kamu bisa mengendalikan navigasi browser sepenuhnya:

// Buka URL
driver.Get("https://example.com")

// Navigasi maju / mundur
driver.Back()
driver.Forward()
driver.Refresh()

// Mendapatkan info halaman saat ini
url,   _ := driver.CurrentURL()
title, _ := driver.Title()
source,_ := driver.PageSource() // HTML lengkap halaman

fmt.Printf("URL   : %s\n", url)
fmt.Printf("Title : %s\n", title)

// Mengatur ukuran window
driver.ResizeTo(1280, 800)

// Maximize / minimize
driver.MaximizeWindow("")

Menemukan Elemen #

Menemukan elemen di halaman adalah operasi paling sering dilakukan. Selenium menyediakan berbagai strategi pencarian:

// Cari satu elemen — mengembalikan error jika tidak ditemukan
elem, err := driver.FindElement(selenium.ByID, "username")
elem, err := driver.FindElement(selenium.ByName, "email")
elem, err := driver.FindElement(selenium.ByClassName, "btn-primary")
elem, err := driver.FindElement(selenium.ByCSSSelector, "#login-form .submit-btn")
elem, err := driver.FindElement(selenium.ByXPATH, "//button[@type='submit']")
elem, err := driver.FindElement(selenium.ByLinkText, "Masuk")
elem, err := driver.FindElement(selenium.ByPartialLinkText, "Mas")
elem, err := driver.FindElement(selenium.ByTagName, "h1")

// Cari banyak elemen — mengembalikan slice (kosong jika tidak ada)
elems, err := driver.FindElements(selenium.ByCSSSelector, "table tbody tr")
for _, row := range elems {
    text, _ := row.Text()
    fmt.Println(text)
}

// Cari elemen di dalam elemen lain (scoped search)
table, _ := driver.FindElement(selenium.ByID, "results-table")
rows,  _ := table.FindElements(selenium.ByTagName, "tr")

Strategi Pencarian: Mana yang Digunakan? #

flowchart TD
    A{Elemen punya ID unik?} -- Ya --> B["ByID\n#id-unik\n← paling cepat & stabil"]
    A -- Tidak --> C{Ada atribut\ndata-testid / aria-label?}
    C -- Ya --> D["ByCSSSelector\n[data-testid='btn-submit']\n← direkomendasikan untuk testing"]
    C -- Tidak --> E{Punya class\nyang unik?}
    E -- Ya --> F["ByCSSSelector\n.class-unik\n← mudah dibaca"]
    E -- Tidak --> G{Perlu traversal\nDOM yang kompleks?}
    G -- Ya --> H["ByXPATH\n//div[@class='parent']/button\n← fleksibel tapi rapuh"]
    G -- Tidak --> I["ByTagName / ByLinkText\nuntuk elemen semantik"]

    style B fill:#16a34a,color:#fff
    style D fill:#16a34a,color:#fff
Untuk test automation, tambahkan atribut data-testid ke elemen-elemen kunci di aplikasi kamu. Ini membuat selector lebih stabil karena tidak bergantung pada class CSS atau struktur DOM yang sering berubah saat styling dimodifikasi.

Berinteraksi dengan Elemen #

Setelah elemen ditemukan, kamu bisa melakukan berbagai interaksi:

Input Teks dan Klik #

// Mengisi input text
input, _ := driver.FindElement(selenium.ByID, "username")
input.Clear()                    // kosongkan dulu jika ada teks lama
input.SendKeys("[email protected]")

// Klik elemen
btn, _ := driver.FindElement(selenium.ByCSSSelector, "button[type='submit']")
btn.Click()

// Mengambil teks dari elemen
heading, _ := driver.FindElement(selenium.ByTagName, "h1")
text, _    := heading.Text()
fmt.Println("Heading:", text)

// Membaca atribut
link,  _ := driver.FindElement(selenium.ByCSSSelector, "a.profile-link")
href,  _ := link.GetAttribute("href")
class, _ := link.GetAttribute("class")

// Cek apakah elemen ditampilkan / enabled
displayed, _ := btn.IsDisplayed()
enabled, _   := btn.IsEnabled()
selected, _  := btn.IsSelected() // untuk checkbox / radio

Keyboard Khusus #

import "github.com/tebeka/selenium/keys"

// Tekan Enter
input.SendKeys(keys.Enter)

// Tekan Tab untuk pindah field
input.SendKeys(keys.Tab)

// Kombinasi Ctrl+A (select all)
input.SendKeys(keys.Control + "a")

// Escape
input.SendKeys(keys.Escape)
// Selenium tidak punya helper khusus untuk <select> di Go binding
// Gunakan pendekatan FindElement + Click

selectElem, _ := driver.FindElement(selenium.ByID, "category")

// Klik option berdasarkan value
option, _ := selectElem.FindElement(
    selenium.ByCSSSelector, "option[value='electronics']")
option.Click()

// Atau via JavaScript untuk select langsung
driver.ExecuteScript(
    `document.getElementById('category').value = 'electronics'`, nil)

Ini adalah bagian paling kritis dalam Selenium. Browser modern me-render konten secara asinkron — elemen belum tentu ada di DOM saat kamu mencarinya.

Explicit Wait (Direkomendasikan) #

// ANTI-PATTERN: time.Sleep — tidak andal dan lambat
time.Sleep(3 * time.Second) // ✗ bisa terlalu lama atau terlalu cepat
elem, _ := driver.FindElement(selenium.ByID, "result")

// BENAR: tunggu kondisi spesifik terpenuhi
func waitForElement(driver selenium.WebDriver, by, value string, timeout time.Duration) (selenium.WebElement, error) {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        elem, err := driver.FindElement(by, value)
        if err == nil {
            displayed, _ := elem.IsDisplayed()
            if displayed {
                return elem, nil
            }
        }
        time.Sleep(300 * time.Millisecond) // polling interval
    }
    return nil, fmt.Errorf("elemen %s=%q tidak muncul dalam %v", by, value, timeout)
}

// Penggunaan
result, err := waitForElement(driver, selenium.ByID, "search-results", 10*time.Second)
if err != nil {
    log.Fatal("timeout menunggu hasil pencarian:", err)
}

Wait untuk Berbagai Kondisi #

// Tunggu URL berubah (misalnya setelah redirect login)
func waitForURL(driver selenium.WebDriver, expectedURL string, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        current, _ := driver.CurrentURL()
        if current == expectedURL {
            return nil
        }
        time.Sleep(300 * time.Millisecond)
    }
    return fmt.Errorf("URL tidak berubah ke %s dalam %v", expectedURL, timeout)
}

// Tunggu teks muncul di elemen
func waitForText(driver selenium.WebDriver, by, value, expectedText string, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        elem, err := driver.FindElement(by, value)
        if err == nil {
            text, _ := elem.Text()
            if strings.Contains(text, expectedText) {
                return nil
            }
        }
        time.Sleep(300 * time.Millisecond)
    }
    return fmt.Errorf("teks %q tidak muncul dalam %v", expectedText, timeout)
}

// Tunggu elemen menghilang (misalnya loading spinner)
func waitForElementGone(driver selenium.WebDriver, by, value string, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        elem, err := driver.FindElement(by, value)
        if err != nil {
            return nil // elemen tidak ditemukan = sudah hilang
        }
        displayed, _ := elem.IsDisplayed()
        if !displayed {
            return nil
        }
        time.Sleep(300 * time.Millisecond)
    }
    return fmt.Errorf("elemen %s=%q masih ada setelah %v", by, value, timeout)
}
sequenceDiagram
    participant Go as Kode Go
    participant WD as WebDriver
    participant DOM

    Go->>WD: FindElement("result")
    WD->>DOM: Cari #result
    DOM-->>WD: Not found (JS masih loading)
    WD-->>Go: error

    loop Setiap 300ms hingga timeout
        Go->>Go: time.Sleep(300ms)
        Go->>WD: FindElement("result")
        WD->>DOM: Cari #result
        DOM-->>WD: Element ditemukan
        WD-->>Go: WebElement ✓
    end

JavaScript Execution #

Untuk interaksi yang tidak bisa dilakukan via API WebDriver standar, gunakan ExecuteScript:

// Scroll ke bawah halaman
driver.ExecuteScript("window.scrollTo(0, document.body.scrollHeight)", nil)

// Scroll ke elemen tertentu
elem, _ := driver.FindElement(selenium.ByID, "target-section")
driver.ExecuteScript("arguments[0].scrollIntoView(true)", []interface{}{elem})

// Klik via JavaScript (berguna jika elemen tertutup elemen lain)
driver.ExecuteScript("arguments[0].click()", []interface{}{elem})

// Mengisi nilai input yang tidak bisa diisi via SendKeys (React controlled input)
driver.ExecuteScript(
    `arguments[0].value = arguments[1];
     arguments[0].dispatchEvent(new Event('input', { bubbles: true }));
     arguments[0].dispatchEvent(new Event('change', { bubbles: true }));`,
    []interface{}{elem, "nilai baru"},
)

// Membaca nilai dari halaman
result, _ := driver.ExecuteScript("return document.title", nil)
title := result.(string)

// Menghapus atribut (misalnya readonly)
driver.ExecuteScript("arguments[0].removeAttribute('readonly')", []interface{}{elem})

Screenshot #

// Screenshot seluruh halaman
screenshot, err := driver.Screenshot()
if err != nil {
    log.Fatal("gagal mengambil screenshot:", err)
}

if err := os.WriteFile("screenshot.png", screenshot, 0644); err != nil {
    log.Fatal("gagal menyimpan screenshot:", err)
}

// Screenshot elemen spesifik
elem, _ := driver.FindElement(selenium.ByID, "chart-container")
elemScreenshot, _ := elem.Screenshot(false)
os.WriteFile("chart.png", elemScreenshot, 0644)

Menangani Popup dan Alert #

// Menerima (OK) alert
alert, err := driver.AlertText()
if err == nil {
    fmt.Println("Alert:", alert)
    driver.AcceptAlert()
}

// Menolak (Cancel) confirm dialog
driver.DismissAlert()

// Mengisi teks pada prompt dialog
driver.AlertText()                          // baca teks prompt
driver.SetAlertText("teks yang diisi")      // isi teks
driver.AcceptAlert()                        // tekan OK

Menangani Iframe #

Konten di dalam <iframe> tidak bisa diakses langsung — kamu harus berpindah konteks terlebih dahulu:

// Switch ke iframe berdasarkan index
driver.SwitchFrame(0) // iframe pertama di halaman

// Switch ke iframe berdasarkan nama atau ID
driver.SwitchFrame("payment-iframe")

// Switch ke iframe berdasarkan elemen WebElement
iframe, _ := driver.FindElement(selenium.ByCSSSelector, "iframe.content-frame")
driver.SwitchFrame(iframe)

// Interaksi di dalam iframe — sekarang bisa diakses
innerBtn, _ := driver.FindElement(selenium.ByID, "submit-inside-iframe")
innerBtn.Click()

// Kembali ke konteks halaman utama
driver.SwitchFrame(nil)

// Kembali ke parent frame (untuk nested iframe)
driver.SwitchToParentFrame()

Multiple Window dan Tab #

// Simpan handle window saat ini
mainWindow, _ := driver.CurrentWindowHandle()

// Klik link yang membuka tab baru
link, _ := driver.FindElement(selenium.ByCSSSelector, "a[target='_blank']")
link.Click()

// Dapatkan semua handle window
handles, _ := driver.WindowHandles()

// Switch ke tab baru (handle terakhir)
for _, handle := range handles {
    if handle != mainWindow {
        driver.SwitchWindow(handle)
        break
    }
}

// Lakukan sesuatu di tab baru
url, _ := driver.CurrentURL()
fmt.Println("URL tab baru:", url)

// Tutup tab ini dan kembali ke tab utama
driver.Close()
driver.SwitchWindow(mainWindow)

Web Scraping dengan Selenium #

Selenium cocok untuk scraping situs yang kontennya di-render via JavaScript. Berikut contoh scraping yang terstruktur:

type Product struct {
    Name  string
    Price string
    URL   string
}

func scrapeProducts(driver selenium.WebDriver, baseURL string) ([]Product, error) {
    var products []Product

    page := 1
    for {
        url := fmt.Sprintf("%s?page=%d", baseURL, page)
        if err := driver.Get(url); err != nil {
            return nil, fmt.Errorf("gagal membuka halaman %d: %w", page, err)
        }

        // Tunggu konten dimuat
        _, err := waitForElement(driver, selenium.ByCSSSelector, ".product-card", 10*time.Second)
        if err != nil {
            break // tidak ada produk lagi, selesai
        }

        // Ambil semua card produk di halaman ini
        cards, _ := driver.FindElements(selenium.ByCSSSelector, ".product-card")
        if len(cards) == 0 {
            break
        }

        for _, card := range cards {
            nameElem, err := card.FindElement(selenium.ByCSSSelector, ".product-name")
            if err != nil {
                continue
            }
            name, _ := nameElem.Text()

            priceElem, err := card.FindElement(selenium.ByCSSSelector, ".product-price")
            if err != nil {
                continue
            }
            price, _ := priceElem.Text()

            linkElem, err := card.FindElement(selenium.ByTagName, "a")
            if err != nil {
                continue
            }
            href, _ := linkElem.GetAttribute("href")

            products = append(products, Product{
                Name:  name,
                Price: price,
                URL:   href,
            })
        }

        // Cek apakah ada halaman berikutnya
        nextBtn, err := driver.FindElement(selenium.ByCSSSelector, ".pagination .next:not(.disabled)")
        if err != nil {
            break // tidak ada tombol next, selesai
        }
        nextBtn.Click()
        page++

        // Jeda kecil agar tidak membebani server
        time.Sleep(500 * time.Millisecond)
    }

    return products, nil
}

Pola Page Object Model (POM) #

Untuk test suite yang besar, hindari menulis selector dan interaksi langsung di test. Gunakan Page Object Model — setiap halaman direpresentasikan oleh struct yang merangkum semua interaksi:

// ANTI-PATTERN: selector tersebar di seluruh test
func TestLogin(t *testing.T) {
    driver.FindElement(selenium.ByID, "username").SendKeys("[email protected]")
    driver.FindElement(selenium.ByID, "password").SendKeys("secret")
    driver.FindElement(selenium.ByCSSSelector, "button[type='submit']").Click()
    // Jika ID "username" berubah, semua test yang pakai ini harus diupdate
}

// BENAR: Page Object Model — perubahan selector hanya di satu tempat
type LoginPage struct {
    driver selenium.WebDriver
}

func NewLoginPage(driver selenium.WebDriver) *LoginPage {
    return &LoginPage{driver: driver}
}

func (p *LoginPage) Open() error {
    return p.driver.Get("https://example.com/login")
}

func (p *LoginPage) FillUsername(username string) error {
    elem, err := p.driver.FindElement(selenium.ByID, "username")
    if err != nil {
        return err
    }
    elem.Clear()
    return elem.SendKeys(username)
}

func (p *LoginPage) FillPassword(password string) error {
    elem, err := p.driver.FindElement(selenium.ByID, "password")
    if err != nil {
        return err
    }
    elem.Clear()
    return elem.SendKeys(password)
}

func (p *LoginPage) Submit() error {
    btn, err := p.driver.FindElement(selenium.ByCSSSelector, "button[type='submit']")
    if err != nil {
        return err
    }
    return btn.Click()
}

func (p *LoginPage) Login(username, password string) error {
    if err := p.FillUsername(username); err != nil {
        return err
    }
    if err := p.FillPassword(password); err != nil {
        return err
    }
    return p.Submit()
}

// Di test — bersih dan mudah dibaca
func TestSuccessfulLogin(t *testing.T) {
    loginPage := NewLoginPage(driver)
    loginPage.Open()
    err := loginPage.Login("[email protected]", "secret")
    if err != nil {
        t.Fatal("gagal login:", err)
    }
    waitForURL(driver, "https://example.com/dashboard", 5*time.Second)
}
graph TD
    subgraph "Test Files"
        A["TestLogin"]
        B["TestCheckout"]
        C["TestSearch"]
    end
    subgraph "Page Objects"
        D["LoginPage\n- Open()\n- Login(user, pass)\n- FillUsername()\n- FillPassword()"]
        E["CheckoutPage\n- AddToCart()\n- Checkout()\n- FillAddress()"]
        F["SearchPage\n- Search(query)\n- GetResults()"]
    end
    subgraph "WebDriver"
        G["selenium.WebDriver\nFindElement, Click,\nSendKeys, dll"]
    end
    A --> D
    B --> E
    C --> F
    D --> G
    E --> G
    F --> G

Menjalankan di CI/CD (Docker) #

Untuk menjalankan test Selenium di pipeline CI/CD, gunakan Selenium Grid via Docker:

# docker-compose.yml
version: "3.8"
services:
  selenium-hub:
    image: selenium/hub:4.18
    ports:
      - "4442:4442"
      - "4443:4443"
      - "4444:4444"

  chrome:
    image: selenium/node-chrome:4.18
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
# Jalankan Selenium Grid
docker-compose up -d

# Jalankan test Go yang terkoneksi ke Grid
SE_URL=http://localhost:4444/wd/hub go test ./tests/...
// Koneksi ke Selenium Grid di CI
func newDriver() (selenium.WebDriver, error) {
    gridURL := os.Getenv("SE_URL")
    if gridURL == "" {
        gridURL = "http://localhost:4444/wd/hub"
    }

    caps := selenium.Capabilities{"browserName": "chrome"}
    chromeCaps := chrome.Capabilities{
        Args: []string{
            "--headless",
            "--no-sandbox",
            "--disable-dev-shm-usage",
        },
    }
    caps.AddChrome(chromeCaps)

    return selenium.NewRemote(caps, gridURL)
}

Kapan Tidak Menggunakan Selenium #

Gunakan Selenium jika:
  ✓ Situs target me-render konten via JavaScript (SPA, React, Vue)
  ✓ Butuh interaksi UI yang kompleks (klik, drag, hover, iframe)
  ✓ End-to-end testing yang mensimulasikan perilaku pengguna nyata
  ✓ Butuh screenshot atau visual regression testing

Pertimbangkan net/http + goquery (scraping HTML statis) jika:
  ✗ Situs target me-render HTML di server (konten sudah ada di response HTTP)
  ✗ Performa adalah prioritas — Selenium jauh lebih lambat dari HTTP request biasa
  ✗ Butuh scraping dalam skala besar (ratusan halaman per menit)

Pertimbangkan chromedp (Chrome DevTools Protocol langsung) jika:
  ✗ Butuh kontrol lebih rendah ke Chrome tanpa lapisan WebDriver
  ✗ Ingin dependency yang lebih ringan (chromedp tidak butuh ChromeDriver terpisah)
  ✗ Performa headless Chrome adalah prioritas

Ringkasan #

  • Arsitektur tiga lapis — kode Go berbicara ke ChromeDriver/GeckoDriver via HTTP, WebDriver kemudian mengendalikan browser; keduanya harus berjalan sebelum program Go dieksekusi.
  • Versi harus cocok — pastikan versi ChromeDriver selalu sinkron dengan versi Chrome yang terinstal; ketidakcocokan adalah penyebab error paling umum.
  • Explicit wait, bukan time.Sleep — buat fungsi wait dengan polling loop yang menunggu kondisi spesifik terpenuhi; time.Sleep dengan durasi tetap tidak andal dan memperlambat test suite.
  • Strategi selector — prioritaskan ByID > CSS selector dengan data-testid > CSS selector umum > XPath; tambahkan atribut data-testid ke elemen kunci di aplikasi untuk membuat test lebih stabil.
  • Page Object Model — enkapsulasi selector dan interaksi setiap halaman ke dalam struct; perubahan UI hanya perlu diupdate di satu tempat, bukan di seluruh test.
  • ExecuteScript sebagai escape hatch — gunakan untuk interaksi yang tidak bisa dilakukan via API WebDriver: scroll, klik elemen tersembunyi, mengisi React controlled input.
  • Iframe butuh switch frame — selalu SwitchFrame(iframe) sebelum berinteraksi dengan konten di dalam iframe, dan SwitchFrame(nil) untuk kembali ke halaman utama.
  • Headless di CI — gunakan flag --headless --no-sandbox --disable-dev-shm-usage saat menjalankan di Docker atau pipeline CI; Selenium Grid via Docker adalah cara paling andal untuk setup CI.

← Sebelumnya: Libraries   Berikutnya: Artikel & Sumber Daya →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact