Skip to content

Commit

Permalink
fix: correctly uninstall the interception manager when disable() is c…
Browse files Browse the repository at this point in the history
…alled

fixes #12

adds tests and docs for the case of intercepting requests on new tabs
  • Loading branch information
niieani committed May 1, 2023
1 parent 2d181e4 commit f665809
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 48 deletions.
87 changes: 53 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ In summary, the `puppeteer-intercept-and-modify-requests` library offers a more
npm install puppeteer-intercept-and-modify-requests
```

## Usage
## Example Usage

To modify intercepted requests:

Expand All @@ -40,12 +40,18 @@ import { RequestInterceptionManager } from 'puppeteer-intercept-and-modify-reque

// assuming 'page' is your Puppeteer page object
const client = await page.target().createCDPSession()
// note: if you want to intercept requests on ALL tabs, instead use:
// const client = await browser.target().createCDPSession()

const interceptManager = new RequestInterceptionManager(client)

await interceptManager.intercept(
{
// specify the URL pattern to intercept:
urlPattern: `https://example.com/*`,
// optionally filter by resource type:
resourceType: 'Document',
// specify how you want to modify the response (may be async):
modifyResponse({ body }) {
return {
// replace break lines with horizontal lines:
Expand All @@ -55,6 +61,7 @@ await interceptManager.intercept(
},
{
urlPattern: '*/api/v4/user.json',
// specify how you want to modify the response (may be async):
modifyResponse({ body }) {
const parsed = JSON.parse(body)
// set role property to 'admin'
Expand Down Expand Up @@ -98,7 +105,7 @@ await interceptManager.intercept(

## Usage Examples

Here's an example of how to use the `RequestInterceptionManager` to intercept and modify a request:
Here's an example of how to use the `RequestInterceptionManager` to intercept and modify a request and a response:

```ts
import puppeteer from 'puppeteer'
Expand Down Expand Up @@ -141,9 +148,9 @@ This example modifies the request by adding a custom header and modifies the res

## Advanced Usage

### Streaming and Modifying Response Chunks
### Applying Delay

You can also stream and modify response chunks using the `streamResponse` and `modifyResponseChunk` options. Here's an example of how to do this:
You can apply a delay to a request or response using the `delay` property in the `modifyRequest` or `modifyResponse` functions. Here's an example of how to add a delay to a request:

```ts
import puppeteer from 'puppeteer'
Expand All @@ -161,11 +168,11 @@ async function main() {

const interceptionConfig: Interception = {
urlPattern: 'https://example.com/*',
streamResponse: true,
modifyResponseChunk: async ({ event, data }) => {
// Modify response chunk
const modifiedData = data.replace(/example/gi, 'intercepted')
return { ...event, data: modifiedData }
modifyRequest: async ({ event }) => {
// Add a 500 ms delay to the request
return {
delay: 500,
}
},
}

Expand All @@ -177,11 +184,11 @@ async function main() {
main()
```

In this example, the response is streamed and each response chunk has all occurrences of the word "example" replaced with "intercepted".
In this example, a 500 ms delay is added to the request for the specified URL pattern.

### Applying Delay
### Handling Errors

You can apply a delay to a request or response using the `delay` property in the `modifyRequest` or `modifyResponse` functions. Here's an example of how to add a delay to a request:
You can handle errors using the `onError` option when creating a new `RequestInterceptionManager` instance. Here's an example of how to handle errors:

```ts
import puppeteer from 'puppeteer'
Expand All @@ -195,14 +202,18 @@ async function main() {
const page = await browser.newPage()

const client = await page.target().createCDPSession()
const requestInterceptionManager = new RequestInterceptionManager(client)
const requestInterceptionManager = new RequestInterceptionManager(client, {
onError: (error) => {
console.error('Request interception error:', error)
},
})

const interceptionConfig: Interception = {
urlPattern: 'https://example.com/*',
modifyRequest: async ({ event }) => {
// Add a 500 ms delay to the request
// Modify request headers
return {
delay: 500,
headers: [{ name: 'X-Custom-Header', value: 'CustomValue' }],
}
},
}
Expand All @@ -215,11 +226,11 @@ async function main() {
main()
```

In this example, a 500 ms delay is added to the request for the specified URL pattern.
In this example, any errors that occur during request interception are logged to the console with the message "Request interception error:".

### Handling Errors
### Failing a Request

You can handle errors using the `onError` option when creating a new `RequestInterceptionManager` instance. Here's an example of how to handle errors:
To fail a request, return an object containing an `errorReason` property in the `modifyRequest` function. Here's an example of how to fail a request:

```ts
import puppeteer from 'puppeteer'
Expand All @@ -233,18 +244,14 @@ async function main() {
const page = await browser.newPage()

const client = await page.target().createCDPSession()
const requestInterceptionManager = new RequestInterceptionManager(client, {
onError: (error) => {
console.error('Request interception error:', error)
},
})
const requestInterceptionManager = new RequestInterceptionManager(client)

const interceptionConfig: Interception = {
urlPattern: 'https://example.com/*',
modifyRequest: async ({ event }) => {
// Modify request headers
// Fail the request with the error reason "BlockedByClient"
return {
headers: [{ name: 'X-Custom-Header', value: 'CustomValue' }],
errorReason: 'BlockedByClient',
}
},
}
Expand All @@ -257,11 +264,23 @@ async function main() {
main()
```

In this example, any errors that occur during request interception are logged to the console with the message "Request interception error:".
In this example, the request for the specified URL pattern is blocked with the error reason "BlockedByClient".

### Failing a Request
### Intercepting network requests from all Pages (rather than just the one)

To fail a request, return an object containing an `errorReason` property in the `modifyRequest` function. Here's an example of how to fail a request:
When creating the `RequestInterceptionManager` instance, you can pass in the `client` object from the `CDPSession` of the `Browser` object. This will allow you to intercept requests from all the pages rather than just the one. Here's an example of how to do this:

```ts
// intercept requests on ALL tabs, instead use:
const client = await browser.target().createCDPSession()
const interceptManager = new RequestInterceptionManager(client)

// ...
```

### Streaming and Modifying Response Chunks

You can also stream and modify response chunks using the `streamResponse` and `modifyResponseChunk` options. Here's an example of how to do this:

```ts
import puppeteer from 'puppeteer'
Expand All @@ -279,11 +298,11 @@ async function main() {

const interceptionConfig: Interception = {
urlPattern: 'https://example.com/*',
modifyRequest: async ({ event }) => {
// Fail the request with the error reason "BlockedByClient"
return {
errorReason: 'BlockedByClient',
}
streamResponse: true,
modifyResponseChunk: async ({ event, data }) => {
// Modify response chunk
const modifiedData = data.replace(/example/gi, 'intercepted')
return { ...event, data: modifiedData }
},
}

Expand All @@ -295,4 +314,4 @@ async function main() {
main()
```

In this example, the request for the specified URL pattern is blocked with the error reason "BlockedByClient".
In this example, the response is streamed and each response chunk has all occurrences of the word "example" replaced with "intercepted".
77 changes: 69 additions & 8 deletions src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ let server: Server
let browser: puppeteerType.Browser
let page: puppeteerType.Page
let client: puppeteerType.CDPSession
let browserClient: puppeteerType.CDPSession
let manager: RequestInterceptionManager

const host = 'localhost'
let port = 3_000
Expand All @@ -37,6 +39,7 @@ describe('RequestInterceptionManager', () => {

beforeAll(async () => {
browser = await puppeteer.launch()
browserClient = await browser.target().createCDPSession()
})

afterAll(async () => {
Expand All @@ -51,6 +54,7 @@ describe('RequestInterceptionManager', () => {
afterEach(async () => {
await page.close()
await stopServer()
await manager.clear()
})

describe.each`
Expand All @@ -66,7 +70,7 @@ describe('RequestInterceptionManager', () => {
res.end('Hello, world!')
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*',
modifyResponse: ({ body }) => ({
Expand Down Expand Up @@ -101,7 +105,7 @@ describe('RequestInterceptionManager', () => {
}
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*/original',
modifyRequest: ({ event }) => ({
Expand All @@ -128,7 +132,7 @@ describe('RequestInterceptionManager', () => {
res.end('Hello, world!')
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: 'non-existent-url/*',
modifyResponse: ({ body }) => ({
Expand All @@ -155,7 +159,7 @@ describe('RequestInterceptionManager', () => {
res.end()
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*',
modifyResponse: () => ({
Expand Down Expand Up @@ -190,7 +194,7 @@ describe('RequestInterceptionManager', () => {
}
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*/redirected',
modifyResponse: ({ body, event: { responseStatusCode } }) => ({
Expand Down Expand Up @@ -238,7 +242,7 @@ describe('RequestInterceptionManager', () => {
res.end('It is forbidden')
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*',
modifyResponse: ({ body }) => ({
Expand Down Expand Up @@ -278,7 +282,7 @@ describe('RequestInterceptionManager', () => {
}
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept(
{
urlPattern: '*/first',
Expand Down Expand Up @@ -313,6 +317,63 @@ describe('RequestInterceptionManager', () => {
expect(firstResponse).toBe('First intercepted')
expect(secondResponse).toBe('Second intercepted')
})

it('should intercept and modify requests on new tabs', async () => {
await startServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end('<a href="/new-tab" target="_blank">Open new tab</a>')
} else if (req.url === '/new-tab') {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello, new tab!')
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' })
res.end('Not found')
}
})

// Set up request interception for the initial page
manager = new RequestInterceptionManager(browserClient)
await manager.intercept({
urlPattern: '*',
modifyResponse: ({ body }) =>
body
? {
body: body.replace('new tab', 'new tab intercepted'),
}
: undefined,
...options,
})

await page.goto(`http://localhost:${port}`)

// Listen for a new page to be opened
const newPagePromise = browser
.waitForTarget(
(target) =>
target.type() === 'page' &&
target.url() === `http://localhost:${port}/new-tab`,
)
.then((target) => target.page())
.then((newPage) => newPage!)

// Click the link to open a new tab
await page.click('a')

// Wait for the new page to be opened
const newPage = await newPagePromise

// Check if the request on the new tab was intercepted and modified
const newText = await newPage.evaluate(() => document.body.textContent)
expect(newText).toBe('Hello, new tab intercepted!')

const newResponse = await newPage.evaluate(async (url) => {
const response = await fetch(url)
return response.text()
}, `http://localhost:${port}/new-tab`)

expect(newResponse).toBe('Hello, new tab intercepted!')
})
},
)

Expand Down Expand Up @@ -349,7 +410,7 @@ describe('RequestInterceptionManager', () => {
sendNextMessage()
})

const manager = new RequestInterceptionManager(client)
manager = new RequestInterceptionManager(client)
await manager.intercept({
urlPattern: '*/stream',
// Replace "world" with "Jest" in the response chunk
Expand Down
Loading

0 comments on commit f665809

Please sign in to comment.