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)'
כדי למצוא את לחצן השליחה, אבל הוא קשור באופן הדוק לגרסה הזו של דף האינטרנט. אם תוסיפו רכיב מעל הלחצן בשלב מאוחר יותר, הבורר הזה לא יפעל יותר.
זה לא חדש לכותבי בדיקות: משתמשי Puppeteer כבר מנסים לבחור סלקטורים עמידים לשינויים כאלה. 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 את היתרונות הבאים:
- איך לשפר את עמידות הבוררים בסקריפטים לבדיקה מפני שינויים בקוד המקור.
- לשפר את הקריאוּת של סקריפטים לבדיקה (שמות נגישים הם תיאורים סמנטיים).
- מעודדים שימוש בשיטות מומלצות להקצאת מאפייני נגישות לרכיבים.
בהמשך המאמר נסביר בפירוט איך הטמענו את הפרויקט Puppetaria.
תהליך העיצוב
רקע
כפי שצוין למעלה, אנחנו רוצים לאפשר לשלוח שאילתות על רכיבים לפי השם והתפקיד שלהם שגלויים לכולם. אלה מאפיינים של עץ הנגישות, עץ מקביל לעץ ה-DOM הרגיל, שמכשירים כמו קוראי מסך משתמשים בו כדי להציג דפי אינטרנט.
אחרי שבדקנו את המפרט של חישוב השם הנגיש, ברור שחישוב השם של רכיב הוא משימה לא פשוטה, ולכן החלטנו כבר מההתחלה שאנחנו רוצים לעשות שימוש חוזר בתשתית הקיימת של Chromium לצורך זה.
איך הגשנו את ההטמעה
גם אם נצמצם את השימוש שלנו לעץ הנגישות של Chromium, יש לא מעט דרכים שבהן אפשר להטמיע שאילתות ARIA ב-Puppeteer. כדי להבין למה, קודם נראה איך Puppeteer שולט בדפדפן.
הדפדפן חושף ממשק לניפוי באגים באמצעות פרוטוקול שנקרא פרוטוקול כלי הפיתוח של Chrome (CDP). כך אפשר לחשוף פונקציונליות כמו 'טעינה מחדש של הדף' או 'הפעלת קטע ה-JavaScript הזה בדף והחזרת התוצאה' באמצעות ממשק שלא תלוי בשפה.
גם ממשק הקצה של DevTools וגם Puppeteer משתמשים ב-CDP כדי לתקשר עם הדפדפן. כדי להטמיע פקודות CDP, יש תשתית של DevTools בכל הרכיבים של Chrome: בדפדפן, במעבד הגרפיקה וכו'. מערכת ה-CDP מטפלת בהפניית הפקודות למקום הנכון.
פעולות של Puppeteer, כמו שליחת שאילתות, לחיצה על לחצנים והערכת ביטויים, מבוצעות באמצעות פקודות CDP כמו Runtime.evaluate
שמעריכות JavaScript ישירות בהקשר הדף ומחזירות את התוצאה. פעולות אחרות של Puppeteer, כמו הדמיה של לקות בראיית צבעים, צילום צילומי מסך או תיעוד של נתוני מעקב, משתמשות ב-CDP כדי לתקשר ישירות עם תהליך העיבוד של Blink.
עכשיו יש לנו שתי דרכים להטמיע את הפונקציונליות של השאילתות:
- לכתוב את הלוגיקה של השאילתות שלנו ב-JavaScript ולהחדיר אותה לדף באמצעות
Runtime.evaluate
, או - שימוש בנקודת קצה של CDP שיכולה לגשת לעץ הנגישות ולבצע שאילתות עליו ישירות בתהליך Blink.
הטמענו 3 אב טיפוס:
- JS DOM traversal – מבוסס על הזרקת JavaScript לדף
- Puppeteer AXTree traversal – על סמך השימוש בגישה הקיימת של CDP לעץ הנגישות
- סריקה של DOM ב-CDP – שימוש בנקודת קצה חדשה ב-CDP שנועדה במיוחד לשליחת שאילתות לעץ הנגישות
סריקה של JS DOM
האב טיפוס הזה מבצע סריקה מלאה של ה-DOM ומשתמש ב-element.computedName
וב-element.computedRole
, שמופעלים על ידי דגל ההשקה ComputedAccessibilityInfo
, כדי לאחזר את השם והתפקיד של כל רכיב במהלך הסריקה.
סריקה של AXTree ב-Puppeteer
במקום זאת, אנחנו מאחזרים את עץ הנגישות המלא דרך 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
אתם יכולים להשתמש באפשרויות הבאות כדי לדון בתכונות החדשות, בעדכונים או בכל דבר אחר שקשור ל-DevTools.
- אתם יכולים לשלוח לנו משוב ובקשות להוספת תכונות בכתובת crbug.com.
- מדווחים על בעיה בכלי הפיתוח באמצעות הסמל אפשרויות נוספות > עזרה > דיווח על בעיה בכלי הפיתוח ב-DevTools.
- שולחים ציוץ אל @ChromeDevTools.
- אפשר להשאיר תגובות בסרטונים של מה חדש בכלי הפיתוח ב-YouTube או בסרטונים של טיפים לכלי הפיתוח ב-YouTube.