Puppetaria: tập lệnh Puppeteer ưu tiên hỗ trợ tiếp cận

Johan Bay
Johan Bay

Puppeteer và cách tiếp cận bộ chọn

Puppeteer là một thư viện tự động hoá trình duyệt cho Nút: cho phép bạn kiểm soát trình duyệt bằng API JavaScript đơn giản và hiện đại.

Tất nhiên, tác vụ nổi bật nhất trên trình duyệt là duyệt web. Việc tự động hoá tác vụ này về cơ bản tương tự như việc tự động hoá các hoạt động tương tác với trang web.

Trong Puppeteer, điều này đạt được bằng cách truy vấn các phần tử DOM bằng cách sử dụng bộ chọn dựa trên chuỗi và thực hiện các hành động như nhấp hoặc nhập văn bản trên các phần tử đó. Ví dụ: một tập lệnh mở ra developer.google.com, tìm hộp tìm kiếm và tìm kiếm puppetaria có thể trông giống như sau:

(async () => {
   const browser = await puppeteer.launch({ headless: false });
   const page = await browser.newPage();
   await page.goto('https://developers.google.com/', { waitUntil: 'load' });
   // Find the search box using a suitable CSS selector.
   const search = await page.$('devsite-search > form > div.devsite-search-container');
   // Click to expand search box and focus it.
   await search.click();
   // Enter search string and press Enter.
   await search.type('puppetaria');
   await search.press('Enter');
 })();

Do đó, cách xác định các phần tử bằng bộ chọn truy vấn là một phần xác định trong trải nghiệm Puppeteer. Cho đến hiện tại, các bộ chọn trong Puppeteer bị giới hạn ở bộ chọn CSS và giám sát.

Bộ chọn cú pháp và ngữ nghĩa

Bộ chọn CSS về bản chất có cú pháp; chúng được liên kết chặt chẽ với hoạt động bên trong của cách trình bày văn bản của cây DOM theo nghĩa là chúng tham chiếu ID và tên lớp từ DOM. Do đó, chúng cung cấp một công cụ không thể thiếu cho các nhà phát triển web để sửa đổi hoặc thêm kiểu cho một phần tử trong trang, nhưng trong ngữ cảnh đó, nhà phát triển có toàn quyền kiểm soát trang và cây DOM của trang đó.

Mặt khác, tập lệnh Puppeteer là trình quan sát bên ngoài của trang, do đó, khi sử dụng bộ chọn CSS trong ngữ cảnh này, tập lệnh sẽ đưa ra các giả định ẩn về cách triển khai trang mà tập lệnh Puppeteer không có quyền kiểm soát.

Kết quả là các tập lệnh như vậy có thể giòn và dễ bị thay đổi mã nguồn. Ví dụ: giả sử bạn sử dụng các tập lệnh Puppeteer để kiểm thử tự động cho một ứng dụng web chứa nút <button>Submit</button> làm phần tử con thứ ba của phần tử body. Một đoạn mã từ trường hợp kiểm thử có thể trông như sau:

const button = await page.$('body:nth-child(3)'); // problematic selector
await button.click();

Ở đây, chúng ta đang dùng bộ chọn 'body:nth-child(3)' để tìm nút gửi, nhưng bộ chọn này có liên kết chặt chẽ với chính xác phiên bản này của trang web. Nếu sau đó một phần tử được thêm vào phía trên nút, thì bộ chọn này sẽ không hoạt động nữa!

Đây không phải là tin tức để kiểm tra người viết: Người dùng Puppeteer đã cố gắng chọn các bộ chọn có khả năng chịu được những thay đổi như vậy. Với Puppetaria, chúng tôi mang đến cho người dùng một công cụ mới trong nhiệm vụ này.

Puppeteer hiện cung cấp một trình xử lý truy vấn thay thế dựa trên việc truy vấn cây hỗ trợ tiếp cận thay vì dựa vào bộ chọn CSS. Tư tưởng cơ bản ở đây là nếu phần tử cụ thể mà chúng ta muốn chọn chưa thay đổi thì nút hỗ trợ tiếp cận tương ứng cũng sẽ không thay đổi.

Chúng tôi đặt tên cho các bộ chọn đó là "bộ chọn ARIA" và hỗ trợ truy vấn tên cũng như vai trò có thể truy cập đã tính toán của cây hỗ trợ tiếp cận. So với bộ chọn CSS, các thuộc tính này về bản chất sẽ có ngữ nghĩa. Chúng không gắn liền với các thuộc tính cú pháp của DOM mà thay vào đó là nội dung mô tả cho cách trang được quan sát thông qua các công nghệ hỗ trợ như trình đọc màn hình.

Trong ví dụ về tập lệnh kiểm thử ở trên, chúng ta có thể sử dụng bộ chọn aria/Submit[role="button"] để chọn nút mong muốn, trong đó Submit là tên thành phần hỗ trợ tiếp cận:

const button = await page.$('aria/Submit[role="button"]');
await button.click();

Bây giờ, nếu sau này chúng ta quyết định thay đổi nội dung văn bản của nút từ Submit thành Done thì thử nghiệm sẽ lại không thành công, nhưng trong trường hợp này là đáng mong muốn; bằng cách thay đổi tên của nút, chúng ta thay đổi nội dung của trang, trái ngược với cách trình bày trực quan của trang hoặc cách cấu trúc của trang trong DOM. Các thử nghiệm sẽ cảnh báo chúng tôi về những thay đổi đó để đảm bảo rằng những thay đổi đó là có chủ ý.

Quay lại ví dụ lớn hơn với thanh tìm kiếm, chúng ta có thể tận dụng trình xử lý aria mới và thay thế

const search = await page.$('devsite-search > form > div.devsite-search-container');

với

const search = await page.$('aria/Open search[role="button"]');

để định vị thanh tìm kiếm!

Nói chung, chúng tôi tin rằng việc sử dụng bộ chọn ARIA như vậy có thể mang lại những lợi ích sau cho người dùng Puppeteer:

  • Giúp bộ chọn trong tập lệnh kiểm thử thích ứng hơn với những thay đổi về mã nguồn.
  • Làm cho tập lệnh kiểm thử dễ đọc hơn (tên dễ tiếp cận là bộ mô tả ngữ nghĩa).
  • Khuyến khích các phương pháp hay trong việc chỉ định thuộc tính hỗ trợ tiếp cận cho các phần tử.

Phần còn lại của bài viết này sẽ trình bày chi tiết về cách chúng tôi triển khai dự án Puppetaria.

Quy trình thiết kế

Thông tin khái quát

Như đã đề cập ở trên, chúng ta muốn bật các phần tử truy vấn theo tên và vai trò dễ tiếp cận của chúng. Đây là các thuộc tính của cây hỗ trợ tiếp cận (một cây DOM kép) thông thường, được các thiết bị như trình đọc màn hình sử dụng để hiển thị trang web.

Từ việc xem thông số kỹ thuật để tính toán tên thành phần hỗ trợ tiếp cận, chúng ta thấy rõ rằng việc tính toán tên cho một phần tử là một công việc không hề quan trọng, do đó ngay từ đầu, chúng ta đã quyết định muốn sử dụng lại cơ sở hạ tầng hiện có của Chromium cho việc này.

Cách chúng tôi triển khai

Ngay cả khi chúng ta chỉ sử dụng cây hỗ trợ tiếp cận của Chromium, chúng ta vẫn có khá nhiều cách để triển khai truy vấn ARIA trong Puppeteer. Để biết lý do, trước tiên hãy xem cách Puppeteer kiểm soát trình duyệt.

Trình duyệt hiển thị giao diện gỡ lỗi thông qua một giao thức có tên là Giao thức Công cụ của Chrome cho nhà phát triển (CDP). Thao tác này sẽ hiển thị chức năng như "tải lại trang" hoặc "thực thi đoạn mã JavaScript này trên trang và trả lại kết quả" thông qua một giao diện không phụ thuộc vào ngôn ngữ.

Cả giao diện người dùng Công cụ cho nhà phát triển và Trình chỉnh sửa đều sử dụng CDP để giao tiếp với trình duyệt. Để triển khai các lệnh CDP, có cơ sở hạ tầng Công cụ cho nhà phát triển bên trong tất cả các thành phần của Chrome: trong trình duyệt, trong trình kết xuất, v.v. CDP đảm nhận việc định tuyến các lệnh đến đúng vị trí.

Các thao tác của trình chỉnh sửa như truy vấn, nhấp và đánh giá biểu thức được thực hiện bằng cách tận dụng các lệnh CDP như Runtime.evaluate để đánh giá JavaScript trực tiếp trong ngữ cảnh trang và trả về kết quả. Các thao tác khác của Puppeteer như mô phỏng khiếm khuyết về thị giác màu, chụp ảnh màn hình hoặc ghi lại dấu vết sẽ sử dụng CDP để giao tiếp trực tiếp với quá trình kết xuất Blink.

CDP

Điều này cho phép chúng ta triển khai chức năng truy vấn theo hai đường dẫn; chúng ta có thể:

  • Viết logic truy vấn của chúng ta trong JavaScript và chèn logic đó vào trang bằng cách sử dụng Runtime.evaluate, hoặc
  • Sử dụng điểm cuối CDP có thể truy cập và truy vấn cây hỗ trợ tiếp cận trực tiếp trong quy trình Blink.

Chúng tôi đã triển khai 3 nguyên mẫu:

  • Truyền tải DOM DOM – dựa trên việc chèn JavaScript vào trang
  • Truyền tải qua Puppeteer AXTree – dựa trên việc sử dụng quyền truy cập CDP hiện có vào cây hỗ trợ tiếp cận
  • Truyền tải DOM CDP – sử dụng một điểm cuối CDP mới được thiết kế riêng cho việc truy vấn cây hỗ trợ tiếp cận

Truyền tải DOM JS

Nguyên mẫu này thực hiện việc truyền tải toàn bộ DOM, đồng thời sử dụng element.computedNameelement.computedRole, được kiểm soát trên cờ khởi chạy ComputedAccessibilityInfo, để truy xuất tên và vai trò của từng phần tử trong quá trình truyền tải.

Truyền tải qua Puppeteer AXTree

Ở đây, chúng ta truy xuất toàn bộ cây hỗ trợ tiếp cận thông qua CDP và truyền tải cây đó trong Puppeteer. Sau đó, các nút hỗ trợ tiếp cận thu được sẽ được ánh xạ tới các nút DOM.

Truyền tải DOM CDP

Đối với nguyên mẫu này, chúng tôi đã triển khai điểm cuối CDP mới dành riêng cho việc truy vấn cây hỗ trợ tiếp cận. Bằng cách này, việc truy vấn có thể diễn ra ở hệ thống phụ trợ thông qua việc triển khai C++ thay vì trong ngữ cảnh trang thông qua JavaScript.

Điểm chuẩn kiểm thử đơn vị

Hình sau đây so sánh tổng thời gian chạy để truy vấn 4 phần tử 1.000 lần cho 3 nguyên mẫu. Điểm chuẩn được thực thi ở 3 cấu hình khác nhau, có thể thay đổi kích thước trang và liệu tính năng lưu các phần tử hỗ trợ tiếp cận vào bộ nhớ đệm có được bật hay không.

Điểm chuẩn: Tổng thời gian chạy truy vấn 4 phần tử 1.000 lần

Rõ ràng là có sự khác biệt đáng kể về hiệu suất giữa cơ chế truy vấn dựa trên CDP và hai cơ chế khác chỉ được triển khai trong Puppeteer, và sự khác biệt tương đối dường như tăng đáng kể theo kích thước trang. Thật thú vị khi thấy rằng nguyên mẫu truyền tải DOM JS đáp ứng rất tốt việc bật chức năng lưu vào bộ nhớ đệm hỗ trợ tiếp cận. Khi tính năng lưu vào bộ nhớ đệm đang tắt, cây hỗ trợ tiếp cận sẽ được tính toán theo yêu cầu và loại bỏ cây này sau mỗi lượt tương tác nếu miền bị tắt. Thay vào đó, việc bật miền sẽ khiến Chromium lưu vào bộ nhớ đệm cây đã tính toán.

Đối với truyền tải DOM JS, chúng tôi yêu cầu tên và vai trò hỗ trợ tiếp cận cho mọi phần tử trong quá trình truyền tải. Vì vậy, nếu chức năng lưu vào bộ nhớ đệm bị tắt, Chromium sẽ tính toán và loại bỏ cây hỗ trợ tiếp cận cho mọi phần tử mà chúng ta truy cập. Mặt khác, đối với các phương pháp dựa trên CDP, cây chỉ bị loại bỏ giữa mỗi lệnh gọi đến CDP, tức là đối với mỗi truy vấn. Các phương pháp này cũng hưởng lợi từ việc bật chức năng lưu vào bộ nhớ đệm, vì cây hỗ trợ tiếp cận sau đó được duy trì qua các lệnh gọi CDP, nhưng mức tăng hiệu suất do đó sẽ tương đối nhỏ hơn.

Mặc dù bạn mong muốn việc bật chức năng lưu vào bộ nhớ đệm ở đây, nhưng việc này cũng khiến bạn tốn thêm bộ nhớ. Đối với các tập lệnh Puppeteer, ví dụ: ghi lại tệp theo dõi, đây có thể là vấn đề. Do đó, chúng tôi quyết định không bật tính năng lưu vào bộ nhớ đệm cây hỗ trợ tiếp cận theo mặc định. Người dùng có thể tự bật tính năng lưu vào bộ nhớ đệm bằng cách bật Miền hỗ trợ tiếp cận cho CDP.

Điểm chuẩn của bộ kiểm thử trong Công cụ cho nhà phát triển

Điểm chuẩn trước đó cho thấy việc triển khai cơ chế truy vấn của chúng tôi tại lớp CDP giúp tăng hiệu suất trong tình huống kiểm thử đơn vị lâm sàng.

Để xem liệu sự khác biệt có đủ rõ ràng để gây chú ý trong tình huống thực tế hơn khi chạy một bộ kiểm thử đầy đủ hay không, chúng tôi đã đã vá bộ kiểm thử toàn diện cho Công cụ cho nhà phát triển để sử dụng nguyên mẫu dựa trên JavaScript và CDP, đồng thời so sánh thời gian chạy. Trong điểm chuẩn này, chúng tôi đã thay đổi tổng cộng 43 bộ chọn từ [aria-label=…] thành trình xử lý truy vấn tuỳ chỉnh aria/… mà sau đó chúng tôi đã triển khai bằng cách sử dụng từng nguyên mẫu.

Một số bộ chọn được sử dụng nhiều lần trong các tập lệnh kiểm thử, do đó, số lần thực thi thực tế của trình xử lý truy vấn aria là 113 lần cho mỗi lần chạy bộ công cụ. Tổng số lựa chọn truy vấn là 2253, do đó, chỉ một phần nhỏ lựa chọn truy vấn được thực hiện thông qua các nguyên mẫu.

Điểm chuẩn: bộ kiểm thử e2e

Như trong hình trên, có sự khác biệt rõ ràng về tổng thời gian chạy. Dữ liệu quá nhiễu để kết luận được bất cứ điều gì cụ thể, nhưng rõ ràng là sự chênh lệch về hiệu suất giữa hai nguyên mẫu cũng xuất hiện trong tình huống này.

Điểm cuối CDP mới

Dựa trên các điểm chuẩn ở trên và vì cách tiếp cận dựa trên cờ khởi chạy nói chung là không mong muốn, chúng tôi quyết định tiếp tục triển khai lệnh CDP mới để truy vấn cây hỗ trợ tiếp cận. Bây giờ, chúng tôi phải tìm hiểu giao diện của điểm cuối mới này.

Đối với trường hợp sử dụng trong Puppeteer, chúng ta cần điểm cuối lấy đối số có tên RemoteObjectIds làm đối số. Để có thể tìm thấy các phần tử DOM tương ứng sau đó, điểm cuối sẽ trả về danh sách đối tượng chứa backendNodeIds cho các phần tử DOM.

Như thấy trong biểu đồ bên dưới, chúng tôi đã thử một vài phương pháp đáp ứng được giao diện này. Qua đó, chúng tôi nhận thấy kích thước của các đối tượng được trả về, tức là liệu chúng ta có trả về các nút hỗ trợ tiếp cận đầy đủ hay chỉ có backendNodeIds không tạo ra sự khác biệt rõ ràng. Mặt khác, chúng tôi nhận thấy rằng việc sử dụng NextInPreOrderIncludingIgnored hiện có là một lựa chọn không hay để triển khai logic truyền tải ở đây, vì làm như vậy đã dẫn đến sự chậm trễ đáng kể.

Điểm chuẩn: So sánh các nguyên mẫu truyền tải AXTree dựa trên CDP

Kết thúc

Hiện tại, khi đã thiết lập điểm cuối CDP, chúng tôi đã triển khai trình xử lý truy vấn ở phía Puppeteer. Trọng tâm của công việc ở đây là cơ cấu lại mã xử lý truy vấn để cho phép các truy vấn phân giải trực tiếp thông qua CDP thay vì truy vấn thông qua JavaScript được đánh giá trong ngữ cảnh trang.

Tiếp theo là gì?

Trình xử lý aria mới đi kèm với Puppeteer v5.4.0 dưới dạng trình xử lý truy vấn tích hợp sẵn. Chúng tôi rất mong được thấy cách người dùng áp dụng công cụ này vào các tập lệnh thử nghiệm. Chúng tôi rất nóng lòng được nghe ý kiến của các bạn về cách chúng tôi có thể làm cho công cụ này trở nên hữu ích hơn nữa!

Tải kênh xem trước xuống

Hãy cân nhắc việc sử dụng Chrome Canary, Nhà phát triển hoặc Beta làm trình duyệt phát triển mặc định của bạn. Các kênh xem trước này cho phép bạn truy cập vào các tính năng mới nhất của Công cụ cho nhà phát triển, kiểm thử các API nền tảng web tiên tiến và tìm ra các vấn đề trên trang web của bạn trước khi người dùng làm điều đó!

Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển

Sử dụng các lựa chọn sau để thảo luận về các tính năng và thay đổi mới trong bài đăng, hoặc bất cứ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.

  • Gửi đề xuất hoặc phản hồi cho chúng tôi qua crbug.com.
  • Báo cáo sự cố trong Công cụ cho nhà phát triển bằng cách sử dụng Tuỳ chọn khác   Thêm   > Trợ giúp > Báo cáo vấn đề về Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
  • Tweet tại @ChromeDevTools.
  • Để lại bình luận về Tính năng mới trong Video trên YouTube của Công cụ cho nhà phát triển hoặc Mẹo video trên YouTube trong Công cụ cho nhà phát triển.