اختبار إنهاء عامل الخدمة مع Puppeteer

يشرح هذا الدليل كيفية إنشاء إضافات أكثر قوة عن طريق اختبار إنهاء مشغّل الخدمات باستخدام Puppeteer. من المهم الاستعداد للتعامل مع الإنهاء في أي وقت لأنّه قد يحدث بدون تحذير، ما يؤدي إلى خسارة حالة عامل الخدمة غير المستمرة. وبالتالي، يجب أن تحفظ الإضافات الحالة المهمة وأن تكون قادرة على معالجة الطلبات عند بدئها مجددًا عند توفّر حدث يجب التعامل معه.

قبل البدء

استنسِخ أو نزِّل مستودع chrome-extensions-pattern. سنستخدم الإضافة التجريبية في /functional-samples/tutorial.terminate-sw/test-extension التي ترسِل رسالة إلى مشغّل الخدمات في كل مرة يتم النقر فيها على زر وتتم إضافة نص إلى الصفحة في حال تلقّي رد.

ستحتاج أيضًا إلى تثبيت Node.JS وهو وقت التشغيل المبني على Puppeteer.

الخطوة 1: بدء مشروع Node.js

أنشئ الملفات التالية في دليل جديد. يتم معًا إنشاء مشروع Node.js جديد وتوفير البنية الأساسية لمجموعة اختبار Puppeteer باستخدام Jest كجهة تشغيل اختبار. يمكنك الاطّلاع على مقالة اختبار إضافات Chrome باستخدام Puppeteer للحصول على مزيد من التفاصيل حول هذا الإعداد.

package.json:

{
  "name": "puppeteer-demo",
  "version": "1.0",
  "dependencies": {
    "jest": "^29.7.0",
    "puppeteer": "^22.1.0"
  },
  "scripts": {
    "start": "jest ."
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0"
  }
}

index.test.js:

const puppeteer = require('puppeteer');

const SAMPLES_REPO_PATH = 'PATH_TO_SAMPLES_REPOSITORY';
const EXTENSION_PATH = `${SAMPLES_REPO_PATH}/functional-samples/tutorial.terminate-sw/test-extension`;
const EXTENSION_ID = 'gjgkofgpcmpfpggbgjgdfaaifcmoklbl';

let browser;

beforeEach(async () => {
  browser = await puppeteer.launch({
    // Set to 'new' to hide Chrome if running as part of an automated build.
    headless: false,
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`
    ]
  });
});

afterEach(async () => {
  await browser.close();
  browser = undefined;
});

لاحظ أنّ الاختبار يُحمِّل test-extension من مستودع النماذج. يعتمد معالج chrome.runtime.onMessage على الحالة التي تم ضبطها في المعالج لحدث chrome.runtime.onInstalled. نتيجةً لذلك، سيتم فقدان محتوى data عند إنهاء مشغّل الخدمات وعند عدم الرد على أي رسائل مستقبلية، سيتم فقدانها. سنحل هذه المشكلة بعد كتابة الاختبار.

service-worker-broken.js:

let data;

chrome.runtime.onInstalled.addListener(() => {
  data = { version: chrome.runtime.getManifest().version };
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse(data.version);
});

الخطوة 2: تثبيت التبعيات

شغِّل npm install لتثبيت التبعيات المطلوبة.

الخطوة 3: كتابة اختبار أساسي

أضِف الاختبار التالي إلى أسفل index.test.js. يؤدي ذلك إلى فتح صفحة الاختبار من الإضافة التجريبية، والنقر على عنصر الزرّ وانتظار تلقّي ردّ من عامل الخدمة.

test('can message service worker', async () => {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector('#response-0');
});

يمكنك إجراء الاختبار باستخدام npm start ومن المفترَض أن يتم إكماله بنجاح.

الخطوة 4: إنهاء حساب مشغّل الخدمات

إضافة دالة المساعدة التالية التي تُنهي عامل الخدمة:

/**
 * Stops the service worker associated with a given extension ID. This is done
 * by creating a new Chrome DevTools Protocol session, finding the target ID
 * associated with the worker and running the Target.closeTarget command.
 *
 * @param {Page} browser Browser instance
 * @param {string} extensionId Extension ID of worker to terminate
 */
async function stopServiceWorker(browser, extensionId) {
  const host = `chrome-extension://${extensionId}`;

  const target = await browser.waitForTarget((t) => {
    return t.type() === 'service_worker' && t.url().startsWith(host);
  });

  const worker = await target.worker();
  await worker.close();
}

وأخيرًا، عدِّل الاختبار باستخدام الرمز التالي. قم الآن بإنهاء عامل الخدمة وانقر فوق الزر مرة أخرى للتحقق من أنك تلقيت ردًا.

test('can message service worker when terminated', async () => {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector('#response-0');

  // Terminate service worker
  await stopServiceWorker(page, EXTENSION_ID);

  // Try to send another message
  await page.click('button');
  await page.waitForSelector('#response-1');
});

الخطوة 5: إجراء الاختبار

قم بتشغيل npm start. من المفترض أن يفشل الاختبار مما يشير إلى أن عامل الخدمة لم يستجب بعد إنهائه.

الخطوة 6: إصلاح مشغّل الخدمات

بعد ذلك، أصلح عامل الخدمات عن طريق إلغاء اعتماده على الحالة المؤقتة. يُرجى تعديل الإضافة الاختبارية لاستخدام الرمز التالي المخزّن في service-worker-fixed.js في المستودع.

service-worker-fixed.js:

chrome.runtime.onInstalled.addListener(() => {
  chrome.storage.local.set({ version: chrome.runtime.getManifest().version });
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  chrome.storage.local.get('version').then((data) => {
    sendResponse(data.version);
  });
  return true;
});

ويتم هنا حفظ الإصدار على "chrome.storage.local" بدلاً من استخدام متغيّر عمومي للحفاظ على الحالة بين عمر مشغّل الخدمة. وبما أنّه لا يمكن الوصول إلى مساحة التخزين إلا بشكل غير متزامن، نعرض أيضًا القيمة "صحيح" من المستمع onMessage للتأكّد من بقاء عملية معاودة الاتصال في "sendResponse" نشطة.

الخطوة 7: إعادة تشغيل الاختبار

يُرجى إعادة تشغيل الاختبار باستخدام "npm start". من المفترض أن تجتاز المراجعة الآن.

الخطوات التالية

يمكنك الآن تطبيق الطريقة نفسها على إضافتك. ضع في اعتبارك ما يلي:

  • أنشئ حزمة الاختبار لدعم العمل مع أو بدون إنهاء غير متوقّع لعامل الخدمة. يمكنك بعد ذلك تشغيل كلا الوضعين على حدة لتوضيح سبب الفشل.
  • اكتب التعليمات البرمجية لإنهاء تشغيل مشغّل الخدمات في نقاط عشوائية داخل الاختبار. وقد تكون هذه طريقة جيدة لاكتشاف المشاكل التي قد يكون من الصعب توقّعها.
  • تعلَّم من حالات الإخفاق في الاختبارات وحاوِل الترميز بشكل دفاعي في المستقبل. على سبيل المثال، أضف قاعدة إشباع للثني عن استخدام المتغيرات العمومية وحاول نقل البيانات إلى حالة أكثر ثباتًا.