בדיקת סיום של קובץ שירות (service worker) באמצעות Puppeteer

במדריך הזה מוסבר איך לבנות תוספים חזקים יותר על ידי בדיקת סיום של service worker באמצעות Puppeteer. חשוב להיות מוכנים להתמודד עם סיום הפעולה בכל שלב, כי זה יכול לקרות בלי אזהרה מוקדמת, וכתוצאה מכך כל מצב לא קבוע ב-service worker יאבד. לכן, התוספים צריכים לשמור מצב חשוב ולטפל בבקשות ברגע שהם מופעלים מחדש, כשמתרחש אירוע שצריך לטפל בו.

לפני שמתחילים

משכפלים או מורידים את מאגר chrome-extensions-samples. נשתמש בתוסף הבדיקה ב-/functional-samples/tutorial.terminate-sw/test-extension ששולח הודעה ל-service worker בכל פעם שלוחצים על לחצן, ומוסיף טקסט לדף אם מתקבלת תגובה.

צריך גם להתקין את 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": "^24.8.1"
  },
  "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,
    pipe: true,
    enableExtensions: [EXTENSION_PATH]
  });
});

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

שימו לב שהבדיקה שלנו טוענת את test-extension ממאגר הדוגמאות. הגורם המטפל באירוע browser.runtime.onMessage מסתמך על מצב שהוגדר בגורם המטפל באירוע browser.runtime.onInstalled. כתוצאה מכך, התוכן של data יאבד כשה-service worker יופסק, ולא תהיה אפשרות להגיב להודעות עתידיות. נפתור את הבעיה הזו אחרי שנכתוב את הבדיקה.

service-worker-broken.js:

let data;

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

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

שלב 2: התקנת יחסי תלות

מריצים את הפקודה npm install כדי להתקין את יחסי התלות הנדרשים.

שלב 3: כותבים בדיקה בסיסית

מוסיפים את הבדיקה הבאה לתחתית הקובץ index.test.js. הפעולה הזו פותחת את דף הבדיקה מתוך תוסף הבדיקה שלנו, לוחצת על רכיב הלחצן וממתינה לתגובה מ-service worker.

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: סיום הפעולה של Service Worker

מוסיפים את פונקציית העזר הבאה שמסיימת את פעולת ה-service worker:

/**
 * 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();
}

לבסוף, מעדכנים את הבדיקה באמצעות הקוד הבא. עכשיו מסיימים את פעולת ה-service worker ולוחצים שוב על הכפתור כדי לבדוק שקיבלתם תשובה.

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. הבדיקה אמורה להיכשל, מה שמצביע על כך ש-service worker לא הגיב אחרי שהוא הופסק.

שלב 6: תיקון של Service Worker

בשלב הבא, מתקנים את ה-service worker על ידי הסרת ההסתמכות שלו על מצב זמני. מעדכנים את התוסף test-extension כך שישתמש בקוד הבא, שמאוחסן ב-service-worker-fixed.js במאגר.

service-worker-fixed.js:

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

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

כאן אנחנו שומרים את הגרסה ב-browser.storage.local במקום במשתנה גלובלי, כדי לשמור את המצב בין משכי החיים של Service Worker. מכיוון שאפשר לגשת לאחסון רק באופן אסינכרוני, אנחנו מחזירים גם את הערך true מה-listener‏ onMessage כדי לוודא שהקריאה החוזרת sendResponse תישאר פעילה.

שלב 7: מריצים שוב את הבדיקה

מריצים את הבדיקה שוב עם npm start. היא אמורה לעבור עכשיו.

השלבים הבאים

עכשיו אפשר ליישם את אותה הגישה בתוסף שלכם. כמה נקודות שכדאי לזכור:

  • כדאי ליצור חבילת מקרים לבדיקה שתתמוך בהרצה עם או בלי סיום לא צפוי של קובץ שירות (service worker). אחר כך תוכלו להריץ כל מצב בנפרד כדי להבין טוב יותר מה גרם לכשל.
  • כתיבת קוד להפסקת פעולת ה-Service Worker בנקודות אקראיות בבדיקה. זו יכולה להיות דרך טובה לגלות בעיות שקשה לחזות.
  • לומדים מכישלונות בבדיקות ומנסים לכתוב קוד בצורה מגינה בעתיד. לדוגמה, אפשר להוסיף כלל של בדיקת קוד כדי להניא משימוש במשתנים גלובליים ולנסות להעביר נתונים למצב עקבי יותר.