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

Vịnh Johan
Vịnh Johan

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 dành cho Nút: cho phép bạn điều khiển trình duyệt bằng một API JavaScript đơn giản và hiện đại.

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

Trong Puppeteer, bạn có thể thực hiện việc này 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 thao tác 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 và tìm thấy hộp tìm kiếm cho puppetaria có thể có dạ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 của trải nghiệm Puppeteer. Cho đến nay, các bộ chọn trong Puppeteer chỉ được giới hạn ở các bộ chọn CSS và XPath, mặc dù về mặt biểu thức, các bộ chọn này có thể có những hạn chế khi duy trì tương tác trình duyệt trong tập lệnh.

Bộ chọn ngữ nghĩa và ngữ nghĩa

Bộ chọn CSS về bản chất có cú pháp; chúng liên kết chặt chẽ với hoạt động bên trong của cách trình bày bằng văn bản trong cây DOM, theo nghĩa là chúng tham chiếu mã nhận dạng và tên lớp từ DOM. Theo đó, chúng cung cấp một công cụ không thể thiếu cho nhà phát triển web để sửa đổi hoặc thêm kiểu vào một phần tử trong trang, nhưng trong bối 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 một trang, vì vậy, 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.

Ảnh hưởng của việc này là các tập lệnh đó có thể dễ hỏng và dễ bị thay đổi mã nguồn. Ví dụ: giả sử bạn sử dụng tập lệnh Puppeteer để kiểm thử tự động cho một ứng dụng web có 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ừ một trường hợp kiểm thử có thể có dạng như sau:

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

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

Đây không phải là tin tức dành cho người viết kiểm thử: Người dùng Puppeteer đã cố gắng chọn các bộ chọn hiệu quả cho những thay đổi như vậy. Với Puppetaria, chúng tôi cung cấp cho người dùng một công cụ mới để thực hiện nhiệm vụ này.

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

Chúng tôi đặt tên cho các bộ chọn như vậy là "ARIA" và hỗ trợ truy vấn tên và vai trò của cây hỗ trợ tiếp cận đã tính toán được. So với bộ chọn CSS, các thuộc tính này có bản chất ngữ nghĩa. Các chỉ số này không gắn liền với thuộc tính cú pháp của DOM mà thay vào đó mô tả cách quan sát trang thông qua công nghệ hỗ trợ, chẳng hạn 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 đề cập đến 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ì bài kiểm thử sẽ lại không thành công. Nhưng trong trường hợp này là mong muốn; bằng cách thay đổi tên của nút, chúng ta sẽ thay đổi nội dung của trang thay vì cách trình bày trực quan hoặc cách sắp xếp nội dung trên trang trong DOM. Các lần kiểm thử của chúng tôi phải cảnh báo về những thay đổi như vậy nhằm đảm bảo rằng những thay đổi đó là có chủ đí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');

cùng với

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

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

Nhìn 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 các lợi ích sau cho người dùng Puppeteer:

  • Giúp các bộ chọn trong tập lệnh kiểm thử thích ứng hơn với các 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à phần mô tả ngữ nghĩa).
  • Khuyến khích các phương pháp hay để 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ư đã thúc đẩy ở trên, chúng tôi muốn bật các phần tử truy vấn theo tên và vai trò có thể truy cập 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 kép với cây DOM 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 hỗ trợ tiếp cận, rõ ràng việc tính toán tên cho một phần tử là một nhiệm vụ không hề đơn giản, vì vậy ngay từ đầu chúng tôi đã quyết định 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

Kể cả khi hạn chế việc 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 điều khiển 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 JavaScript này trên trang và gửi trả kết quả" 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à công cụ Puppeteer đề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 sẽ đảm nhận việc định tuyến các lệnh đến đúng vị trí.

Các thao tác kéo 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 (chẳng hạn như Runtime.evaluate) đánh giá JavaScript trực tiếp trong ngữ cảnh trang và trả về kết quả. Các hành động 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ử dụng CDP để giao tiếp trực tiếp với quá trình hiển thị Blink.

CDP

Việc này khiến chúng ta có 2 đường dẫn để triển khai chức năng truy vấn; chúng ta có thể:

  • Viết logic truy vấn bằng JavaScript và đưa 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 ngay trong quá trình Blink.

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

  • Truyền tải DOM JS – dựa trên việc chèn JavaScript vào trang
  • Puppeteer AXTree traversal – 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 điểm cuối CDP mới được thiết kế để truy vấn cây hỗ trợ tiếp cận

Truyền tải DOM JS

Nguyên mẫu này sẽ truyền tải toàn bộ DOM và 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 Puppeteer AXTree

Thay vào đó, 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 này bằng 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 một đ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. Theo cách này, việc truy vấn có thể xảy ra trên máy chủ 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 của việc truy vấn 4 phần tử 1000 lần cho 3 nguyên mẫu. Điểm chuẩn được thực thi ở 3 cấu hình khác nhau, khác nhau về 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ử 1000 lần

Rõ ràng là có sự chênh lệch đáng kể về hiệu suất giữa cơ chế truy vấn được CDP hỗ trợ 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 phản hồi rất tốt việc bật chức năng lưu khả năng hỗ trợ tiếp cận vào bộ nhớ đệm. Khi tắt tính năng lưu vào bộ nhớ đệm, cây hỗ trợ tiếp cận sẽ được tính toán theo yêu cầu và sẽ 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 kích hoạt miền sẽ khiến Chromium lưu cây tính toán vào bộ nhớ đệm.

Đối với truyền tải DOM JS, chúng tôi yêu cầu tên và vai trò có thể truy cập được cho mọi phần tử trong quá trình truyền tải, vì vậy nếu tính 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 tôi 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 được 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 đó vẫn được duy trì qua các lệnh gọi CDP, nhưng mức tăng hiệu suất do đó tương đối nhỏ hơn.

Mặc dù việc bật tính năng lưu vào bộ nhớ đệm là điều mong muốn tại đây, nhưng việc này sẽ tốn thêm chi phí sử dụng bộ nhớ. Đối với các tập lệnh Puppeteer, ví dụ như ghi lại các tệp theo dõi, điều này có thể gây ra sự cố. Do đó, chúng tôi quyết định không bật tính năng lưu cây hỗ trợ tiếp cận vào bộ nhớ đệm 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 của CDP.

Điểm chuẩn của bộ thử nghiệm 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 để đáng chú ý trong tình huống thực tế hơn về việc chạy một bộ thử nghiệm đầy đủ hay không, chúng tôi đã bản vá bộ thử nghiệm toàn diện cho Công cụ cho nhà phát triển để sử dụng các nguyên mẫu dựa trên JavaScript và CDP và so sánh các thời gian chạy. Trong điểm chuẩn này, chúng ta đã thay đổi tổng cộng 43 bộ chọn từ [aria-label=…] thành một trình xử lý truy vấn tuỳ chỉnh aria/…, 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ử, vì vậy, số lần thực thi thực tế của trình xử lý truy vấn aria là 113 lần trong mỗi lần chạy bộ. Tổng số lượt chọn cụm từ tìm kiếm là 2253, nên chỉ một phần nhỏ trong số cụm từ tìm kiếm được chọn xảy ra thông qua các nguyên mẫu.

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

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

Điểm cuối CDP mới

Dựa trên các điểm chuẩn ở trên và vì nhìn chung, phương pháp dựa trên cờ khởi chạy 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 ta đã phải xác định 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 là RemoteObjectIds. Để sau đó, chúng ta có thể tìm thấy các phần tử DOM tương ứng. Điểm cuối này sẽ trả về danh sách các đối tượng chứa backendNodeIds cho các phần tử DOM.

Như được thấy trong biểu đồ bên dưới, chúng tôi đã thử khá nhiều phương pháp đáp ứng giao diện này. Từ đó, chúng tôi nhận thấy rằng kích thước của các đối tượng được trả về, tức là chúng tôi có trả về các nút hỗ trợ tiếp cận đầy đủ hay không 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 việc sử dụng NextInPreOrderIncludingIgnored hiện có là một lựa chọn không phù hợp để triển khai logic truyền tải tại đây, vì điều đó làm chậm đá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

Tổng kết

Bây giờ, với điểm cuối CDP đã sẵn sàng, chúng tôi đã triển khai trình xử lý truy vấn ở phía Puppeteer. Mục tiêu cuối cùng ở đây là cơ cấu lại mã xử lý truy vấn để cho phép các truy vấn giải quyết 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 được vận chuyển cùng Puppeteer v5.4.0 dưới dạng trình xử lý truy vấn tích hợp. Chúng tôi rất mong được thấy người dùng áp dụng công cụ này vào tập lệnh kiểm thử như thế nào và rất nóng lòng được nghe ý kiến của bạn về cách có thể giúp chương trình này hữu ích hơn nữa!

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

Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Những kênh xem trước này cung cấp cho bạn quyề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ử API nền tảng web tiên tiến và tìm ra các sự cố trên trang web của bạn trước khi người dùng làm việc đó!

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

Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng mới và thay đổi trong bài đăng hoặc bất kỳ vấn đề 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ố Công cụ cho nhà phát triển bằng cách sử dụng mục Tuỳ chọn khác   Thêm   > Trợ giúp > Báo cáo sự cố 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 nhận xét về Video trên YouTube của chúng tôi 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 Công cụ cho nhà phát triển.