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

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

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

שכפול או הורדת המאגר של 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": "^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 ממאגר הדוגמאות. ה-handler של chrome.runtime.onMessage מסתמך על המצב שמוגדר ב-handler של האירוע chrome.runtime.onInstalled. כתוצאה מכך, התוכן של data יאבד כש-Service Worker יסתיים ולא תגיב להודעות עתידיות ייכשלו. נתקן זאת לאחר כתיבת הבדיקה.

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. פעולה זו פותחת את דף הבדיקה מתוסף הבדיקה שלנו, לוחץ על אלמנט הלחצן וממתינה לתשובה מ-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). מעדכנים את תוסף הבדיקה כך שישתמש בקוד הבא, שמאוחסן במאגר 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 במקום במשתנה גלובלי, כדי לשמור את המצב בין משך החיים של קובץ השירות (service worker). מכיוון שניתן לגשת לאחסון באופן אסינכרוני בלבד, אנחנו מחזירים גם את הערך true מה-listener של onMessage כדי לוודא שהקריאה החוזרת של sendResponse תישאר פעילה.

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

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

השלבים הבאים

כעת תוכל להחיל את אותה גישה על התוסף שלך. מומלץ לשקול את הדברים הבאים:

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