Puppeteer והגישה שלו לבוררים
Puppeteer היא ספריית אוטומציה של דפדפנים ל-Node.js: היא מאפשרת לשלוט בדפדפן באמצעות ממשק API פשוט ומודרני של JavaScript.
המשימה הבולטת ביותר של הדפדפן היא, כמובן, גלישה בדפי אינטרנט. אוטומציה של המשימה הזו היא למעשה אוטומציה של אינטראקציות עם דף האינטרנט.
ב-Puppeteer, כדי לעשות זאת שולחים שאילתה על רכיבי DOM באמצעות סלקטורים שמבוססים על מחרוזות ומבצעים פעולות כמו לחיצה או הקלדה של טקסט על הרכיבים. לדוגמה, סקריפט שפותח את developer.google.com, מוצא את תיבת החיפוש וחיפושים של puppetaria
יכול להיראות כך:
(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');
})();
לכן, האופן שבו הרכיבים מזוהים באמצעות בוחרי שאילתות הוא חלק מרכזי בחוויית השימוש ב-Puppeteer. עד עכשיו, הסלקטורים ב-Puppeteer היו מוגבלים לסלקטורים של CSS ו-XPath. למרות שהם חזקים מאוד מבחינת הביטוי, יכולות להיות להם נקודות חולשה כשמדובר באינטראקציות מתמשכות עם הדפדפן בסקריפטים.
בוררים תחביריים לעומת בוררים סמנטיים
סלקטורים ב-CSS הם בעלי אופי תחבירי. הם קשורים באופן הדוק לפעולות הפנימיות של הייצוג הטקסטואלי של עץ ה-DOM, במובן שהם מפנים למזהים ולשמות של כיתות ב-DOM. לכן, הם מספקים כלי אינטגרלי למפתחי אתרים לשינוי או להוספה של סגנונות לרכיב בדף, אבל בהקשר הזה יש למפתח שליטה מלאה על הדף ועל עץ ה-DOM שלו.
לעומת זאת, סקריפט Puppeteer הוא משקיף חיצוני של דף, ולכן כשמשתמשים בבוררי CSS בהקשר הזה, הם גורמים להנחות מוסתרות לגבי אופן ההטמעה של הדף, ושאין לסקריפט Puppeteer שליטה עליהן.
כתוצאה מכך, סקריפטים כאלה עלולים להיות לא עמידים ונתונים לשינויים בקוד המקור. נניח, לדוגמה, שמישהו משתמש בסקריפטים של Puppeteer לבדיקות אוטומטיות של אפליקציית אינטרנט שמכילה את הצומת <button>Submit</button>
כצאצא השלישי של הרכיב body
. קטע קוד ממקרה בדיקה עשוי להיראות כך:
const button = await page.$('body:nth-child(3)'); // problematic selector
await button.click();
כאן אנחנו משתמשים בבורר 'body:nth-child(3)'
כדי למצוא את לחצן השליחה, אבל הוא קשור באופן הדוק לגרסה הזו של דף האינטרנט. אם רכיב יתווסף בשלב מאוחר יותר מעל הלחצן, הבורר הזה לא יפעל יותר.
זו לא חדשות שצריך לבדוק על כותבים: אנשים שמתבססים על בובות כבר מנסים לבחור סלקטורים שמתאימים לשינויים כאלה. בעזרת Puppetaria, אנחנו נותנים למשתמשים כלי חדש בקווסט הזה.
Puppeteer כולל עכשיו מתבצע טיפול שאילתות חלופי שמבוסס על שליחת שאילתות לעץ הנגישות במקום להסתמך על סלקטורים ב-CSS. לפי פילוסופיה זו, אם רכיב הבטון שרוצים לבחור לא השתנה, גם צומת הנגישות המתאים לא אמור להשתנות.
אנחנו קוראים לסלקטורים כאלה 'ARIA', ואנחנו תומכים בשאילתות לגבי השם והתפקיד הנגישים המחושבים של עץ הנגישות. בהשוואה לסלקטורים ב-CSS, המאפיינים האלה הם סמנטיים במהותם. הם לא קשורים למאפיינים תחביריים של DOM, אלא הם תיאור של האופן שבו הדף נצפה באמצעות טכנולוגיות מסייעות כמו קוראי מסך.
בדוגמה של סקריפט הבדיקה שלמעלה, אפשר להשתמש במקום זאת בבורר aria/Submit[role="button"]
כדי לבחור את הלחצן הרצוי, כאשר Submit
מתייחס לשם הנגיש של הרכיב:
const button = await page.$('aria/Submit[role="button"]');
await button.click();
עכשיו, אם נחליט לשנות את תוכן הטקסט של הלחצן מ-Submit
ל-Done
, הבדיקה תיכשל שוב, אבל במקרה הזה זה רצוי. שינוי השם של הלחצן משנה את תוכן הדף, בניגוד להצגה החזותית שלו או לאופן שבו הוא מובנה ב-DOM. הבדיקות שלנו אמורות להזהיר אותנו על שינויים כאלה כדי לוודא שהם נעשים בכוונה.
חוזרים לדוגמה הרחבה יותר עם סרגל החיפוש, ומשתמשים במטפל החדש aria
ומחליפים את
const search = await page.$('devsite-search > form > div.devsite-search-container');
עם
const search = await page.$('aria/Open search[role="button"]');
כדי למצוא את סרגל החיפוש!
באופן כללי, אנחנו מאמינים שהשימוש בבוררי ARIA כאלה יכול לספק למשתמשים ב-Puppeteer את היתרונות הבאים:
- הסלקטורים בסקריפטים לבדיקה עמידים יותר בפני שינויים בקוד המקור.
- לשפר את הקריאוּת של סקריפטים לבדיקה (שמות נגישים הם תיאורים סמנטיים).
- מעודדים שימוש בשיטות מומלצות להקצאת מאפייני נגישות לרכיבים.
שאר המאמר יתעמק בפרטים לגבי האופן שבו יישמנו את פרויקט Puppeteria.
תהליך העיצוב
רקע
כפי שצוין למעלה, אנחנו רוצים לאפשר לשלוח שאילתות על רכיבים לפי השם והתפקיד שלהם שגלויים לכולם. אלה המאפיינים של עץ הנגישות, שהוא כפול לעץ ה-DOM הרגיל, שמשמש מכשירים כמו קוראי מסך להצגת דפי אינטרנט.
אחרי שבדקנו את המפרט של חישוב השם הנגיש, ברור שחישוב השם של רכיב הוא משימה לא פשוטה, ולכן החלטנו כבר מההתחלה שאנחנו רוצים לעשות שימוש חוזר בתשתית הקיימת של Chromium לצורך זה.
איך הגשנו את ההטמעה
גם אם נצמצם את עצמנו לשימוש בעץ הנגישות של Chromium, יש לא מעט דרכים שבהן אפשר להטמיע שאילתות ARIA ב-Puppeteer. כדי להבין למה, קודם נראה איך Puppeteer שולט בדפדפן.
הדפדפן חושף ממשק לניפוי באגים באמצעות פרוטוקול שנקרא פרוטוקול Chrome DevTools (CDP). כך אפשר לחשוף פונקציונליות כמו 'טעינה מחדש של הדף' או 'הפעלת קטע ה-JavaScript הזה בדף והחזרת התוצאה' באמצעות ממשק שלא תלוי בשפה.
גם הקצה הקדמי של DevTools וגם Puppeteer משתמשים ב-CDP כדי לתקשר עם הדפדפן. כדי ליישם פקודות CDP, קיימת תשתית של כלי פיתוח בתוך כל הרכיבים של Chrome: בדפדפן, בכלי הרינדור וכו'. CDP מטפל בהעברת הפקודות למקום הנכון.
פעולות של Puppeteer, כמו שליחת שאילתות, לחיצה על לחצנים והערכת ביטויים, מבוצעות באמצעות פקודות CDP כמו Runtime.evaluate
שמעריכות JavaScript ישירות בהקשר הדף ומחזירות את התוצאה. פעולות אחרות של Puppeteer, כמו הדמיה של לקות בראיית צבעים, צילום צילומי מסך או תיעוד של נתוני מעקב, משתמשות ב-CDP כדי לתקשר ישירות עם תהליך העיבוד של Blink.
כך יש לנו שתי נתיבים להטמעה של פונקציונליות השאילתות:
- לכתוב את לוגיקת השאילתות ב-JavaScript ולהחדיר אותה לדף באמצעות
Runtime.evaluate
, או - להשתמש בנקודת קצה CDP שיכולה לגשת לעץ הנגישות ולשלוח שאילתות לגביו ישירות בתהליך Blink.
הטמענו 3 אבות טיפוס:
- מעבר JS DOM – מבוסס על החדרת JavaScript לדף
- Puppeteer AXTree traversal – על סמך השימוש בגישה הקיימת של CDP לעץ הנגישות
- מעבר DOM של CDP – שימוש בנקודת קצה (endpoint) חדשה של CDP שמיועד לשליחת שאילתות בעץ הנגישות
סריקה של JS DOM
האב טיפוס הזה מבצע סריקה מלאה של ה-DOM ומשתמש ב-element.computedName
וב-element.computedRole
, שמופעלים על ידי דגל ההשקה ComputedAccessibilityInfo
, כדי לאחזר את השם והתפקיד של כל רכיב במהלך הסריקה.
מעבר Puppeteer AXTree
כאן אנחנו מאחזרים את עץ הנגישות המלא דרך CDP וחוצים אותו ב-Puppeteer. לאחר מכן, צומתי הנגישות שמתקבלים ממופה לצומתי DOM.
סריקה של DOM ב-CDP
באב טיפוס הזה הטמענו נקודת קצה חדשה ב-CDP, שמיועדת במיוחד לשליחת שאילתות לעץ הנגישות. כך, השאילתה יכולה להתבצע בקצה העורפי באמצעות הטמעה של C++, במקום בהקשר הדף באמצעות JavaScript.
נקודת השוואה לבדיקת יחידה
בתרשים הבא מוצגת השוואה של זמן הריצה הכולל של שליחת שאילתות לגבי ארבעה רכיבים 1,000 פעמים ב-3 האב טיפוס. בדיקת הביצועים בוצעה ב-3 הגדרות שונות, עם שינויים בגודל הדף ובאפשרות לשמירת רכיבי נגישות במטמון.
ברור שיש פער משמעותי בביצועים בין מנגנון השאילתות המבוסס על CDP לשני המנגנונים האחרים שהוגדרו רק ב-Puppeteer, ונראה שההפרש היחסי גדל באופן משמעותי ככל שגודל הדף גדול יותר. מעניין לראות שהאב טיפוס של JS DOM traversal מגיב בצורה כל כך טובה להפעלת מטמון הנגישות. כשהאחסון במטמון מושבת, עץ הנגישות מחושב על פי דרישה, והוא נמחק אחרי כל אינטראקציה אם הדומיין מושבת. הפעלת הדומיין גורמת ל-Chromium לשמור את העץ המחושב במטמון במקום זאת.
במהלך הסריקה של DOM ב-JS, אנחנו מבקשים את השם והתפקיד הנגישים של כל רכיב במהלך הסריקה. לכן, אם האחסון במטמון מושבת, Chromium מחשב את עץ הנגישות של כל רכיב שאנחנו מבקרים בו ומחק אותו. לעומת זאת, בגישות המבוססות על CDP, העץ נמחק רק בין כל קריאה ל-CDP, כלומר עבור כל שאילתה. הגישה הזו מאפשרת גם להפעיל אחסון במטמון, כי עץ הנגישות נשמר בכל הקריאות ל-CDP, אבל לכן השיפור בביצועים קטן יותר יחסית.
למרות שמומלץ להפעיל שמירה במטמון, זה כרוך בעלות נוספת של שימוש בזיכרון. למשל, יכול להיות שזו תהיה בעיה בסקריפטים של Puppeteer שמתעדים קובצי מעקב. לכן, החלטנו לא להפעיל את האחסון במטמון של עץ הנגישות כברירת מחדל. משתמשים יכולים להפעיל את האחסון במטמון בעצמם על ידי הפעלת הדומיין של נגישות ב-CDP.
בנצ'מרק של חבילה לבדיקות של DevTools
נקודת ההשוואה הקודמת הראו שהטמעה של מנגנון השאילתות בשכבת ה-CDP משפרת את הביצועים בתרחיש של בדיקת יחידה קלינית.
כדי לבדוק אם ההבדל בולט מספיק כדי שיהיה מורגש בתרחיש ריאליסטי יותר של הפעלת חבילת בדיקות מלאה, תיקנו את חבילת הבדיקות מקצה לקצה של DevTools כדי להשתמש באב טיפוס מבוסס JavaScript ובאב טיפוס מבוסס CDP, והשווינו בין זמני הריצה. במבחן השוואה הזה, שינינו סה"כ 43 בוחרים מ-[aria-label=…]
למטפל שאילתות מותאם אישית aria/…
, ולאחר מכן הטמענו אותו באמצעות כל אחד מהאב טיפוס.
חלק מהבוררים משמשים כמה פעמים בסקריפטים לבדיקה, כך שמספר ההפעלות בפועל של aria
query handler היה 113 לכל הפעלה של הסוויטה. המספר הכולל של בחירת השאילתות היה 2,253, כך שרק חלק מבחירת השאילתות התרחשה דרך האב טיפוס.
כפי שרואים בתרשים שלמעלה, יש הבדל ניכר בזמן הריצה הכולל. הנתונים רועשים מדי מכדי להסיק משהו ספציפי, אבל ברור שהפער בביצועים בין שני אבות הטיפוס מופיע גם בתרחיש הזה.
נקודת קצה חדשה של CDP
לאור נקודות ההשוואה שצוינו למעלה, וכיוון שהגישה שמבוססת על דגל ההשקה באופן כללי לא הייתה רצויה, החלטנו להמשיך בתהליך ההטמעה של פקודת CDP חדשה לשליחת שאילתות לגבי עץ הנגישות. עכשיו היינו צריכים להבין את הממשק של נקודת הקצה החדשה.
בתרחיש לדוגמה שלנו ב-Puppeteer, נדרשת נקודת קצה שתקבל את מה שנקרא RemoteObjectIds
כארגומנטים, וכדי שנוכל למצוא את רכיבי ה-DOM התואמים לאחר מכן, היא צריכה להחזיר רשימה של אובייקטים שמכילה את backendNodeIds
של רכיבי ה-DOM.
כפי שמוצג בתרשים שבהמשך, ניסינו כמה גישות שיעמדו בדרישות של הממשק הזה. על סמך התוצאות האלה, גילינו שלא היה הבדל משמעותי בין הגודל של האובייקטים שהוחזרו, כלומר בין החזרת צמתים מלאים של נגישות לבין החזרת backendNodeIds
בלבד. מצד שני, גילינו שהשימוש ב-NextInPreOrderIncludingIgnored
הקיים היה בחירה לא טובה להטמעת הלוגיקה של הניווט כאן, כי היא גרמה להאטה ניכרת.
סיכום
עכשיו, אחרי שהטמענו את נקודת הקצה של CDP, הטמענו את הטיפול בשאילתות בצד Puppeteer. העבודה העיקרית כאן הייתה לשנות את המבנה של הקוד לטיפול בשאילתות כדי לאפשר לשאילתות להתקבל ישירות דרך CDP במקום לשלוח שאילתות דרך JavaScript שמוערך בהקשר הדף.
מה השלב הבא?
הטיפולר החדש של aria
מגיע עם Puppeteer v5.4.0 כטיפולר שאילתות מובנה. אנחנו מחכים לראות איך המשתמשים ישלבו את התכונה הזו בסקריפטים של הבדיקות שלהם, ונשמח לשמוע את הרעיונות שלכם לשיפור התכונה.
הורדת הערוצים לתצוגה מקדימה
מומלץ להשתמש ב-Chrome Canary, ב-Dev או ב-Beta כדפדפן הפיתוח שמוגדר כברירת מחדל. ערוצי התצוגה המקדימה האלה מעניקים לכם גישה לתכונות העדכניות ביותר של DevTools, מאפשרים לכם לבדוק ממשקי API מתקדמים לפלטפורמות אינטרנט ולמצוא בעיות באתר לפני שהמשתמשים שלכם יעשו זאת.
יצירת קשר עם צוות כלי הפיתוח ל-Chrome
אפשר לבחור מבין האפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל נושא אחר שקשור לכלי פיתוח.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- נשמח לשמוע מה חדש: מה חדש בסרטונים של כלי הפיתוח ב-YouTube או טיפים בנושא כלי פיתוח ב-YouTube.