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

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

قبل البدء

يمكنك استنساخ مستودع chrome-extensions-samples أو تنزيله. سنستخدم الإضافة التجريبية في /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 بدلاً من متغير عمومي للحفاظ على الحالة بين عمر مشغّل الخدمة. ونظرًا لأن التخزين لا يمكن يتم الوصول إليه بشكل غير متزامن، فإننا أيضًا نُرجع القيمة true من مستمع onMessage إلى لضمان بقاء معاودة الاتصال sendResponse نشطة.

الخطوة 7: إعادة إجراء الاختبار

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

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

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

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