Files
apify--crawlee/test/core/puppeteer_request_interception.test.ts
2026-07-13 13:23:39 +08:00

262 lines
10 KiB
TypeScript

import type { Server } from 'node:http';
import { sleep } from '@crawlee/utils';
import { launchPuppeteer, utils } from 'crawlee';
import type { HTTPRequest } from 'puppeteer';
import { runExampleComServer } from '../shared/_helper';
const { addInterceptRequestHandler, removeInterceptRequestHandler } = utils.puppeteer;
let serverAddress = 'http://localhost:';
let port: number;
let server: Server;
beforeAll(async () => {
[server, port] = await runExampleComServer();
serverAddress += port;
});
afterAll(() => {
server.close();
});
describe('utils.puppeteer.addInterceptRequestHandler|removeInterceptRequestHandler()', () => {
test('should allow multiple handlers', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
const allUrls: string[] = [];
const loadedUrls: string[] = [];
try {
const page = await browser.newPage();
// Just collect all URLs.
await addInterceptRequestHandler(page, async (request) => {
allUrls.push(request.url());
return request.continue();
});
// Abort images.
await addInterceptRequestHandler(page, async (request) => {
if (request.resourceType() === 'image') return request.abort();
return request.continue();
});
// Abort scripts.
await addInterceptRequestHandler(page, async (request) => {
if (request.resourceType() === 'script') return request.abort();
return request.continue();
});
// Save all loaded URLs.
page.on('response', (response) => loadedUrls.push(response.url()));
await page.goto(`${serverAddress}/special/resources`, { waitUntil: 'networkidle0' });
} finally {
await browser.close();
}
expect(allUrls).toEqual(
expect.arrayContaining([
`${serverAddress}/script.js`,
`${serverAddress}/style.css`,
`${serverAddress}/image.png`,
]),
);
expect(loadedUrls).toEqual(expect.arrayContaining([`${serverAddress}/style.css`]));
});
test('should not propagate aborted/responded requests to following handlers', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
const propagatedUrls: string[] = [];
try {
const page = await browser.newPage();
// Abort images.
await addInterceptRequestHandler(page, async (request) => {
if (request.resourceType() === 'image') return request.abort();
return request.continue();
});
// Respond scripts.
await addInterceptRequestHandler(page, async (request) => {
if (request.resourceType() === 'script') {
return request.respond({
status: 404,
});
}
return request.continue();
});
// Just collect all URLs propagated to the last handler.
await addInterceptRequestHandler(page, async (request) => {
propagatedUrls.push(request.url());
return request.continue();
});
await page.goto(`${serverAddress}/special/resources`);
} finally {
await browser.close();
}
expect(propagatedUrls).toEqual(expect.arrayContaining([`${serverAddress}/style.css`]));
});
test('should allow to modify request', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
try {
const page = await browser.newPage();
// Change all requests to DELETE.
await addInterceptRequestHandler(page, async (request) => {
return request.continue({ method: 'DELETE' });
});
// Add some query parameters to request URLs.
await addInterceptRequestHandler(page, async (request) => {
return request.continue({
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
postData: '{ "foo": "bar" }',
});
});
// Change all requests to POST and add payload.
await addInterceptRequestHandler(page, async (request) => {
return request.continue({
method: 'POST',
});
});
// Check response that it's correct.
const response = await page.goto(`${serverAddress}/special/getDebug`, { waitUntil: 'networkidle0' });
const { method, headers, bodyLength } = JSON.parse(await response!.text());
expect(method).toBe('POST');
expect(bodyLength).toBe(16);
expect(headers['content-type']).toBe('application/json; charset=utf-8');
} finally {
await browser.close();
}
});
test('should allow async handler', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
try {
const page = await browser.newPage();
await addInterceptRequestHandler(page, async (request) => {
await sleep(100);
return request.continue({
method: 'POST',
});
});
// Check response that it's correct.
const response = await page.goto(`${serverAddress}/special/getDebug`, { waitUntil: 'networkidle0' });
const { method } = JSON.parse(await response!.text());
expect(method).toBe('POST');
} finally {
await browser.close();
}
});
describe('internal handleRequest function should return correctly formatted headers', () => {
test('should correctly capitalize headers', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
try {
const page = await browser.newPage();
await addInterceptRequestHandler(page, async (request) => {
// Override headers
const headers = {
...request.headers(),
'accept': 'text/html',
'accept-language': 'en-GB',
'upgrade-insecure-requests': '2',
};
return request.continue({ headers });
});
const response = await page.goto(`${serverAddress}/special/getRawHeaders`);
const rawHeadersArr = JSON.parse(await response!.text()) as string[];
const acceptIndex = rawHeadersArr.findIndex((headerItem) => headerItem === 'Accept');
expect(typeof acceptIndex).toBe('number');
expect(rawHeadersArr[acceptIndex + 1]).toEqual('text/html');
const acceptLanguageIndex = rawHeadersArr.findIndex((headerItem) => headerItem === 'Accept-Language');
expect(typeof acceptLanguageIndex).toBe('number');
expect(rawHeadersArr[acceptLanguageIndex + 1]).toEqual('en-GB');
const upgradeInsReqIndex = rawHeadersArr.findIndex(
(headerItem) => headerItem === 'Upgrade-Insecure-Requests',
);
expect(typeof upgradeInsReqIndex).toBe('number');
expect(rawHeadersArr[upgradeInsReqIndex + 1]).toEqual('2');
// defaults should be capitalized too
const connectionIndex = rawHeadersArr.findIndex((headerItem) => headerItem === 'Connection');
expect(typeof connectionIndex).toBe('number');
expect(rawHeadersArr[connectionIndex + 1]).toEqual('keep-alive');
} finally {
await browser.close();
}
});
});
});
describe('utils.puppeteer.removeInterceptRequestHandler()', () => {
test('works', async () => {
const browser = await launchPuppeteer({ launchOptions: { headless: true } });
const loadedUrls: string[] = [];
try {
const page = await browser.newPage();
page.on('response', (response) => loadedUrls.push(response.url()));
// Abort images.
const abortImagesHandler = async (request: HTTPRequest) => {
if (request.resourceType() === 'image') return request.abort();
return request.continue();
};
await addInterceptRequestHandler(page, abortImagesHandler);
// Abort scripts.
await addInterceptRequestHandler(page, async (request) => {
if (request.resourceType() === 'script') return request.abort();
return request.continue();
});
// Load with scripts and images disabled.
await page.goto(`${serverAddress}/special/resources`, { waitUntil: 'networkidle0' });
expect(loadedUrls).toEqual(expect.arrayContaining([`${serverAddress}/style.css`]));
// Try it once again.
await page.goto(`${serverAddress}/special/resources`, { waitUntil: 'networkidle0' });
expect(loadedUrls).toEqual(
expect.arrayContaining([`${serverAddress}/style.css`, `${serverAddress}/style.css`]),
);
// Enable images.
await removeInterceptRequestHandler(page, abortImagesHandler);
// Try to load once again if images appear there.
await page.goto(`${serverAddress}/special/resources`, { waitUntil: 'networkidle0' });
expect(loadedUrls).toEqual(
expect.arrayContaining([
`${serverAddress}/style.css`,
`${serverAddress}/style.css`,
`${serverAddress}/style.css`,
`${serverAddress}/image.png`,
]),
);
} finally {
await browser.close();
}
});
});