通过本地主机上的 ServiceWorker 运行 拦截外部 URL 获取

Intercept external URL fetch via ServiceWorker running on localhost

我在 Karma 中有一组集成测试 运行。不幸的是,他们调用了一个外部生产 API 端点。我不想调用集成测试并正在探索我的选择。

我想知道服务工作者是否是一个可行的解决方案。我的假设是它们不起作用,因为 https://github.com/w3c/ServiceWorker/issues/1188 明确表示不支持跨域获取,并且本地主机与生产 API 端点不同。

为清楚起见,这里是一些代码,我是 运行ning:

  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 && (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 的请求未被获取处理程序拦截。

这种场景没有办法拦截是不是正确?

可以使用服务工作者来拦截浏览器发出的作为测试套件一部分的请求。只要 service worker 控制着网页,它就可以拦截跨源请求并生成您想要的任何响应。

(关于“foreign fetch”的 issue you link to 有所不同;将其视为部署自己的 service worker 的远程生产服务器。这已被放弃。)

Stop mocking fetch" is a comprehensive article covering how to use the msw 测试套件上下文中的服务工作者库。

我不能说出你当前设置不起作用的确切原因,但根据过去的经验,我可以说在执行此操作时要记住的最重要的事情是你需要延迟从客户端测试页面发出任何请求,直到页面本身被一个活跃的 service worker 控制。否则,会出现竞争条件,您可能最终会触发需要触发 fetch 处理程序的请求,但如果 service worker 尚未受到控制,则不会。

您可以等待这种情况发生,逻辑如下:

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 promise 解决与新激活的服务工作者之间可能存在时间间隔控制当前页面。您不希望这段时间导致不稳定的测试。