همانطور که میدانید برای اجرای تستهای واحد در Angular از jasmine و karma استفاده میشود. برای این منظور در پوشه پروژه یک ترمینال باز کرده و دستور زیر را وارد کنید:
npm run test
که خروجی آن تصویر زیر است:
حالا در مروگر اینترنت صفحه زیر را باز کنید:
http://localhost:9876/
که صفحه زیر را خواهد دید:
در این فرآیند، karma یک وب سرور راهاندازی کرده و با هر تغییری در پروژه،فرآیند build پروژه دوباره انجام شده و تمام تستها نیز انجام خواهد شد و نتیجه را در پنجرهی مروگر خواهید دید.
اما گاهی اوقات لازم است که فرآیند انجام تستها بدون حضور وضعیت live ایجاد شده توسط فرآیند بالا انجام شود.برای نمونه هنگامی که شما برای یک پروژه در github action یک رول تست نوشتهاید، باید فرآیند تست انجام شده (و پایان یابد) و نتیجه در خط فرمان مشخص گردد تا گردش کار (با توجه به موفق بودن تست یا عدم موفقیت آن) ادامه یابد.
اینجاست که puppeteer وارد میشود.این کتابخانه تحت nodejs به شما اجازه اجرای یک chrome یا chromium و کنترل آن را میدهد.حتی میتوانید این مرورگر را بدون هیچ گونه UI اجرا کنید (اصطلاحا headless) و دستورات مورد نظر را برای مرورگر باز شده ارسال کنید.مثلا یک آدرسی را باز کند یا فیلد هایی را پر کند و ...
من در اینجا از توضیح جزئیات این کتابخانه صرف نظر میکنم و پیشنهاد میکنم برای اطلاعات بشتر به صفحه خوده این کتابخانه مراجعه کنید.
اما قبل از اینکه به کار اصلی بپردازیم، یک توضیح کوچک در مورد puppeteer بدهم. در موقع نصب این کتابخانه، دو کار انجام میشود:
از آنجایی که گوگل طبق معمول ایران را از دسترسی آزاد به اطلاعات فناوری تحریم کرده است، فرآیند دانلود این بسته ممکن است شکست بخورد و حتما باید بر روی سیستم شما فیلترشکن یا تحریمشکن برای دانلود موجود باشد.
و البته با هر بار نصب puppeteer نیز باید حدود 150 مگا بابت نیز دانلود کند که اگر از حجمش بگذریم از زمانش نمیتوان چشم پوشی کرد!
اما یک نسخه سبک تر از puppeteer موجود است که به آن puppeteer-core میگویند که فقط قسمت اول را انجام میدهد و بخش دوم را به عهدهی مرورگر موجود بر روی سیستم میگذارد که البته باید آن را config کنید (لطفا مستندات مربوطه را خودتان مطالعه کنید)
برای پروژه های محلی استفاده از puppeteer-core کافیست ولی اگر میخواهد از github action استفاده کنید، حتما باید از 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 میخواهد که دانلود را انجام ندهد و دستور دوم نیز نحوه اجرای کروم روی سیستم را نشان میهد.
ابتدا بسته را نصب کنید یعنی در پوشه پروژه ترمینال را باز کرده و بزنید:
سپس فایل package.json مرتبط با پروژه را باز کرده و خط زیر را در بخش scripts اضافه کنید:
"test:headless": "ng test --no-watch --no-progress --browsers=ChromeHeadlessCI",
در گام سوم فایل 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
چیزی شبیه تصویر زیر را مشاهده میکنید.
کد کامل فایل package.json
{ "name": "testapp", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "test:headless": "ng test --no-watch --no-progress --browsers=ChromeHeadlessCI", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "~11.2.6", "@angular/common": "~11.2.6", "@angular/compiler": "~11.2.6", "@angular/core": "~11.2.6", "@angular/forms": "~11.2.6", "@angular/platform-browser": "~11.2.6", "@angular/platform-browser-dynamic": "~11.2.6", "@angular/router": "~11.2.6", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.11.3" }, "devDependencies": { "@angular-devkit/build-angular": "~0.1102.5", "@angular/cli": "~11.2.5", "@angular/compiler-cli": "~11.2.6", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.1", "codelyzer": "^6.0.0", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~6.1.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "protractor": "~7.0.0", "puppeteer": "^8.0.0", "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "~4.1.5" } }
و کد کامل فایل 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 }); };