CSS Selector
Duy Văn | 16/1/2025
Khái niệm
CSS Selector về cơ bản là một cách để chọn phần tử HTML mà ta muốn thao tác. Ban đầu thì nó được dùng để có thể áp dụng styling cho các phần tử HTML, HTML sẽ dựa vào CSS Selector để biết được phần tử nào cần được thay đổi, ví dụ
p {
color: red;
}
Đoạn code trên nằm ở file CSS, nó sẽ hiểu rằng tất cả các thẻ <p> sẽ có màu chữ là màu đỏ.
Đồng thời, trong Browser Automation, chúng ta sẽ sử dụng CSS Selector để chọn phần tử HTML mà chúng ta muốn xác định, có thể là để thao tác click, hay lấy ra nội dung của thẻ, v.v…
Hiệu quả
- CSS Selector về cơ bản là nhanh hơn XPath, vì trình duyệt được tối ưu để làm việc với CSS Selector do nó vốn dành cho áp dụng styling cho trang web, còn XPath thì lại là một ngôn ngữ truy vấn XML.
- CSS Selector dễ học hơn, vì nó đã bao gồm ở trong cách viết CSS, ai làm quen Web Dev hay Front End thì sẽ ngay lập tức biết CSS Selector, ít nhất là mức cơ bản
- CSS Selector vì đơn giản, nên nếu một element đã có thể sử dụng CSS Selector để chọn ra thì nó sẽ ngắn hơn so với XPath, ngược lại, vì đơn giản nên sẽ có một số kiểu xác định tương đối (dựa theo cha, theo con như bài trước) không thể thực hiện được
Syntax
Syntax Chung
Một syntax cơ bản để xác định 1 phần tử trong trang có mẫu như sau:
div#idname.classname[attribute="value"]
div: tên thẻ HTML, có thể thay bằng bất kỳ thẻ nào khác, như làp,a, …#idname: id của phần tử, không bắt buộc phải có, tuỳ trang web được thiết kế như thế nào.classname: class của phần tử, cũng không bắt buộc phải có, tuỳ trang web[attribute="value"]: thuộc tính của phần tử, cũng không bắt buộc phải có, nó sẽ nằm ở dạngattribute="value", ví dụhref="https://google.com", tuỳ theo cách bên phát triển thiết kế
Child / Descendant / Sibling
Để đi từ thẻ lớn vào thẻ ở trong, chúng ta có 2 hướng:
div > input- direct child - tức là các thẻ là con trực tiếp của thẻdivdiv input- all descendants - tức là các thẻ nằm ở trong thẻdiv, không cần phải là con trực tiếp
Nếu chúng ta cần tìm thẻ ngang hàng với thẻ input, có hai hướng:
input + p- direct sibling, chúng ta đang tìm thẻplà direct sibling của thẻinput, direct sibling ở đây có nghĩa là thẻ ngang hàng với thẻinput, và nằm ngay sau thẻinputinput ~ p- sibling, chúng ta đang tìm tất cả các thẻplà sibling của thẻinput, sibling ở đây có nghĩa là các thẻ ngang hàng với thẻinput, nằm sau thẻinput
Một điều quan trọng cần nhớ chính là CSS Selector không có khả năng leo lên các thẻ bậc cao hơn mà chỉ có thể đi xuống từ các thẻ lớn vào thẻ ở trong
Đồng thời chúng ta có syntax sử dụng pseudo class để bắt các phần tử con:
Con đầu và con cuối có thể dùng :first-child và :last-child pseudo-classes.
Ví dụ:
div:first-child- thẻ con đầu tiên nằm trong thẻdivdiv:last-child- thẻ con cuối cùng nằm trong thẻdiv
Một cách khác chính là bắt theo thứ tự để nth-child(1) và nth-child(last), đồng thời chúng ta có thể bắt thẻ con ở thứ tự bất kỳ
Ví dụ:
div:nth-child(1)- thẻ con thứ nhất nằm trong thẻdivdiv:nth-child(2)- thẻ con thứ hai nằm trong thẻdivdiv:nth-last-child(1)- thẻ con đầu tiên tính từ cuối lên trong thẻdiv
attribute
Như đã nêu ở syntax chung, thì chúng ta có thể xác định phần tử dựa vào các thuộc tính của nó, ví dụ:
input[type="text"]
Ở đây chúng ta đang chọn tất cả các thẻ input có thuộc tính type là text, ngoài ra, nếu chúng ta để trống giá trị
input[type]
Hiểu rằng chúng ta đang chọn tất cả các thẻ input có thuộc tính type, không quan trọng giá trị của nó
Substring matching
Đôi khi các thuộc tính có thể sẽ rất dài và việc rút gọn cú pháp sẽ giúp quá trình code nhanh hơn, có 4 cáhc rút gọn cú pháp:
^- starts with : bắt đầu bằng$- ends with : kết thúc bằng*- contains : chứa~- space-separated : có dấu cách
Ví dụ:
input[id^='test']- các thẻ input mà có id bắt đầu bằng ‘test’input[id$='id']- các thẻ input mà có id kết thúc bằng ‘id’input[id*='id']- các thẻ input mà có id chứa ‘id’input[id~='id']- các thẻ input mà có id có dấu cách chứa ‘id’
~= có thể áp dụng để tìm các thuộc tính có giá trị dạng “my id” và sẽ bỏ qua các kết quả có giá trị là “id123”, còn *= sẽ lấy tất
id chứa dấu cách
Giả sử với một id chứa dấu cách “my id”, thì để tìm đúng theo “my id” thì làm thế nào?
input[id='my id']
id cũng là thuộc tính nên hoàn toàn có thể sử dụng cú pháp như trên
class chứa dấu cách
Tương tự như id, class cũng có thể chứa dấu cách, và cách xác định cũng tương tự
input[class='my class']
Ngoài ra, do bản chất của class, nếu một thẻ có class dạng “class1 class2” thì hiểu rằng thẻ này thuộc về cả 2 class “class1” và “class2”, nên việc xác định class chứa dấu cách cũng có thể được thực hiện bằng cách
input.class1.class2
all
* sẽ chọn tất cả các phần tử, có bao nhiêu phần tử thì nó chọn hết, vì thế số lượng kết quả nó trả ra khi inspect sử dụng * chính là số lượng element có trong trang.
Giả sử như chúng ta đang có 8 elements div đều có class là container, thì việc sử dụng
* div.container
hay
div.container
Đều sẽ cùng được hiểu là chọn tất cả các element div có class là container, tuy nhiên nếu chúng ta đang nói đến việc hướng dẫn một framework tự động hoá trình duyệt thì cách tiếp cận của nó sẽ khác nhau:
- Với
* div.containerthì nó đầu tiên sẽ chọn tất cả các element theo*, rồi sau đó mới lọc xuống các thẻdivcó class làcontainer - Với
div.containerthì nó sẽ chọn trực tiếp các thẻdivcó class làcontainer
Sự khác biệt này dẫn đến hiệu năng chênh lệch, sử dụng * sẽ chậm hơn do cách tiếp cận phức tạp hơn
pseudo-elements
Tuy việc sử dụng pseudo-elements để áp dụng style CSS thì được
div::before {
content: "Hello";
}
Nhưng việc tìm elements lại không thể thực hiện theo như ở đây
CSS pseudo-elements will never return any elements, as specified in the Selectors API.
Tức là, giả sử có một button có ::before và ::after pseudo-elements, thì việc sử dụng CSS Selector để tìm ra button đó sẽ không thể thực hiện được
Một trường hợp mà các tester vướng phải đó là xác minh xem button đó đã được ấn hay chưa dựa trên pseudo-elements, thì việc xác minh này không thể làm được mà cần dựa vào các dấu hiệu khác.