عروسک گردان و رویکرد آن به انتخاب کنندگان
Puppeteer یک کتابخانه اتوماسیون مرورگر برای Node است: به شما امکان می دهد مرورگر را با استفاده از یک 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>
به عنوان فرزند سوم عنصر 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 فراهم کند:
- انتخابگرها را در اسکریپت های آزمایشی نسبت به تغییرات کد منبع انعطاف پذیرتر کنید.
- اسکریپت های آزمایشی را خواناتر کنید (نام های قابل دسترس توصیف کننده های معنایی هستند).
- انگیزه شیوه های خوب برای تخصیص ویژگی های دسترسی به عناصر.
بقیه این مقاله به جزئیات نحوه اجرای پروژه Puppetaria می پردازد.
فرآیند طراحی
پس زمینه
همانطور که در بالا توضیح داده شد، ما می خواهیم عناصر پرس و جو را با نام و نقش قابل دسترس آنها فعال کنیم. اینها ویژگیهای درخت دسترسی هستند، درختی دوگانه نسبت به درخت DOM معمول، که توسط دستگاههایی مانند صفحهخوانها برای نمایش صفحات وب استفاده میشود.
از نگاهی به مشخصات محاسبه نام قابل دسترسی ، واضح است که محاسبه نام برای یک عنصر یک کار غیر ضروری است، بنابراین از ابتدا تصمیم گرفتیم که میخواهیم از زیرساخت موجود Chromium برای این کار مجدد استفاده کنیم.
چگونه به اجرای آن نزدیک شدیم
حتی اگر خود را به استفاده از درخت دسترسی Chromium محدود کنیم، راههای زیادی وجود دارد که میتوانیم پرسوجو ARIA را در Puppeteer پیادهسازی کنیم. برای اینکه بفهمیم چرا، ابتدا ببینیم Puppeteer چگونه مرورگر را کنترل می کند.
مرورگر یک رابط اشکال زدایی را از طریق پروتکلی به نام Chrome DevTools Protocol (CDP) نشان می دهد. این کار عملکردهایی مانند "بارگذاری مجدد صفحه" یا "اجرای این قطعه از جاوا اسکریپت در صفحه و بازگرداندن نتیجه" از طریق یک رابط زبان-آگنوستیک را نشان می دهد.
هم DevTools front-end و هم Puppeteer از CDP برای صحبت با مرورگر استفاده می کنند. برای پیاده سازی دستورات CDP، زیرساخت DevTools در تمام اجزای کروم وجود دارد: در مرورگر، در رندر و غیره. CDP از مسیریابی دستورات به مکان مناسب مراقبت می کند.
اقدامات Puppeteer مانند پرس و جو، کلیک کردن، و ارزیابی عبارات با استفاده از دستورات CDP مانند Runtime.evaluate
انجام می شود که جاوا اسکریپت را مستقیماً در زمینه صفحه ارزیابی می کند و نتیجه را پس می دهد. سایر اقدامات Puppeteer مانند شبیه سازی کمبود دید رنگی، گرفتن اسکرین شات یا گرفتن ردیابی از CDP برای ارتباط مستقیم با فرآیند رندر Blink استفاده می کنند.
این در حال حاضر ما را با دو مسیر برای پیاده سازی عملکرد پرس و جو ما باقی می گذارد. ما می توانیم:
- منطق جستجوی ما را در جاوا اسکریپت بنویسید و آن را با استفاده از
Runtime.evaluate
به صفحه تزریق کنید. - از یک نقطه پایانی CDP استفاده کنید که بتواند مستقیماً در فرآیند Blink به درخت دسترسی دسترسی داشته باشد و آن را پرس و جو کند.
ما 3 نمونه اولیه را پیاده سازی کردیم:
- پیمایش JS DOM - بر اساس تزریق جاوا اسکریپت به صفحه
- پیمایش Puppeteer AXTree - بر اساس استفاده از دسترسی CDP موجود به درخت دسترسی
- پیمایش DOM CDP - با استفاده از یک نقطه پایانی جدید CDP که برای جستجو در درخت دسترسی ساخته شده است
پیمایش JS DOM
این نمونه اولیه یک پیمایش کامل از DOM را انجام میدهد و از element.computedName
و element.computedRole
استفاده میکند که روی پرچم راهاندازی ComputedAccessibilityInfo
قرار دارند تا نام و نقش هر عنصر را در طول پیمایش بازیابی کند.
پیمایش AXTree Puppeteer
در اینجا، به جای آن درخت دسترسی کامل را از طریق CDP بازیابی می کنیم و در Puppeteer از آن عبور می کنیم. سپس گره های دسترسی به دست آمده به گره های DOM نگاشت می شوند.
پیمایش CDP DOM
برای این نمونه اولیه، ما یک نقطه پایانی جدید CDP را به طور خاص برای جستجو در درخت دسترسی پیادهسازی کردیم. به این ترتیب، پرسوجو میتواند در بکاند از طریق پیادهسازی C++ به جای اینکه در زمینه صفحه از طریق جاوا اسکریپت انجام شود، انجام شود.
معیار آزمون واحد
شکل زیر کل زمان اجرای پرس و جو از چهار عنصر را 1000 بار برای 3 نمونه اولیه مقایسه می کند. معیار در 3 پیکربندی مختلف اجرا شد که اندازه صفحه و فعال بودن یا نبودن حافظه پنهان عناصر دسترسی را تغییر میداد.
کاملاً واضح است که شکاف عملکرد قابل توجهی بین مکانیسم پرس و جو مبتنی بر CDP و دو مکانیسم دیگر که فقط در Puppeteer پیادهسازی شدهاند وجود دارد، و به نظر میرسد که تفاوت نسبی به طور چشمگیری با اندازه صفحه افزایش مییابد. تا حدودی جالب است که ببینیم نمونه اولیه پیمایش JS DOM به خوبی به فعال کردن کش دسترسی پاسخ می دهد. با غیرفعال بودن حافظه پنهان، درخت دسترسپذیری در صورت تقاضا محاسبه میشود و اگر دامنه غیرفعال باشد، پس از هر تعامل درخت را کنار میگذارد. فعال کردن دامنه باعث میشود Chromium درخت محاسبهشده را در حافظه پنهان نگه دارد.
برای پیمایش JS DOM ما نام و نقش قابل دسترسی برای هر عنصر را در طول پیمایش میخواهیم، بنابراین اگر حافظه پنهان غیرفعال باشد، Chromium درخت دسترسپذیری را برای هر عنصری که بازدید میکنیم محاسبه کرده و کنار میگذارد. از سوی دیگر، برای رویکردهای مبتنی بر CDP، درخت فقط بین هر فراخوانی به CDP، یعنی برای هر پرس و جو حذف می شود. این رویکردها همچنین از فعال کردن کش سود میبرند، زیرا درخت دسترسی در سراسر فراخوانیهای CDP ادامه مییابد، اما بنابراین افزایش عملکرد نسبتاً کوچکتر است.
اگرچه فعال کردن کش در اینجا مطلوب به نظر می رسد، هزینه استفاده از حافظه اضافی را نیز به همراه دارد. برای اسکریپت های Puppeteer که به عنوان مثال فایل های ردیابی را ضبط می کنند ، این می تواند مشکل ساز باشد. بنابراین تصمیم گرفتیم که ذخیره درخت دسترسپذیری را بهصورت پیشفرض فعال نکنیم. کاربران میتوانند با فعال کردن دامنه دسترسپذیری CDP، ذخیره خود را در حافظه پنهان فعال کنند.
معیار مجموعه آزمایشی DevTools
معیار قبلی نشان داد که پیاده سازی مکانیسم پرس و جو ما در لایه CDP باعث افزایش عملکرد در سناریوی بالینی آزمون واحد می شود.
برای اینکه ببینیم آیا این تفاوت به اندازهای مشخص است که در یک سناریوی واقعیتر اجرای یک مجموعه آزمایشی کامل قابل توجه باشد، ما مجموعه آزمایشی سرتاسر DevTools را برای استفاده از نمونههای اولیه جاوا اسکریپت و CDP وصله کردیم و زمانهای اجرا را مقایسه کردیم. . در این معیار، ما در مجموع 43 انتخابگر را از [aria-label=…]
به aria/…
پرس و جو سفارشی تغییر دادیم که سپس با استفاده از هر یک از نمونه های اولیه آن را پیاده سازی کردیم.
برخی از انتخابگرها چندین بار در اسکریپتهای آزمایشی استفاده میشوند، بنابراین تعداد واقعی اجرای کنترلکننده پرس و جوی aria
113 در هر اجرای مجموعه بود. تعداد کل انتخاب های پرس و جو 2253 بود، بنابراین تنها کسری از انتخاب های پرس و جو از طریق نمونه های اولیه اتفاق افتاد.
همانطور که در شکل بالا مشاهده می شود، یک تفاوت قابل تشخیص در کل زمان اجرا وجود دارد. داده ها برای نتیجه گیری خاصی بسیار پر سر و صدا هستند، اما واضح است که شکاف عملکرد بین دو نمونه اولیه در این سناریو نیز نشان داده می شود.
یک نقطه پایانی جدید CDP
با توجه به معیارهای بالا، و از آنجایی که رویکرد مبتنی بر پرچم راه اندازی به طور کلی نامطلوب بود، تصمیم گرفتیم با اجرای یک دستور CDP جدید برای پرس و جو از درخت دسترسی به جلو حرکت کنیم. اکنون، باید رابط کاربری این نقطه پایانی جدید را کشف میکردیم.
برای مورد استفاده ما در Puppeteer، ما به نقطه پایانی نیاز داریم تا RemoteObjectIds
به عنوان آرگومان در نظر بگیرد و برای اینکه بتوانیم عناصر DOM مربوطه را پس از آن پیدا کنیم، باید فهرستی از اشیاء را که حاوی backendNodeIds
برای عناصر DOM است، برگرداند.
همانطور که در نمودار زیر مشاهده می شود، ما چندین روش را برای رضایت این رابط امتحان کردیم. از این، متوجه شدیم که اندازه اشیاء برگشتی، یعنی اینکه آیا گرههای دسترسی کامل را برگرداندهایم یا نه، یا فقط backendNodeIds
تفاوت قابلتوجهی ایجاد نمیکند. از سوی دیگر، متوجه شدیم که استفاده از NextInPreOrderIncludingIgnored
موجود، انتخاب ضعیفی برای پیادهسازی منطق پیمایش در اینجا بود، زیرا کاهش قابل توجهی را به همراه داشت.
تمام کردنش
اکنون، با در نظر گرفتن نقطه پایانی CDP، کنترل کننده پرس و جو را در سمت Puppeteer پیاده سازی کردیم. غرغر کار در اینجا این بود که کد مدیریت پرس و جو را بازسازی کنیم تا به جای پرس و جو از طریق جاوا اسکریپت ارزیابی شده در متن صفحه، پرس و جوها را مستقیماً از طریق CDP حل کنند.
بعدش چی؟
هندلر جدید aria
با Puppeteer v5.4.0 به عنوان یک کنترل کننده پرس و جو داخلی عرضه شد. ما مشتاقانه منتظر این هستیم که ببینیم کاربران چگونه آن را در اسکریپت های آزمایشی خود به کار می گیرند، و نمی توانیم منتظر شنیدن ایده های شما در مورد اینکه چگونه می توانیم این را حتی مفیدتر کنیم!
کانال های پیش نمایش را دانلود کنید
استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیشفرض خود در نظر بگیرید. این کانالهای پیشنمایش به شما امکان دسترسی به جدیدترین ویژگیهای DevTools را میدهند، به شما اجازه میدهند APIهای پلتفرم وب پیشرفته را آزمایش کنید و به شما کمک میکنند تا قبل از کاربران، مشکلات سایت خود را پیدا کنید!
با تیم Chrome DevTools در تماس باشید
از گزینههای زیر برای بحث در مورد ویژگیهای جدید، بهروزرسانیها یا هر چیز دیگری مربوط به DevTools استفاده کنید.
- بازخورد و درخواست های ویژگی را برای ما در crbug.com ارسال کنید.
- یک مشکل DevTools را با استفاده از گزینه های بیشتر > راهنما > گزارش مشکل DevTools در DevTools گزارش کنید.
- توییت در @ChromeDevTools .
- نظرات خود را در مورد موارد جدید در ویدیوهای DevTools YouTube یا DevTools Tips ویدیوهای YouTube بگذارید.