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.$(>'d>evsite-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-sea>rch f>orm 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 ส่วนนี้ในหน้าเว็บและส่งคืนผลลัพธ์" ผ่านอินเทอร์เฟซที่ไม่ขึ้นอยู่กับภาษา
ทั้งฟรอนท์เอนด์ของเครื่องมือสำหรับนักพัฒนาเว็บและ Puppeteer ต่างก็ใช้ CDP เพื่อสื่อสารกับเบราว์เซอร์ หากต้องการใช้คำสั่ง CDP จะมีโครงสร้างพื้นฐานของเครื่องมือสำหรับนักพัฒนาเว็บอยู่ภายในคอมโพเนนต์ทั้งหมดของ Chrome ไม่ว่าจะเป็นในเบราว์เซอร์ ในโปรแกรมแสดงผล และอื่นๆ CDP จะดูแลการกำหนดเส้นทางคำสั่งไปยังตำแหน่งที่ถูกต้อง
การดำเนินการของ Puppeteer เช่น การค้นหา การคลิก และการประเมินนิพจน์จะดำเนินการโดยใช้ประโยชน์จากคำสั่ง CDP เช่น Runtime.evaluate ซึ่งประเมิน JavaScript โดยตรงในบริบทของหน้าเว็บและส่งคืนผลลัพธ์ การดำเนินการอื่นๆ ของ Puppeteer เช่น การจำลองการมองเห็นสีบกพร่อง การจับภาพหน้าจอ หรือการบันทึกการติดตามจะใช้ CDP เพื่อสื่อสารกับกระบวนการแสดงผล Blink โดยตรง

ซึ่งทำให้เรามี 2 วิธีในการใช้ฟังก์ชันการค้นหา ได้แก่
- เขียนตรรกะการค้นหาใน JavaScript และแทรกลงในหน้าโดยใช้
Runtime.evaluateหรือ - ใช้ปลายทาง CDP ที่สามารถเข้าถึงและค้นหาแผนผังการช่วยเหลือพิเศษได้โดยตรงในกระบวนการ Blink
เราได้สร้างต้นแบบ 3 รายการดังนี้
- การข้ามผ่าน JS DOM - อิงตามการแทรก JavaScript ลงในหน้าเว็บ
- การข้ามผ่าน AXTree ของ Puppeteer - อิงตามการใช้สิทธิ์เข้าถึง CDP ที่มีอยู่ไปยังแผนผังการช่วยเหลือพิเศษ
- การข้ามผ่าน DOM ของ CDP - การใช้ปลายทาง CDP ใหม่ที่สร้างขึ้นเพื่อวัตถุประสงค์ในการค้นหาแผนผังการช่วยเหลือพิเศษโดยเฉพาะ
การข้ามผ่าน JS DOM
ต้นแบบนี้จะทำการข้ามผ่าน DOM ทั้งหมด และใช้ element.computedName และ element.computedRole ซึ่งถูกจำกัดด้วยแฟล็กการเปิดตัว ComputedAccessibilityInfo เพื่อดึงชื่อและบทบาทของแต่ละองค์ประกอบในระหว่างการข้ามผ่าน
การข้ามผ่าน AXTree ของ Puppeteer
ในที่นี้ เราจะดึงข้อมูลแผนผังการช่วยเหลือพิเศษแบบเต็มผ่าน CDP และไปยังส่วนต่างๆ ใน Puppeteer แทน จากนั้นระบบจะแมปโหนดการช่วยเหลือพิเศษที่ได้กับโหนด DOM
การข้ามผ่าน DOM ของ CDP
สำหรับต้นแบบนี้ เราได้ใช้ปลายทาง CDP ใหม่สำหรับการค้นหาแผนผังการช่วยเหลือพิเศษโดยเฉพาะ วิธีนี้จะช่วยให้การค้นหาเกิดขึ้นในแบ็กเอนด์ผ่านการติดตั้งใช้งาน C++ แทนที่จะเกิดขึ้นในบริบทของหน้าผ่าน JavaScript
การเปรียบเทียบ Unit Test
รูปภาพต่อไปนี้เปรียบเทียบรันไทม์ทั้งหมดของการค้นหาองค์ประกอบ 4 รายการ 1,000 ครั้งสำหรับต้นแบบทั้ง 3 รายการ การเปรียบเทียบดำเนินการในการกำหนดค่าที่แตกต่างกัน 3 แบบ โดยมีขนาดหน้าเว็บที่แตกต่างกัน และเปิดใช้การแคชองค์ประกอบการช่วยเหลือพิเศษหรือไม่
เห็นได้ชัดว่ามีช่องว่างด้านประสิทธิภาพที่สำคัญระหว่างกลไกการค้นหาที่สนับสนุนโดย CDP กับอีก 2 กลไกที่ใช้ใน Puppeteer เพียงอย่างเดียว และความแตกต่างที่เกี่ยวข้องดูเหมือนจะเพิ่มขึ้นอย่างมากตามขนาดหน้าเว็บ น่าสนใจที่เห็นว่าต้นแบบการข้ามผ่าน JS DOM ตอบสนองต่อการเปิดใช้การแคชการช่วยเหลือพิเศษได้ดีมาก เมื่อปิดใช้การแคช ระบบจะคำนวณแผนผังการช่วยเหลือพิเศษตามต้องการและทิ้งแผนผังหลังจากการโต้ตอบแต่ละครั้งหากโดเมนถูกปิดใช้ การเปิดใช้โดเมนจะทำให้ Chromium แคชแผนผังที่คำนวณแล้วแทน
สำหรับการท่อง JS DOM เราจะขอชื่อและบทบาทที่เข้าถึงได้สำหรับทุกองค์ประกอบในระหว่างการท่อง ดังนั้นหากปิดใช้การแคช Chromium จะคำนวณและทิ้งแผนผังการช่วยเหลือพิเศษสำหรับทุกองค์ประกอบที่เราเข้าชม ในทางกลับกัน สำหรับแนวทางที่อิงตาม CDP ระบบจะทิ้งโครงสร้างต้นไม้ระหว่างการเรียกใช้ CDP แต่ละครั้งเท่านั้น กล่าวคือสำหรับทุกคำค้นหา แนวทางเหล่านี้ยังได้รับประโยชน์จากการเปิดใช้การแคชด้วย เนื่องจากต้นไม้การช่วยเหลือพิเศษจะยังคงอยู่ในการเรียก CDP แต่ประสิทธิภาพที่เพิ่มขึ้นจึงค่อนข้างน้อยกว่า
แม้ว่าการเปิดใช้การแคชจะดูน่าสนใจในกรณีนี้ แต่ก็มีค่าใช้จ่ายในการใช้งานหน่วยความจำเพิ่มเติม สำหรับสคริปต์ Puppeteer เช่น บันทึกไฟล์การติดตาม อาจทำให้เกิดปัญหาได้ เราจึงตัดสินใจที่จะไม่เปิดใช้การแคชแผนผังการช่วยเหลือพิเศษโดยค่าเริ่มต้น ผู้ใช้สามารถเปิดการแคชด้วยตนเองได้โดยเปิดใช้โดเมนการช่วยเหลือพิเศษของ CDP
การเปรียบเทียบชุดโปรแกรมทดสอบเครื่องมือสำหรับนักพัฒนาเว็บ
เกณฑ์มาตรฐานก่อนหน้านี้แสดงให้เห็นว่าการใช้กลไกการค้นหาที่เลเยอร์ CDP ช่วยเพิ่มประสิทธิภาพในสถานการณ์การทำ Unit Test ทางคลินิก
เราแพตช์ชุดโปรแกรมทดสอบตั้งแต่ต้นจนจบของเครื่องมือสำหรับนักพัฒนาเว็บเพื่อใช้ประโยชน์จากต้นแบบที่ใช้ JavaScript และ CDP และเปรียบเทียบรันไทม์ เพื่อดูว่าความแตกต่างนั้นชัดเจนพอที่จะสังเกตเห็นได้ในสถานการณ์ที่สมจริงยิ่งขึ้นของการเรียกใช้ชุดโปรแกรมทดสอบแบบเต็มหรือไม่ ในการเปรียบเทียบนี้ เราได้เปลี่ยนตัวเลือกทั้งหมด 43 รายการจาก [aria-label=…] เป็นเครื่องมือจัดการการค้นหาที่กำหนดเอง aria/… ซึ่งเราได้นำไปใช้โดยใช้ต้นแบบแต่ละรายการ
ระบบใช้ตัวเลือกบางรายการหลายครั้งในสคริปต์ทดสอบ ดังนั้นจำนวนการดำเนินการจริงของเครื่องจัดการการค้นหา aria จึงเท่ากับ 113 ครั้งต่อการเรียกใช้ชุดทดสอบ การเลือกคำค้นหาทั้งหมดมีจำนวน 2, 253 รายการ ดังนั้นการเลือกคำค้นหาเพียงส่วนน้อยเท่านั้นที่เกิดขึ้นผ่านต้นแบบ
ดังที่เห็นในภาพด้านบน ระยะเวลาที่ใช้ในการประมวลผลทั้งหมดมีความแตกต่างกันอย่างเห็นได้ชัด ข้อมูลมีความผันผวนมากเกินกว่าที่จะสรุปอะไรที่เฉพาะเจาะจงได้ แต่เห็นได้ชัดว่าช่องว่างด้านประสิทธิภาพระหว่างต้นแบบทั้ง 2 รายการแสดงให้เห็นในสถานการณ์นี้ด้วย
ปลายทาง CDP ใหม่
เมื่อพิจารณาจากเกณฑ์เปรียบเทียบข้างต้น และเนื่องจากวิธีการที่อิงตาม Flag การเปิดตัวไม่เป็นที่ต้องการโดยทั่วไป เราจึงตัดสินใจที่จะดำเนินการต่อด้วยการใช้คำสั่ง CDP ใหม่สำหรับการค้นหาแผนผังการช่วยเหลือพิเศษ ตอนนี้เราต้องหาอินเทอร์เฟซของปลายทางใหม่นี้
สำหรับ Use Case ของเราใน Puppeteer เราต้องการให้ปลายทางใช้สิ่งที่เรียกว่า RemoteObjectIds เป็นอาร์กิวเมนต์ และเพื่อให้เราค้นหาองค์ประกอบ DOM ที่เกี่ยวข้องได้ในภายหลัง ปลายทางควรแสดงรายการออบเจ็กต์ที่มี backendNodeIds สำหรับองค์ประกอบ DOM
ดังที่เห็นในแผนภูมิด้านล่าง เราได้ลองใช้แนวทางหลายอย่างที่สอดคล้องกับอินเทอร์เฟซนี้ จากข้อมูลนี้ เราพบว่าขนาดของออบเจ็กต์ที่แสดงผล ไม่ว่าเราจะแสดงผลโหนดการช่วยเหลือพิเศษแบบเต็มหรือเฉพาะ backendNodeIds ก็ไม่ได้สร้างความแตกต่างที่สังเกตเห็นได้ ในทางกลับกัน เราพบว่าการใช้ NextInPreOrderIncludingIgnored ที่มีอยู่เป็นตัวเลือกที่ไม่ดีสำหรับการใช้ตรรกะการข้ามผ่านที่นี่ เนื่องจากทำให้เกิดความล่าช้าอย่างเห็นได้ชัด
สรุป
ตอนนี้เมื่อมีปลายทาง CDP แล้ว เราได้ใช้เครื่องมือจัดการการค้นหาในฝั่ง Puppeteer งานที่หนักหน่วงในส่วนนี้คือการปรับโครงสร้างโค้ดการจัดการคำค้นหาเพื่อให้คำค้นหาแก้ไขผ่าน CDP ได้โดยตรง แทนที่จะค้นหาผ่าน JavaScript ที่ประเมินในบริบทของหน้าเว็บ
ต้องทำอะไรต่อ
เครื่องจัดการ aria ใหม่มาพร้อมกับ Puppeteer v5.4.0 เป็นเครื่องจัดการการค้นหาในตัว เราหวังเป็นอย่างยิ่งว่าจะได้เห็นวิธีที่ผู้ใช้นำไปใช้ในสคริปต์ทดสอบ และหวังว่าจะได้ฟังแนวคิดของคุณเกี่ยวกับวิธีที่เราจะทำให้สคริปต์นี้มีประโยชน์มากยิ่งขึ้น
ดาวน์โหลดช่องทางตัวอย่าง
ลองใช้ Chrome Canary, Dev หรือ Beta เป็นเบราว์เซอร์สำหรับการพัฒนาเริ่มต้น ช่องทางเวอร์ชันตัวอย่างเหล่านี้ช่วยให้คุณเข้าถึงฟีเจอร์ล่าสุดของเครื่องมือสำหรับนักพัฒนาเว็บ, ทดสอบ API ของแพลตฟอร์มเว็บที่ล้ำสมัย และช่วยคุณค้นหาปัญหาในเว็บไซต์ก่อนที่ผู้ใช้จะพบ
ติดต่อทีมเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
ใช้ตัวเลือกต่อไปนี้เพื่อพูดคุยเกี่ยวกับฟีเจอร์ใหม่ การอัปเดต หรือสิ่งอื่นๆ ที่เกี่ยวข้องกับเครื่องมือสำหรับนักพัฒนาเว็บ
- ส่งความคิดเห็นและคำขอฟีเจอร์ถึงเราได้ที่ crbug.com
- รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บโดยใช้ ตัวเลือกเพิ่มเติม > ความช่วยเหลือ > รายงานปัญหาเกี่ยวกับเครื่องมือสำหรับนักพัฒนาเว็บในเครื่องมือสำหรับนักพัฒนาเว็บ
- ทวีตไปที่ @ChromeDevTools
- แสดงความคิดเห็นในวิดีโอ YouTube เกี่ยวกับมีอะไรใหม่ในเครื่องมือสำหรับนักพัฒนาเว็บหรือวิดีโอ YouTube เกี่ยวกับเคล็ดลับสำหรับเครื่องมือสำหรับนักพัฒนาเว็บ