Wiki

← Tất cả bài wiki

CSS & XPath Selectors (Selector truyền thống & fallback engines)

automationplaywright

CSS & XPath là hai ngôn ngữ selector kinh điển mà Playwright hỗ trợ qua engine css= (mặc định) và xpath=, cộng thêm engine tiện ích text=, id=, data-testid=, visible=. Chúng mạnh về mặt kỹ thuật nhưng giòn trước refactor — nên dùng làm fallback sau khi getBy* không đủ, và cần biết một số selector CSS "chỉ Playwright có" như :has-text(), :visible, :has().

Khái niệm & Bản chất

  • CSS selector = mặc định: page.locator('button.submit') tự hiểu là CSS — không cần prefix. Mạnh ở: class/id/attribute, combinator (>, , ~), pseudo-class, attribute partial match ([class*=...]).
  • XPath = khi CSS bất lực: duy nhất đi được lên ancestor (..), tìm theo nội dung text chính xác, index trong sibling — nhưng verbose, khó đọc, và team hiện đại hiếm dùng.
  • Engine prefix của Playwright: css=, xpath=, text=, id=, data-testid=, nth=, visible= — kết hợp được bằng >> (vd: css=article >> text=Mua ngay).
  • Selector mở rộng của Playwright (không phải CSS chuẩn): :has-text("..."), :text("..."), :text-is("...") (exact), :visible, :has(...), :nth-match() — chạy trong engine Playwright, đừng copy sang jQuery/CSS thuần.
  • Bản chất vẫn là Locator: dù dùng CSS hay XPath, kết quả page.locator() vẫn là locator lazy + auto-wait — sự khác biệt chỉ nằm ở "độ giòn" của selector trước thay đổi UI.

Cách hoạt động & API chính

  • CSS cơ bản: .class, #id, tag, [attr="v"], [attr*="partial"], parent > child, ancestor descendant, li:nth-child(2), input:not([disabled]).
  • CSS pseudo mở rộng của Playwright: button:has-text("Lưu"), div:has(> img), tr:visible, :nth-match(.item, 2) — bù đúng những lỗ hổng khiến người ta phải bỏ CSS sang XPath.
  • XPath: bắt đầu // (mọi nơi) hoặc ./ (trong scope locator cha); //button[text()='Lưu'], //label[text()='Email']/following-sibling::input, ancestor::, parent::div.
  • text= engine: page.locator('text=Đăng nhập') — ngắn gọn hơn getByText khi nhanh, hỗ trợ 'text="exact"' (nháy đơn + ngoặc kép = exact match).
  • Nối engine bằng >>: page.locator('form#checkout >> css=input[name=card]') — dùng khi cần trộn engine, nhưng thường locator.locator() trong code rõ ràng hơn.

Ví dụ & Cú pháp thực tế

  • CSS trong locator: ``ts import { test, expect } from '@playwright/test'; // class + attribute await page.locator('input[name="email"]').fill('a@b.com'); // combinator: chỉ lấy button con trực tiếp của form await page.locator('form#login > button[type="submit"]').click(); // pseudo mở rộng của Playwright await page.locator('button:has-text("Thanh toán")').click(); await expect(page.locator('.toast:visible')).toHaveCount(1); // :has để lấy card chứa item cụ thể const card = page.locator('.product-card', { hasText: 'iPhone' }); ``
  • XPath khi thật sự cần: ``ts // đi ngược lên cha — CSS không làm được const row = page.locator('xpath=//span[text()="SKU-123"]/ancestor::tr'); // sibling đi sau await page.locator('xpath=//label[text()="Email"]/following-sibling::input').fill('x@y.z'); ``
  • Trộn engine: ``ts // scoped: tìm text trong đúng vùng bằng >> await page.locator('css=.order-summary >> text=Tổng cộng').waitFor(); // tương đương viết rõ ràng hơn bằng chain await page.locator('.order-summary').getByText('Tổng cộng').waitFor(); ``
  • id= và data-testid= shortcut: page.locator('id=submit-btn') tương đương #submit-btn; page.locator('data-testid=menu') tương đương [data-testid="menu"].

Best practices & Khi nào dùng

  • Khi nào chấp nhận CSS/XPath: element không có role/text/testid (icon, layout container, list động), hoặc legacy app không sửa markup được — lúc đó chọn selector ít giòn nhất có thể.
  • Ưu tiên attribute ổn định: [name="email"], [type="submit"], [data-*] bền hơn class do build tool sinh (css-1x2y3z, MuiButton-root đổi mỗi build).
  • Độ sâu tối thiểu: .cart .item button tốt hơn body > div > main > div > div.cart > ul > li > div > button — càng nhiều tầng càng vỡ sớm.
  • Chỉ dùng XPath cho việc CSS không làm được: đi lên parent, text matching phức tạp, hoặc XML/SVG. Còn lại giữ CSS để team đọc dễ.
  • Dùng page.locator(css).filter() thay vì nhồi hết vào 1 chuỗi — selector ngắn + filter theo hasText/has đọc và bảo trì dễ hơn mega-selector.

Gotchas, So sánh & Lưu ý

  • Selector giòn = test giòn: class auto-generated, index nth-child, đường dẫn tuyệt đối từ body — ba ông trùm gây broken test khi dev đổi layout dù chức năng vẫn đúng.
  • :has-text vs :text vs :text-is: :has-text match substring ở mọi descendant; :text match substring trong chính element; :text-is exact match. Nhầm lẫn → match thừa, strict mode violation.
  • XPath // tìm TOÀN TRANG: trong locator.locator('xpath=//div') dấu // vẫn chạy global chứ không scoped — phải dùng .// để tìm trong subtree. Đây là bug kinh điển.
  • Không copy selector từ DevTools Copy → Copy selector nguyên xi: DevTools sinh đường dẫn tuyệt đối cực giòn. Dùng npx playwright codegen thay thế.
  • So sánh Selenium By.cssSelector/By.xpath: cú pháp giống hệt, nhưng Playwright thêm pseudo mở rộng + engine chaining + lazy resolution — nên ít phải fallback hơn hẳn.
  • Shadow DOM: CSS của Playwright tự xuyên shadow root (page.locator('my-element .inner') hoạt động); XPath KHÔNG xuyên shadow DOM — một lý do nữa để ưu tiên CSS.