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ườnglocator.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 buttontốt hơnbody > 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-textvs:textvs:text-is::has-textmatch substring ở mọi descendant;:textmatch substring trong chính element;:text-isexact match. Nhầm lẫn → match thừa, strict mode violation.- XPath
//tìm TOÀN TRANG: tronglocator.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 codegenthay 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.
