ทดสอบการสิ้นสุดการทำงานของ 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

สร้างไฟล์ต่อไปนี้ในไดเรกทอรีใหม่ ทั้ง 2 อย่างนี้จะสร้างโปรเจ็กต์ 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 โดยนำการอิงตามสถานะชั่วคราวออก อัปเดต ส่วนขยายการทดสอบเพื่อใช้โค้ดต่อไปนี้ ซึ่งจัดเก็บไว้ใน 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 เนื่องจากเข้าถึงพื้นที่เก็บข้อมูลได้แบบไม่พร้อมกันเท่านั้น เราจึงส่งคืนค่าเป็นจริงจาก Listener onMessage เพื่อให้แน่ใจว่า Callback sendResponse จะยังคงทำงานอยู่

ขั้นตอนที่ 7: ทำการทดสอบอีกครั้ง

ทำการทดสอบอีกครั้งด้วย npm start ตอนนี้ควรจะผ่านแล้ว

ขั้นตอนถัดไป

ตอนนี้คุณสามารถใช้วิธีเดียวกันนี้กับส่วนขยายของคุณเองได้แล้ว ลองพิจารณาสิ่งต่อไปนี้

  • สร้างชุดการทดสอบเพื่อรองรับการเรียกใช้ที่มีหรือไม่มีการสิ้นสุด Service Worker ที่ไม่คาดคิด จากนั้นคุณจะเรียกใช้ทั้ง 2 โหมดแยกกันเพื่อให้เห็นได้ชัดเจนยิ่งขึ้น ว่าอะไรเป็นสาเหตุที่ทำให้เกิดข้อผิดพลาด
  • เขียนโค้ดเพื่อสิ้นสุดการทำงานของ Service Worker ในจุดต่างๆ แบบสุ่มในการทดสอบ ซึ่งอาจเป็นวิธีที่ดีในการค้นพบปัญหาที่คาดเดาได้ยาก
  • เรียนรู้จากข้อผิดพลาดในการทดสอบและพยายามเขียนโค้ดอย่างระมัดระวังในอนาคต เช่น เพิ่มกฎการตรวจสอบโค้ดเพื่อไม่ให้ใช้ตัวแปรส่วนกลางและพยายาม ย้ายข้อมูลไปยังสถานะที่คงอยู่มากขึ้น