Puppetaria: סקריפטים של Puppeteer שמתמקדים בנגישות

Johan Bay
Johan Bay

הבובה על בסיס פטירה והגישה שלו לסלקטורים

Puppeteer היא ספרייה לאוטומציה של הדפדפן של Node: היא מאפשרת לשלוט בדפדפן באמצעות ממשק JavaScript API פשוט ומודרני.

המשימה הבולטת ביותר בדפדפן היא, כמובן, גלישה בדפי אינטרנט. אוטומציה של המשימה הזו היא למעשה אוטומציה של אינטראקציות עם דף האינטרנט.

ב-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 היו מוגבלים לסלקטורים ב-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, אנחנו נותנים למשתמשים כלי חדש בקווסט הזה.

בשלב הזה, המפעילים של הספק כוללים handler חלופי לשאילתות על סמך שאילתות על עץ הנגישות, במקום להשתמש בסלקטורים ב-CSS. לפי פילוסופיה זו, אם רכיב הבטון שרוצים לבחור לא השתנה, גם צומת הנגישות המתאים לא אמור להשתנות.

אנחנו קוראים לסלקטורים האלה "סלקטורים של ARIA" ותומכים בשליחת שאילתות לגבי השם הנגיש והתפקיד המחושבים של עץ הנגישות. בהשוואה לסלקטורים ב-CSS, המאפיינים האלה הם סמנטיים מטבעם. הם לא קשורים למאפיינים התחביריים של ה-DOM, אלא מתארים את האופן שבו הדף ניתן לצפייה באמצעות טכנולוגיות מסייעות כמו קוראי מסך.

בסקריפט הבדיקה שלמעלה, אפשר במקום זאת להשתמש בבורר aria/Submit[role="button"] כדי לבחור את הלחצן הרצוי, כאשר Submit מתייחס לשם הנגיש של הרכיב:

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

עכשיו, אם נחליט לשנות את תוכן הטקסט בלחצן שלנו מ-Submit ל-Done, הבדיקה תיכשל שוב אבל במקרה הזה רצוי. על ידי שינוי שם הלחצן אנחנו משנים את תוכן הדף, בניגוד להצגה החזותית שלו או האופן שבו הוא מובנה ב-DOM. הבדיקות שלנו צריכות להזהיר אותנו מפני שינויים כאלה, כדי להבטיח שהשינויים האלה נעשים באופן מכוון.

אם נחזור לדוגמה הגדולה יותר עם סרגל החיפוש, נוכל להשתמש ב-handler החדש aria ולהחליף

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

עם

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

כדי למצוא את סרגל החיפוש!

באופן כללי, אנחנו מאמינים שהשימוש בסלקטורים מסוג ARIA יכולים לספק את היתרונות הבאים למשתמשי 'בובות':

  • הסלקטורים בסקריפטים לבדיקה עמידים יותר בפני שינויים בקוד המקור.
  • מומלץ להפוך את הסקריפטים של הבדיקה לקריאים יותר (שמות נגישים הם תיאורים סמנטיים).
  • לעודד שיטות מומלצות להקצאת מאפייני נגישות לאלמנטים.

שאר המאמר יתעמק בפרטים לגבי האופן שבו יישמנו את פרויקט Puppeteria.

תהליך העיצוב

רקע

כפי שמפורט למעלה, אנחנו רוצים להפעיל את רכיבי השאילתות לפי השם והתפקיד הנגישים שלהם. אלה המאפיינים של עץ הנגישות, שהוא כפול לעץ ה-DOM הרגיל, שמשמש מכשירים כמו קוראי מסך להצגת דפי אינטרנט.

מעיון במפרט של חישוב השם הנגיש, ברור שחישוב השם של רכיב הוא משימה לא פשוטה, ולכן מתחילת התהליך החלטנו לעשות שימוש חוזר בתשתית הקיימת של Chromium.

איך ניגשנו ליישום שלה

ואפילו הגבילו את עצמנו לשימוש בעץ הנגישות של Chromium, יש לא מעט דרכים שבהן אנחנו יכולים להטמיע שאילתות ARIA ב-Puppeteer. כדי להבין למה, קודם נראה איך Puppeteer שולט בדפדפן.

הדפדפן חושף ממשק לניפוי באגים באמצעות פרוטוקול שנקרא פרוטוקול Chrome DevTools (CDP). הפעולה הזו חושפת פונקציונליות כמו 'טעינה מחדש של הדף' או 'הפעלת קטע ה-JavaScript הזה בדף והחזרת התוצאה' באמצעות ממשק שלא תלוי בשפה.

גם הקצה הקדמי של כלי הפיתוח וגם ה-Puppeteer משתמשים ב-CDP כדי לדבר עם הדפדפן. כדי ליישם פקודות CDP, קיימת תשתית של כלי פיתוח בתוך כל הרכיבים של Chrome: בדפדפן, בכלי הרינדור וכו'. CDP מטפל בהעברת הפקודות למקום הנכון.

פעולות של כותבי שאילתות כמו שליחת שאילתות, לחיצה והערכת ביטויים מבוצעות על ידי שימוש בפקודות CDP כמו Runtime.evaluate, שבודק את JavaScript ישירות בהקשר של הדף ומחזיר את התוצאה. פעולות אחרות של חובבי התחום, כמו אמולציה של ליקוי בראיית צבעים, צילום מסך או תיעוד עקבות, משתמשות ב-CDP כדי לתקשר ישירות עם תהליך העיבוד של Blink.

CDP (CDP)

כך יש לנו שתי נתיבים להטמעה של פונקציונליות השאילתות:

  • לכתוב את לוגיקת השאילתות ב-JavaScript ולהחדיר אותה לדף באמצעות Runtime.evaluate, או
  • שימוש בנקודת קצה CDP שיכולה לגשת לעץ הנגישות ולשלוח שאילתות לגביו ישירות בתהליך Blink.

הטמענו 3 אבות טיפוס:

  • מעבר JS DOM – מבוסס על החדרת JavaScript לדף
  • מעבר של Puppeteer AXTree – על סמך גישת ה-CDP הקיימת לעץ הנגישות
  • מעבר DOM של CDP – שימוש בנקודת קצה (endpoint) חדשה של CDP שמיועד לשליחת שאילתות בעץ הנגישות

מעבר DOM של JS

אב הטיפוס הזה מבצע מעבר מלא של ה-DOM ומשתמש ב-element.computedName וב-element.computedRole, שמוגבל בדגל ההפעלה ComputedAccessibilityInfo, כדי לאחזר את השם והתפקיד של כל רכיב במהלך המעבר.

מעבר Puppeteer AXTree

כאן אנחנו מאחזרים את עץ הנגישות המלא דרך CDP וחוצים אותו ב-Puppeteer. צמתי הנגישות שמתקבלים ממופים לאחר מכן לצומתי DOM.

מעבר DOM של CDP

עבור אב הטיפוס הזה, הטמענו נקודת קצה (endpoint) חדשה של CDP כדי לשלוח שאילתות לגבי עץ הנגישות. כך, שליחת השאילתות יכולה להתבצע בקצה העורפי באמצעות הטמעת C++ במקום בהקשר הדף באמצעות JavaScript.

נקודת השוואה לבדיקת יחידה

האיור הבא משווה את זמן הריצה הכולל של שליחת שאילתות על ארבעה אלמנטים 1,000 פעמים עבור שלושת האב טיפוסים. נקודת ההשוואה בוצעה ב-3 תצורות שונות, שתלויות בגודל הדף וב הפעלה של שמירה במטמון של רכיבי נגישות.

נקודת השוואה: זמן ריצה כולל של שליחת שאילתות על ארבעה רכיבים 1,000 פעמים

ברור למד שיש פער משמעותי בביצועים בין מנגנון השאילתות בגיבוי CDP לבין מנגנון השאילתות של שני הסוגים האחרים שהוטמע רק ב-Puppeteer, ונראה שההבדל היחסי גדל באופן משמעותי בגודל הדף. קצת מעניין לראות שאבי הטיפוס של מעבר JS DOM מגיב כל כך טוב להפעלת שמירת נגישות במטמון. כשהשמירה במטמון מושבתת, עץ הנגישות מחושב על פי דרישה ומוחק את העץ אחרי כל אינטראקציה אם הדומיין מושבת. כשמפעילים את הדומיין, Chromium שומר במקום זאת את העץ המחושב.

עבור המעבר של JS DOM אנחנו מבקשים את השם והתפקיד הנגישים של כל רכיב במהלך המעבר, כך שאם השמירה במטמון מושבתת, Chromium מחשב את עץ הנגישות ומוחק אותו לכל רכיב שבו אנחנו מבקרים. לעומת זאת, בגישות המבוססות על CDP, העץ נמחק רק בין כל קריאה ל-CDP, כלומר עבור כל שאילתה. גם הגישות האלה מפיקות תועלת מהפעלת שמירה במטמון, כי עץ הנגישות נשמר לאחר מכן בקריאות CDP, אבל כתוצאה מכך השיפור בביצועים קטן יחסית.

למרות שמומלץ להפעיל שמירה במטמון, זה כרוך בעלות נוספת של שימוש בזיכרון. בסקריפטים של Puppeteer, למשל מתעדים קובצי מעקב, זה עלול להיות בעייתי. לכן החלטנו לא להפעיל שמירה במטמון של עץ נגישות כברירת מחדל. המשתמשים יכולים להפעיל בעצמם שמירה במטמון על ידי הפעלת דומיין הנגישות של CDP.

נקודת השוואה לחבילת הבדיקה של כלי פיתוח

נקודת ההשוואה הקודמת הראו שהטמעה של מנגנון השאילתות בשכבת ה-CDP משפרת את הביצועים בתרחיש של בדיקת יחידה קלינית.

כדי לראות אם ההבדל מודגש מספיק בתרחיש מציאותי של הפעלת חבילת בדיקות מלאה, תיקנו את חבילת הבדיקות מקצה לקצה של כלי הפיתוח כך שנעשה בה שימוש באבי הטיפוס המבוססים על JavaScript ו-CDP והשווינו בין זמני הריצה. ביחס למתחרים, שינינו מספר כולל של 43 סלקטורים מ-[aria-label=…] ל-handler מותאם אישית aria/…, שאותו הטמענו לאחר מכן באמצעות כל אחד מבני הטיפוס.

חלק מהסלקטורים נמצאים בשימוש מספר פעמים בסקריפטים של בדיקה, כך שמספר ההפעלות בפועל של handler השאילתה aria היה 113 לכל הרצה של החבילה. המספר הכולל של בחירות שאילתות היה 2,253, כך שרק חלק מבחירות השאילתות התבצעו דרך אבות הטיפוס.

נקודת השוואה: חבילת בדיקות e2e

כפי שאפשר לראות בתרשים שלמעלה, יש הבדל ברור בזמן הריצה הכולל. הנתונים רועשים מדי מכדי להסיק משהו ספציפי, אבל ברור שהפער בביצועים בין שני אבות הטיפוס מופיע גם בתרחיש הזה.

נקודת קצה חדשה של CDP

לאור נקודות ההשוואה שצוינו למעלה, וכיוון שהגישה שמבוססת על דגל ההשקה באופן כללי לא הייתה רצויה, החלטנו להמשיך בהטמעה של פקודת CDP חדשה לשליחת שאילתות לגבי עץ הנגישות. עכשיו היינו צריכים להבין את הממשק של נקודת הקצה החדשה.

בתרחיש לדוגמה שלנו ב-Puppeteer, אנחנו צריכים שנקודת הקצה תיקח מה שנקרא RemoteObjectIds כארגומנט, וכדי לאפשר לנו למצוא את רכיבי ה-DOM התואמים לאחר מכן, היא צריכה להחזיר רשימה של אובייקטים שמכילה את ה-backendNodeIds בשביל רכיבי ה-DOM.

כפי שאפשר לראות בתרשים הבא, ניסינו כמה גישות שמתאימות לממשק הזה. מכאן גילינו שגודל האובייקטים שהוחזרו, כלומר אם החזרנו צומתי נגישות מלאים או רק את backendNodeIds, ללא הבדל. לעומת זאת, גילינו שהשימוש ב-NextInPreOrderIncludingIgnored הקיים היה בחירה לא טובה להטמעת לוגיקת החצייה כאן, כי היא גרמה להאטה משמעותית.

נקודת השוואה: השוואה בין אבות טיפוס של AXTree מסוג AXTree שמבוססים על CDP

סיכום של כל המידע

עכשיו, אחרי שקבענו את נקודת הקצה (endpoint) של CDP, הטמענו את ה-handler של השאילתה בצד Puppeteer. העבודה הקשה כאן הייתה לארגן מחדש את קוד הטיפול בשאילתות כדי לאפשר עיבוד של שאילתות ישירות דרך CDP במקום לשלוח שאילתות באמצעות JavaScript שנבדק בהקשר של הדף.

מה השלב הבא?

ה-handler החדש של aria נשלח עם Puppeteer גרסה 5.4.0 כ-handler מובנה של שאילתות. אנחנו מצפים לראות איך המשתמשים יאמצו את השימוש בסקריפטים של הבדיקה שלנו. נשמח לשמוע את הרעיונות שלכם לגבי הדרך שבה נוכל להפוך את זה לשימושי עוד יותר.

הורדת הערוצים של התצוגה המקדימה

כדאי להשתמש ב-Chrome Canary, Dev או בטא כדפדפן הפיתוח שמוגדר כברירת מחדל. הערוצים לתצוגה מקדימה אלה מעניקים לך גישה לתכונות החדשות של כלי הפיתוח, בודקים ממשקי API מתקדמים של פלטפורמות אינטרנט ומוצאים בעיות באתר שלך לפני שהמשתמשים עושים זאת.

יצירת קשר עם הצוות של כלי הפיתוח ל-Chrome

אפשר להשתמש באפשרויות הבאות כדי לדון בתכונות ובשינויים החדשים בפוסט, או בכל נושא אחר שקשור לכלי פיתוח.

  • אפשר לשלוח לנו הצעה או משוב דרך crbug.com.
  • כדי לדווח על בעיה בכלי הפיתוח, לוחצים על אפשרויות נוספות   עוד   > עזרה > דיווח על בעיות בכלי הפיתוח בכלי הפיתוח.
  • שליחת ציוץ אל @ChromeDevTools.
  • נשמח לשמוע מה חדש בסרטונים ב-YouTube של כלי הפיתוח או בסרטונים ב-YouTube שקשורים לכלי פיתוח.