Перехватить извлечение внешнего URL-адреса с помощью ServiceWorker, работающего на локальном хосте

#javascript #karma-runner #service-worker

Вопрос:

У меня есть набор интеграционных тестов, которые выполняются в Карме. К сожалению, они обращаются к внешней конечной точке производственного API. Я не хочу, чтобы интеграционные тесты вызывали и изучали мои варианты.

Мне интересно, являются ли работники сферы услуг жизнеспособным решением. Я предполагаю, что они не работают, потому что https://github.com/w3c/ServiceWorker/issues/1188 дает понять, что извлечение из разных источников не поддерживается, и localhost не является тем же источником, что и конечная точка производственного API.

Для ясности, вот некоторый код, который я запускаю:

   try {
    const { scope, installing, waiting, active } = await navigator.serviceWorker.register('./base/htdocs/test/imageMock.sw.js');
    console.log('ServiceWorker registration successful with scope: ', scope, installing, waiting, active);


    (installing || waiting || active).addEventListener('statechange', (e) => {
      console.log('state', e.target.state);
    });
  } catch (error) {
    console.error('ServiceWorker registration failed: ', error);
  }
 

и работник сферы обслуживания

 // imageMock.sw.js

if (typeof self.skipWaiting === 'function') {
  console.log('self.skipWaiting() is supported.');
  self.addEventListener('install', (e) => {
    // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-skipwaiting
    e.waitUntil(self.skipWaiting());
  });
} else {
  console.log('self.skipWaiting() is not supported.');
}

if (self.clients amp;amp; (typeof self.clients.claim === 'function')) {
  console.log('self.clients.claim() is supported.');
  self.addEventListener('activate', (e) => {
    // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#clients-claim-method
    e.waitUntil(self.clients.claim());
  });
} else {
  console.log('self.clients.claim() is not supported.');
}

self.addEventListener('fetch', (event) => {
  console.log('fetching resource', event);
  if (/.jpg$/.test(event.request.url)) {
    const response = new Response('<p>This is a response that comes from your service worker!</p>', {
      headers: { 'Content-Type': 'text/html' },
    });

    event.respondWith(response);
  }
});
 

и когда этот код запускается, я вижу в консоли

 ServiceWorker registration successful with scope:  http://localhost:9876/base/htdocs/test/ null null ServiceWorker
 

и затем запросы https://<productionServer>.com/image.php не перехватываются обработчиком выборки.

Правильно ли, что в этом сценарии нет способа перехватить?

Ответ №1:

Вы можете использовать работника службы для перехвата запросов, сделанных браузером в рамках набора тестов. Пока сотрудник службы контролирует веб-страницу, он может перехватывать запросы разных источников и генерировать любой желаемый ответ.

(Проблема, на которую вы ссылаетесь по поводу «внешней выборки», — это нечто другое; думайте об этом как об удаленном производственном сервере, развертывающем своего собственного работника службы. Это было заброшено.)

«Прекратите издеваться над выборкой» — это всеобъемлющая статья, в которой рассказывается о том, как использовать библиотеку msw service worker в контексте набора тестов.

Я не могу сказать с уверенностью, почему ваша текущая настройка не работает, но по прошлому опыту я могу сказать, что самое важное, что нужно помнить при этом, — это то, что вам нужно отложить выполнение любых запросов с тестовой страницы клиента до тех пор, пока сама страница не будет контролироваться активным сотрудником службы. В противном случае существует состояние гонки, при котором вы можете в конечном итоге отправить запрос, который должен вызвать fetch обработчик, но не будет выполнен, если работник службы еще не контролирует ситуацию.

Вы можете подождать, пока это произойдет с помощью такой логики, как:

 const controlledPromise = new Promise((resolve) => {
  // Resolve right away if this page is already controlled.
  if (navigator.serviceWorker.controller) {
    resolve();
  } else {
    navigator.serviceWorker.addEventListener('controllerchange', () => {
      resolve();
    });
  }
});

await controlledPromise;
// At this point, the page will be controlled by a service worker.
// You can start making requests at this point.
 

Обратите внимание, что в этом случае использования await navigator.serviceWorker.ready вы не будете вести себя так, как вам нужно, так как может возникнуть промежуток времени между тем, когда navigator.serviceWorker.ready обещание будет выполнено, и тем, когда недавно активированный сотрудник службы возьмет под контроль текущую страницу. Вы не хотите, чтобы этот промежуток времени привел к ошибочным тестам.