Angular + puppeteer for (github action)

همانطور که می‌دانید برای اجرای تست‌های واحد در Angular از jasmine و karma استفاده می‌شود. برای این منظور در پوشه پروژه یک ترمینال باز کرده و دستور زیر را وارد کنید:npm run testکه خروجی آن تصویر زیر است:اجرای تست‌ها در یک پروژه‌ی Angularحالا در مروگر اینترنت صفحه زیر را باز کنید:http://localhost:9876/که صفحه زیر را خواهد دید:نتیجه اجرای تست‌ها در مرورگردر این فرآیند، karma یک وب سرور راه‌اندازی کرده و با هر تغییری در پروژه،فرآیند build پروژه دوباره انجام شده و تمام تست‌ها نیز انجام خواهد شد و نتیجه را در پنجره‌ی مروگر خواهید دید. اما گاهی اوقات لازم است که فرآیند انجام تست‌ها بدون حضور وضعیت live ایجاد شده توسط فرآیند بالا انجام شود.برای نمونه هنگامی که شما برای یک پروژه در github action یک رول تست نوشته‌اید، باید فرآیند تست انجام شده (و پایان یابد) و نتیجه در خط فرمان مشخص گردد تا گردش کار (با توجه به موفق بودن تست یا عدم موفقیت آن) ادامه یابد.اینجاست که puppeteer وارد می‌شود.این کتابخانه تحت nodejs به شما اجازه اجرای یک chrome یا chromium و کنترل آن را می‌دهد.حتی می‌توانید این مرورگر را بدون هیچ گونه UI اجرا کنید (اصطلاحا headless) و دستورات مورد نظر را برای مرورگر باز شده ارسال کنید.مثلا یک آدرسی را باز کند یا فیلد هایی را پر کند و ...من در اینجا از توضیح جزئیات این کتابخانه صرف نظر می‌کنم و پیشنهاد می‌کنم برای اطلاعات بشتر به صفحه خوده این کتابخانه مراجعه کنید.اما قبل از اینکه به کار اصلی بپردازیم، یک توضیح کوچک در مورد puppeteer بدهم. در موقع نصب این کتابخانه، دو کار انجام می‌شود:نخست اینکه زیر ساخت مورد نیاز برای دست‌اندازی به یک مرورگر chromium base فراهم می‌شوددوم اینکه آخرین نسخه از مرورگر chrome از سرورهای گوگل دانلود شده و در یک مسیر مشخص برای فراخوانی توسط puppeteer ذخیره می‌شود.از آنجایی که گوگل طبق معمول ایران را از دسترسی آزاد به اطلاعات فناوری تحریم کرده است، فرآیند دانلود این بسته ممکن است شکست بخورد و حتما باید بر روی سیستم شما فیلترشکن یا تحریم‌شکن برای دانلود موجود باشد.و البته با هر بار نصب puppeteer نیز باید حدود 150 مگا بابت نیز دانلود کند که اگر از حجمش بگذریم از زمانش نمی‌توان چشم پوشی کرد!اما یک نسخه سبک تر از puppeteer موجود است که به آن puppeteer-core می‌گویند که فقط قسمت اول را انجام می‌دهد و بخش دوم را به عهده‌ی مرورگر موجود بر روی سیستم می‌گذارد که البته باید آن را config کنید (لطفا مستندات مربوطه را خودتان مطالعه کنید)برای پروژه های محلی استفاده از puppeteer-core کافیست ولی اگر می‌خواهد از github action استفاده کنید، حتما باید از puppeteer استفاده نماید.برای اینکه در هر بار نصب puppeteer بر روی پروژه‌ی سیستم اقدام به دانلود و نصب مرورگر نکند نیز راهی وجود داد!عدم دانلود مرورگر در هنگام نصب puppeteerچون من از ubuntu استفاده می‌کنم برای همین راه حل‌ها هم مربوط به این سیستم است ولی برای سایر توزیع‌های لینوکس نیز صادق است.همچنین برای سیستم عامل‌های دیگر نیز می‌توانید راه حل را (با کمی جستجو) پیدا کنید.دو دستور زیر را در ترمینال بزنید:npm config set puppeteer_skip_chromium_download true npm config set puppeteer_excutable_path /snap/bin/chromiumاین دو دستور باعث می‌شود که فایل ~/.npmrcبا محتویات زیر ایجاد شود:puppeteer_skip_chromium_download=true puppeteer_executable_path=/snap/bin/chromiumالبته می‌توانید این فایل را هم مستقیما ویرایش کنید!دستور اول صریحا از puppeteer می‌خواهد که دانلود را انجام ندهد و دستور دوم نیز نحوه اجرای کروم روی سیستم را نشان می‌هد.نصب و پیکربندی puppeteer بر روی پروژه Angularابتدا بسته را نصب کنید یعنی در پوشه پروژه ترمینال را باز کرده و بزنید:npm install puppeteer --save-devسپس فایل package.json مرتبط با پروژه را باز کرده و خط زیر را در بخش scripts اضافه کنید:&quottest:headless&quot: &quotng test --no-watch --no-progress --browsers=ChromeHeadlessCI&quot,در گام سوم فایل karma.conf.js را باز کنید .در این فایل باید دو قسمت را ویرایش کنید.اول اینکه خط زیر را در ابتدای فایل وارد کنیدprocess.env.CHROME_BIN = require('puppeteer').executablePath();در واقع این خط محل نصب فایل مرورگر مورد نظر را به متغیر محیطی CHROME_BIN نسبت می‌دهد تا فرآیند اجرا به درستی انجام شود.دوم اینکه خط زیر ار پیدا کنیدbrowsers: ['Chrome'],و آن را با این جانشین کنیدbrowsers: ['ChromeHeadlessCI'], customLaunchers: { ChromeHeadlessCI: { base: 'ChromeHeadless', flags: ['--no-sandbox'] } },و تمام.حالا با دستور npm run test:headlessچیزی شبیه تصویر زیر را مشاهده می‌کنید.اجرای تست‌ها در حالت headlessکد کامل فایل package.json{ &quotname&quot: &quottestapp&quot, &quotversion&quot: &quot0.0.0&quot, &quotscripts&quot: { &quotng&quot: &quotng&quot, &quotstart&quot: &quotng serve&quot, &quotbuild&quot: &quotng build&quot, &quottest&quot: &quotng test&quot, &quottest:headless&quot: &quotng test --no-watch --no-progress --browsers=ChromeHeadlessCI&quot, &quotlint&quot: &quotng lint&quot, &quote2e&quot: &quotng e2e&quot }, &quotprivate&quot: true, &quotdependencies&quot: { &quot@angular/animations&quot: &quot~11.2.6&quot, &quot@angular/common&quot: &quot~11.2.6&quot, &quot@angular/compiler&quot: &quot~11.2.6&quot, &quot@angular/core&quot: &quot~11.2.6&quot, &quot@angular/forms&quot: &quot~11.2.6&quot, &quot@angular/platform-browser&quot: &quot~11.2.6&quot, &quot@angular/platform-browser-dynamic&quot: &quot~11.2.6&quot, &quot@angular/router&quot: &quot~11.2.6&quot, &quotrxjs&quot: &quot~6.6.0&quot, &quottslib&quot: &quot^2.0.0&quot, &quotzone.js&quot: &quot~0.11.3&quot }, &quotdevDependencies&quot: { &quot@angular-devkit/build-angular&quot: &quot~0.1102.5&quot, &quot@angular/cli&quot: &quot~11.2.5&quot, &quot@angular/compiler-cli&quot: &quot~11.2.6&quot, &quot@types/jasmine&quot: &quot~3.6.0&quot, &quot@types/node&quot: &quot^12.11.1&quot, &quotcodelyzer&quot: &quot^6.0.0&quot, &quotjasmine-core&quot: &quot~3.6.0&quot, &quotjasmine-spec-reporter&quot: &quot~5.0.0&quot, &quotkarma&quot: &quot~6.1.0&quot, &quotkarma-chrome-launcher&quot: &quot~3.1.0&quot, &quotkarma-coverage&quot: &quot~2.0.3&quot, &quotkarma-jasmine&quot: &quot~4.0.0&quot, &quotkarma-jasmine-html-reporter&quot: &quot^1.5.0&quot, &quotprotractor&quot: &quot~7.0.0&quot, &quotpuppeteer&quot: &quot^8.0.0&quot, &quotts-node&quot: &quot~8.3.0&quot, &quottslint&quot: &quot~6.1.0&quot, &quottypescript&quot: &quot~4.1.5&quot } }و کد کامل فایل karma.conf.js// Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html process.env.CHROME_BIN = require('puppeteer').executablePath(); module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage'), require('@angular-devkit/build-angular/plugins/karma') ], client: { jasmine: { // you can add configuration options for Jasmine here // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html // for example, you can disable the random execution with `random: false` // or set a specific seed with `seed: 4321` }, clearContext: false // leave Jasmine Spec Runner output visible in browser }, jasmineHtmlReporter: { suppressAll: true // removes the duplicated traces }, coverageReporter: { dir: require('path').join(__dirname, './coverage/testapp'), subdir: '.', reporters: [ { type: 'html' }, { type: 'text-summary' } ] }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['ChromeHeadlessCI'], customLaunchers: { ChromeHeadlessCI: { base: 'ChromeHeadless', flags: ['--no-sandbox'] } }, singleRun: false, restartOnFileChange: true }); };