Puppeteer를 사용하여 서비스 워커 종료 테스트

이 가이드에서는 Puppeteer를 사용하여 서비스 워커 종료를 테스트하여 더 강력한 확장 프로그램을 빌드하는 방법을 설명합니다. 경고 없이 종료가 발생하여 서비스 워커의 비영구적 상태가 손실될 수 있으므로 언제든지 종료를 처리할 준비가 되어 있어야 합니다. 따라서 확장 프로그램은 중요한 상태를 저장하고 처리할 이벤트가 있을 때 다시 시작되는 즉시 요청을 처리할 수 있어야 합니다.

시작하기 전에

chrome-extensions-samples 저장소를 복제하거나 다운로드합니다. 버튼을 클릭할 때마다 서비스 워커에 메시지를 전송하고 응답이 수신되면 페이지에 텍스트를 추가하는 /functional-samples/tutorial.terminate-sw/test-extension의 테스트 확장 프로그램을 사용합니다.

또한 Puppeteer가 빌드된 런타임인 Node.JS도 설치해야 합니다.

1단계: Node.js 프로젝트 시작

새 디렉터리에 다음 파일을 만듭니다. 이들은 함께 새로운 Node.js 프로젝트를 만들고 Jest를 테스트 실행기로 사용하여 Puppeteer 테스트 모음의 기본 구조를 제공합니다. 이 설정에 관한 자세한 내용은 Puppeteer로 Chrome 확장 프로그램 테스트를 참고하세요.

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 리스너에서도 true를 반환하여 sendResponse 콜백이 활성 상태로 유지되도록 합니다.

7단계: 테스트 다시 실행

npm start를 사용하여 테스트를 다시 실행합니다. 이제 통과됩니다.

다음 단계

이제 동일한 접근 방식을 자체 확장 프로그램에 적용할 수 있습니다. 다음 사항을 고려하세요.

  • 예기치 않은 서비스 워커 종료 여부와 관계없이 실행을 지원하도록 테스트 모음을 빌드합니다. 그런 다음 두 모드를 개별적으로 실행하여 장애의 원인을 명확히 할 수 있습니다.
  • 테스트 내의 임의의 지점에서 서비스 워커를 종료하는 코드를 작성합니다. 예측하기 어려운 문제를 발견하는 좋은 방법입니다.
  • 테스트 실패로부터 배우고 향후에는 방어적으로 코딩해 보세요. 예를 들어 전역 변수 사용을 지양하고 데이터를 보다 영구적인 상태로 전환하기 위해 린트 규칙을 추가할 수 있습니다.