Puppetaria: สคริปต์ Puppeteer ที่เน้นการเข้าถึงเป็นหลัก

โจฮันเบย์
โจฮันเบย์

นักเชิดหุ่นกระบอกและแนวทางการเลือกผู้ตรวจ

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 จนถึงขณะนี้ ตัวเลือกใน Puppeteer ยังจำกัดไว้เฉพาะตัวเลือก CSS และ XPath ซึ่งแม้ว่าจะมีประสิทธิภาพมากแต่อาจมีข้อเสียในการรักษาการโต้ตอบของเบราว์เซอร์ในสคริปต์

ไวยากรณ์กับตัวเลือกความหมาย

ตัวเลือก CSS มีลักษณะที่เป็นไวยากรณ์ โดยจะมีการเชื่อมโยงอย่างใกล้ชิดกับการทำงานภายในของการแสดงข้อความของต้นไม้ DOM ในลักษณะที่จะอ้างอิงรหัสและชื่อคลาสจาก DOM ด้วยเหตุนี้ จึงมีเครื่องมือครบวงจรให้นักพัฒนาเว็บแก้ไขหรือเพิ่มสไตล์ให้กับองค์ประกอบในหน้าเว็บได้ แต่ในบริบทดังกล่าว นักพัฒนาซอฟต์แวร์มีสิทธิ์ควบคุมหน้าเว็บและแผนผัง DOM ของหน้าเว็บดังกล่าวอย่างสมบูรณ์

ในอีกแง่หนึ่ง สคริปต์ Puppeteer เป็นผู้สังเกตการณ์ภายนอกของหน้าเว็บ ดังนั้นเมื่อมีการใช้ตัวเลือก CSS ในบริบทนี้ สคริปต์จึงมีสมมติฐานที่ซ่อนอยู่เกี่ยวกับวิธีการติดตั้งใช้งานหน้าเว็บซึ่งสคริปต์ Puppeteer ไม่สามารถควบคุมได้

ผลกระทบก็คือสคริปต์ดังกล่าวอาจเปราะบางและมีความเสี่ยงต่อการเปลี่ยนแปลงซอร์สโค้ด ตัวอย่างเช่น สมมติว่ามีสคริปต์ Puppeteer สำหรับการทดสอบแบบอัตโนมัติสำหรับเว็บแอปพลิเคชันที่มีโหนด <button>Submit</button> เป็นองค์ประกอบย่อยที่ 3 ขององค์ประกอบ body ข้อมูลโค้ด 1 รายการจากกรอบการทดสอบอาจมีลักษณะดังนี้

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 ก็ยังมีวิธีการอยู่ 2-3 วิธีที่เราสามารถนำการค้นหา ARIA มาใช้ใน Puppeteer โปรดดูเหตุผลก่อนว่า Puppeteer ควบคุมเบราว์เซอร์ได้อย่างไร

เบราว์เซอร์แสดงอินเทอร์เฟซการแก้ไขข้อบกพร่องผ่านโปรโตคอลชื่อ Chrome DevTools Protocol (CDP) วิธีนี้จะแสดงฟังก์ชันการทำงาน เช่น "โหลดหน้าเว็บซ้ำ" หรือ "เรียกใช้ JavaScript ส่วนนี้ในหน้าและส่งผลลัพธ์กลับ" ผ่านอินเทอร์เฟซที่เข้าใจได้โดยไม่จำเป็นต้องเข้าใจภาษาที่พูด

ทั้งส่วนหน้าของ DevTools และ Puppeteer ใช้ CDP เพื่อสื่อสารกับเบราว์เซอร์ ในการใช้งานคำสั่ง CDP จะมีโครงสร้างพื้นฐานสำหรับ DevTools อยู่ภายในคอมโพเนนต์ทั้งหมดของ Chrome อันได้แก่ ในเบราว์เซอร์ ในโหมดแสดงภาพ และอื่นๆ CDP จะดูแลการกำหนดเส้นทางคำสั่งไปยังที่ที่ถูกต้อง

การดำเนินการแบบ Puppeteer เช่น การค้นหา การคลิก และการประเมินนิพจน์จะดำเนินการโดยการใช้ประโยชน์จากคำสั่ง CDP เช่น Runtime.evaluate ที่ประเมิน JavaScript โดยตรงในบริบทของหน้าเว็บและส่งคืนผลลัพธ์ ส่วนการทำงานอื่นๆ ของผู้เชิดหุ่นกระบอก เช่น การเลียนแบบภาวะบกพร่องในการมองเห็นสี การจับภาพหน้าจอ หรือการจับภาพการติดตามจะใช้ CDP ในการสื่อสารโดยตรงกับกระบวนการแสดงผล Blink

CDP

ซึ่งทำให้เรามี 2 เส้นทางในการใช้งานฟังก์ชันการค้นหา ซึ่งเราสามารถ:

  • เขียนตรรกะการค้นหาของเราใน JavaScript และแทรกเข้าไปในหน้าเว็บโดยใช้ Runtime.evaluate หรือ
  • ใช้ปลายทาง CDP ที่เข้าถึงและค้นหาโครงสร้างการช่วยเหลือพิเศษได้โดยตรงในกระบวนการ Blink

เราสร้างต้นแบบ 3 อย่าง ได้แก่

  • JS DOM Traversal - ขึ้นอยู่กับการแทรก JavaScript ในหน้าเว็บ
  • การส่งผ่าน Puppeteer AXTree - อิงจากการใช้การเข้าถึง CDP ที่มีอยู่กับแผนผังการช่วยเหลือพิเศษ
  • การส่งผ่าน DOM ของ CDP - โดยใช้ปลายทาง CDP ใหม่ที่สร้างขึ้นเพื่อการค้นหาแผนผังการช่วยเหลือพิเศษ

การส่งผ่าน DOM ของ JS

ต้นแบบนี้จะส่งผ่าน DOM อย่างเต็มรูปแบบและใช้ element.computedName และ element.computedRole ซึ่งกั้นไว้ในแฟล็กการเปิดตัว ComputedAccessibilityInfo เพื่อดึงข้อมูลชื่อและบทบาทของแต่ละองค์ประกอบในระหว่างการส่งผ่าน

การท่องผ่านหุ่นเชิด AXTree

ในที่นี้เราจะเรียกข้อมูลแผนผังการช่วยเหลือพิเศษแบบเต็มรูปแบบผ่าน CDP แทน และข้ามผ่านแผนผังใน Puppeteer จากนั้นโหนดการช่วยเหลือพิเศษที่ได้จะแมปกับโหนด DOM

การส่งผ่าน DOM ของ CDP

ในต้นแบบนี้ เราได้ใช้ปลายทาง CDP ใหม่สำหรับการค้นหาโครงสร้างการช่วยเหลือพิเศษโดยเฉพาะ ด้วยวิธีนี้ การค้นหาจะเกิดขึ้นที่แบ็กเอนด์ผ่านการใช้งาน C++ แทนการดูในบริบทของหน้าเว็บผ่าน JavaScript

การเปรียบเทียบการทดสอบหน่วย

ตัวเลขต่อไปนี้จะเปรียบเทียบรันไทม์ทั้งหมดของการค้นหาองค์ประกอบ 4 อย่าง 1,000 ครั้งสำหรับต้นแบบ 3 ชิ้น เราได้ดำเนินการเปรียบเทียบในการกำหนดค่า 3 แบบซึ่งแตกต่างกันไปตามขนาดหน้าเว็บ และมีการเปิดใช้งานการแคชองค์ประกอบการเข้าถึงหรือไม่

การเปรียบเทียบ: รันไทม์ทั้งหมดของการค้นหาองค์ประกอบ 4 รายการ 1,000 ครั้ง

ค่อนข้างชัดเจนว่ามีช่องว่างด้านประสิทธิภาพอย่างมากระหว่างกลไกการค้นหาที่ใช้ CDP กับอีก 2 เครื่องมือซึ่งนำมาใช้เพียงใน Puppeteer และความแตกต่างสัมพัทธ์น่าจะเพิ่มขึ้นอย่างมากเมื่อมีขนาดหน้ากระดาษ ค่อนข้างน่าสนใจที่ได้เห็นว่าต้นแบบ JS DOM Traversal นั้นตอบสนองดีเยี่ยมในการเปิดใช้การแคชการเข้าถึง เมื่อปิดใช้การแคช ระบบจะคำนวณแผนผังการช่วยเหลือพิเศษตามคําขอและทิ้งโครงสร้างหลังการโต้ตอบแต่ละครั้งหากปิดใช้โดเมน การเปิดใช้โดเมนจะทำให้ Chromium แคชในโครงสร้างที่คำนวณแทน

สำหรับการข้ามผ่าน JS DOM เราจะขอชื่อและบทบาทที่เข้าถึงได้สำหรับทุกองค์ประกอบในระหว่างการส่งผ่าน ดังนั้นหากปิดใช้การแคช Chromium จะคำนวณและทิ้งโครงสร้างการช่วยเหลือพิเศษสำหรับทุกองค์ประกอบที่เราเข้าชม ในทางตรงกันข้าม โครงสร้างตาม CDP จะถูกทิ้งระหว่างการเรียก CDP แต่ละครั้งเท่านั้น นั่นคือ สำหรับทุกคำค้นหา วิธีเหล่านี้ยังส่งผลดีต่อการเปิดใช้การแคชด้วย เนื่องจากโครงสร้างการช่วยเหลือพิเศษยังคงมีอยู่ในการเรียกใช้ CDP ด้วยเช่นกัน แต่การเพิ่มประสิทธิภาพจึงถือว่าน้อยกว่า

แม้ว่าการแคชจะดูสวยงามในส่วนนี้ แต่ก็ต้องมีค่าใช้จ่ายในการใช้หน่วยความจำเพิ่มเติม สำหรับสคริปต์ Puppeteer ที่ เช่น บันทึกไฟล์การติดตาม อาจเป็นปัญหาได้ เราจึงตัดสินใจที่จะไม่เปิดใช้งานแคชต้นไม้สำหรับการเข้าถึงโดยค่าเริ่มต้น ผู้ใช้สามารถเปิดการแคชด้วยตนเองโดยเปิดใช้โดเมนการเข้าถึงของ CDP

การเปรียบเทียบชุดทดสอบเครื่องมือสำหรับนักพัฒนาเว็บ

การเปรียบเทียบก่อนหน้านี้แสดงให้เห็นว่าการใช้กลไกการค้นหาของเราในชั้น CDP จะช่วยเพิ่มประสิทธิภาพในสถานการณ์การทดสอบหน่วยทางคลินิก

เราแพตช์ชุดการทดสอบแบบรอบด้านของเครื่องมือสำหรับนักพัฒนาเว็บเพื่อใช้ประโยชน์จากต้นแบบ JavaScript และ CDP เทียบกับรันไทม์ เพื่อดูว่าความแตกต่างนั้นเด่นชัดมากพอที่จะทำให้สังเกตเห็นได้ในสถานการณ์ที่สมจริงมากขึ้นหรือไม่ ในการเปรียบเทียบนี้ เราได้เปลี่ยนแปลงตัวเลือกทั้งหมด 43 รายการจาก [aria-label=…] เป็นเครื่องจัดการคำค้นหาที่กำหนดเอง aria/… ซึ่งจากนั้นเราจะนำมาใช้โดยใช้ต้นแบบแต่ละรายการ

ตัวเลือกบางตัวมีการใช้หลายครั้งในสคริปต์ทดสอบ ดังนั้นจำนวนการดำเนินการจริงของเครื่องจัดการคำค้นหา aria จึงเท่ากับ 113 ต่อการเรียกใช้ชุดโปรแกรม จำนวนคำค้นหาทั้งหมดที่เลือกคือ 2, 253 รายการ ดังนั้นจึงมีการเลือกคำค้นหาเพียงส่วนเล็กๆ ผ่านต้นแบบ

การเปรียบเทียบ: ชุดทดสอบ e2e

ดังที่เห็นในรูปด้านบน มีความแตกต่างที่เห็นได้ชัดในรันไทม์ทั้งหมด ข้อมูลมีความชัดเจนมากเกินกว่าที่จะสรุปอะไรที่เฉพาะเจาะจงได้ แต่เราเห็นได้ชัดว่าช่องว่างด้านประสิทธิภาพระหว่างต้นแบบ 2 อย่างนี้แสดงให้เห็นในสถานการณ์นี้เช่นกัน

ปลายทาง CDP ใหม่

จากเกณฑ์เปรียบเทียบข้างต้น และเนื่องจากวิธีการอิงตามการแจ้งว่าไม่เหมาะสมในการเปิดตัวไม่เป็นที่ต้องการโดยทั่วไป เราจึงตัดสินใจใช้คำสั่ง CDP ใหม่ในการค้นหาแผนผังการช่วยเหลือพิเศษ ในตอนนี้ เราต้องค้นหาอินเทอร์เฟซของปลายทางใหม่นี้

สำหรับ Use Case ของเราใน Puppeteer เราต้องการให้ปลายทางใช้สิ่งที่เรียกว่า RemoteObjectIds เป็นอาร์กิวเมนต์ และเพื่อให้เราค้นหาองค์ประกอบ DOM ที่เกี่ยวข้องได้หลังจากนั้น คุณควรแสดงรายการออบเจ็กต์ที่มี backendNodeIds สำหรับองค์ประกอบ DOM

ดังที่เห็นในแผนภูมิด้านล่าง เราได้ทดลองหลายวิธีที่จะทำให้อินเทอร์เฟซนี้พึงพอใจ จากข้อมูลนี้ เราพบว่าขนาดของออบเจ็กต์ที่ส่งกลับมา เช่น เราแสดงผลโหนดการช่วยเหลือพิเศษแบบเต็มหรือไม่ หรือเพียง backendNodeIds เท่านั้นที่ไม่มีความแตกต่างกันอย่างชัดเจน ในทางตรงกันข้าม เราพบว่าการใช้ NextInPreOrderIncludingIgnored ที่มีอยู่เป็นตัวเลือกที่ไม่ดีในการใช้ตรรกะการข้ามผ่านในที่นี้ เนื่องจากทำให้การทำงานช้าลงอย่างเห็นได้ชัด

การเปรียบเทียบ: การเปรียบเทียบต้นแบบการข้ามผ่าน AXTree ที่ใช้ CDP

ดำเนินการให้เสร็จ

ตอนนี้เมื่อเรามีปลายทาง CDP อยู่แล้ว เราจึงได้ใช้เครื่องจัดการคำค้นหาในฝั่ง Puppeteer งานที่น่าเบื่อก็คือการปรับโครงสร้างโค้ดที่ใช้จัดการคำค้นหาเพื่อให้แก้ไขคำค้นหาผ่าน CDP ได้โดยตรง แทนการค้นหาผ่าน JavaScript ที่ประเมินในบริบทของหน้า

ขั้นตอนถัดไปคือ

ตัวแฮนเดิล aria ใหม่ที่มาพร้อมกับ Puppeteer v5.4.0 เป็นตัวแฮนเดิลการค้นหาในตัว เราตั้งตารอที่จะได้เห็นผู้ใช้นำไปปรับใช้ในสคริปต์การทดสอบ และเราก็ตื่นเต้นที่จะได้ฟังไอเดียของคุณว่าเราจะทำให้งานนี้เป็นประโยชน์มากขึ้นได้อย่างไร

ดาวน์โหลดช่องตัวอย่าง

ลองใช้ Chrome Canary, Dev หรือเบต้าเป็นเบราว์เซอร์สำหรับการพัฒนาเริ่มต้น ช่องทางแสดงตัวอย่างเหล่านี้ช่วยให้คุณได้เข้าถึงฟีเจอร์ล่าสุดของเครื่องมือสำหรับนักพัฒนาเว็บ ทดสอบ API แพลตฟอร์มเว็บที่ล้ำสมัย และค้นหาปัญหาในเว็บไซต์ก่อนที่ผู้ใช้จะได้ใช้งาน

ติดต่อทีมเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

ใช้ตัวเลือกต่อไปนี้เพื่อพูดคุยเกี่ยวกับฟีเจอร์ใหม่ๆ และการเปลี่ยนแปลงในโพสต์ หรือเรื่องอื่นๆ ที่เกี่ยวข้องกับเครื่องมือสำหรับนักพัฒนาเว็บ

  • ส่งคำแนะนำหรือความคิดเห็นถึงเราผ่าน crbug.com
  • รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บโดยใช้ตัวเลือกเพิ่มเติม   เพิ่มเติม   > ความช่วยเหลือ > รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บในเครื่องมือสำหรับนักพัฒนาเว็บ
  • ทวีตที่ @ChromeDevTools
  • โปรดแสดงความคิดเห็นในวิดีโอ YouTube เกี่ยวกับมีอะไรใหม่ในเครื่องมือสำหรับนักพัฒนาเว็บในวิดีโอ YouTube หรือเคล็ดลับสำหรับเครื่องมือสำหรับนักพัฒนาเว็บ