Skip to content

Commit

Permalink
improved exception handling, tests, examples and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobias committed Dec 15, 2017
1 parent 28e7f13 commit 7206cdd
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 141 deletions.
14 changes: 0 additions & 14 deletions .idea/codeStyleSettings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 84 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# puppeteer-request-spy

The RequestInterceptor was build and tested with minimatch but minimatch is not included in the package. This allows the matching function to be replaced with custom matchers.
> With puppeteer-request-spy you can easily watch or block requests from puppeteer matching patterns.
- allows you to write tests verifying specific resources getting requested as expected
- allows you to exclude unneeded requests from tests, speeding them up significantly
- avoids conflicts resulting from already aborted / continued requests

## Install

Expand All @@ -10,84 +14,127 @@ npm install puppeteer-request-spy
## Usage

create a new RequestInterceptor with a matching function and an optional logger.
### KeywordMatcher
First create a new `RequestInterceptor` with a `matcher` function and an optional logger.
```js
requestInterceptor = new RequestInterceptor(minimatch, console);
function KeywordMatcher(testee, keyword) {
return testee.indexOf(keyword) > -1;
}

let requestInterceptor = new RequestInterceptor(KeywordMatcher, console);
```
create a new RequestSpy with a pattern to be matched agains all requests.
Next create a new `RequestSpy` with a `pattern` to be matched against all requests.
```js
pngSpy = new RequestSpy('**/*.png');
let imageSpy = new RequestSpy('/pictures');
```
The Spy needs to be registered with the RequestInterceptor.
The `RequestSpy` needs to be registered with the `RequestInterceptor`.
```js
requestInterceptor.addSpy(pngSpy);
requestInterceptor.addSpy(imageSpy);
```
optionally you can add patterns to block requests.
```js
requestInterceptor.block('!https://www.example.com');
```
The RequestInterceptor must be registered with puppeteer and requestInterception must be set to true.
The `RequestInterceptor` must be registered with puppeteer.

```js
page.setRequestInterception(true);
page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
```
after puppeteers page navigates to any page, you can query the requestSpy.
After puppeteer's page object finished navigating to any page, you can query the `RequestSpy`.
```js
await page.goto('https://www.example.com');
assert.ok(imageSpy.hasMatch() && imageSpy.getMatchCount() > 0);
```
### blocking requests
Optionally you can add `patterns` to block requests. When blocking requests puppeteer's requestInterception flag must be set to true or puppeteer will throw an exception. For further information check the official [puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagesetrequestinterceptionvalue). Blocked requests will still be counted by a `RequestSpy` with a matching pattern.
```js
requestInterceptor.block(['scripts', 'track', '.png']);
await page.setRequestInterception(true);
```
Note
> Since unhandled Promise rejections causes the node process to keep running after test failure, the `RequestInterceptor` will catch and log puppeteer's exception.
### minimatch
puppeteer-request-spy works great with [minimatch](https://github.com/isaacs/minimatch), it can be passed as the `matcher` function.
```js
const minimatch = require('minimatch');

let pngSpy = new RequestSpy('**/*.png');

let requestInterceptor = new RequestInterceptor(minimatch);
requestInterceptor.addSpy(pngSpy);
requestInterceptor.block('!https://www.example.com');

await page.setRequestInterception(true);
page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
await page.goto('https://www.example.com');

assert.ok(pngSpy.hasMatch() && pngSpy.getMatchCount() > 0);
```
## API

### class: RequestInterceptor

### class: RequestInterceptor
The `RequestInterceptor` will match any intercepted request against the `matcher` function and notify all spies with a matching pattern and block requests matching any pattern in `urlsToBlock`.
#### RequestInterceptor constructor(matcher, logger?)
- matcher: <(url: string, pattern: string) => boolean>>
- logger?: <{log: (text: string) => void}> optional logger
- `matcher`: <(url: string, pattern: string) => boolean>>
- `logger?`: <{log: (text: string) => void}>

The matcher will be called on any requested url and has to return either true or false. The matcher will be used for testing urls agains patterns of any spies provided and also any url set to be blocked.
The `matcher` will be called for every url, testing the url against patterns of any `RequestSpy` provided and also any url added to `urlsToBlock`.

The Logger if provided will output any all requested urls with a 'loaded' or 'aborted' prefix.
The `logger` if provided will output any requested url with a 'loaded' or 'aborted' prefix and any exception caused by puppeteer's abort and continue functions.
#### RequestInterceptor.intercept(interceptedUrl)
- interceptedUrl: <Request> interceptedUrl provided by puppeteer's on 'request' event
- interceptedUrl: <Request> interceptedUrl provided by puppeteer's 'request' event

function to register with puppeteer.
Function to be registered with puppeteer's request event.

#### RequestInterceptor.addSpy(requestSpy)
- requestSpy: \<RequestSpy> spy to register.
- requestSpy: \<RequestSpy> spy to register

register a spy with the RequestInterceptor
Register a spy with the `RequestInterceptor`.

#### RequestInterceptor.clearSpies()
clears all registered spies.
Clears all registered spies.

#### RequestInterceptor.block(urlsToBlock)
- urlsToBlock: <Array<[string]> | <[string]>> urls to be blocked if matched
- urlsToBlock: \<Array\<string\> | \<string\>\> urls to be blocked if matched

block will always add urls to the list of urls to be blocked. Passed arrays will be merged with the list.

#### RequestInterceptor.clearUrlsToBlock()
clears all registered urls to be blocked.
`block` will always add urls to the list `urlsToBlock`. Passed arrays will be merged with `urlsToBlock`.

#### RequestInterceptor.setUrlsToBlock(urlsToBlock)
- urlsToBlock: <Array\<string>> setter for urlsToBlock
- urlsToBlock: <Array\<string>> setter for `urlsToBlock`

### class: RequestSpy
#### RequestInterceptor.clearUrlsToBlock()
Clears all registered patterns in `urlsToBlock`.

### class: RequestSpy
`RequestSpy` is used to count and verify intercepted requests matching a specific pattern.
#### RequestSpy constructor(pattern)
- pattern: \<string|Array<string>> returns whether any url matched the pattern
- pattern: \<string|Array<string>>

`pattern` passed to the `matcher` function of the `RequestInterceptor`.

#### RequestSpy.hasMatch()
- returns: \<boolean> returns whether any url matched the pattern
- returns: \<boolean> returns whether any url matched the `pattern`

#### RequestSpy.getMatchedUrls()
- returns: \<Array\<string\>\> returns a list of urls that matched the pattern
- returns: \<Array\<string\>\> returns a list of urls that matched the `pattern`

#### RequestSpy.getMatchCount()
- returns: \<number> number of urls that matched the pattern
- returns: \<number> number of urls that matched the `pattern`

#### RequestSpy.getPatterns()
- returns: \<Array\<string\>\> return the pattern list of the spy

#### RequestSpy.addMatchedUrl(matchedUrl)
- matchedUrl: \<string> url that was matched

The RequestInterceptor calls this method when an interceptedUrl matches the pattern.
The `RequestInterceptor` calls this method when an interceptedUrl matches the pattern.

# Examples

There are some simple usage examples included in the [github repository](https://github.com/Tabueeee/puppeteer-request-spy/tree/master/examples). Check them out to get started with writing a simple test with puppeteer and puppeteer-request-spy.

# Related
- [minimatch](https://github.com/isaacs/minimatch) - For easily matching path-like strings to patterns.
- [puppeteer](https://github.com/GoogleChrome/puppeteer) - Control chrome in headless mode with puppeteer for automated testing.

# License
MIT
18 changes: 9 additions & 9 deletions examples/block-test.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const puppeteer = require('puppeteer');
const RequestInterceptor = require('..').RequestInterceptor;
const RequestSpy = require('..').RequestSpy;
const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
const RequestSpy = require('puppeteer-request-spy').RequestSpy;
const minimatch = require('minimatch');
const assert = require('assert');

let browser;
Expand All @@ -16,26 +17,25 @@ after(async () => {
});

describe('example-block', function () {

this.timeout(30000);
this.slow(10000);

let requestInterceptor;
let secondaryRequestSpy;

before(() => {
function matcher(testee, pattern) {
return testee !== pattern;
}
requestInterceptor = new RequestInterceptor(minimatch, console);
secondaryRequestSpy = new RequestSpy('!https://www.example.org/');

requestInterceptor = new RequestInterceptor(matcher, console);
secondaryRequestSpy = new RequestSpy('https://www.example.org/');
requestInterceptor.addSpy(secondaryRequestSpy);
requestInterceptor.block('https://www.example.org/');
requestInterceptor.block('!https://www.example.org/');
});

describe('example-block', function () {

it('example-test', async function () {
let page = await browser.newPage();

page.setRequestInterception(true);
page.on('request', requestInterceptor.intercept.bind(requestInterceptor));

Expand Down
50 changes: 50 additions & 0 deletions examples/keyword-matcher.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const puppeteer = require('puppeteer');
const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
const RequestSpy = require('puppeteer-request-spy').RequestSpy;
const assert = require('assert');

let browser;

before(async () => {
browser = await puppeteer.launch({
headless: true
});
});

after(async () => {
await browser.close();
});

describe('example-block', function () {

this.timeout(30000);
this.slow(20000);

let requestInterceptor;
let imagesSpy;

before(() => {
imagesSpy = new RequestSpy('images');
requestInterceptor = new RequestInterceptor(
(testee, pattern) => testee.indexOf(pattern) > -1,
console
);

requestInterceptor.addSpy(imagesSpy);
});

describe('example-block', function () {
it('example-test', async function () {
let page = await browser.newPage();

page.on('request', requestInterceptor.intercept.bind(requestInterceptor));

await page.goto('https://www.example.org/', {
waitUntil: 'networkidle0',
timeout: 3000000
});

assert.ok(imagesSpy.hasMatch() && imagesSpy.getMatchCount() > 0);
});
});
});
9 changes: 6 additions & 3 deletions examples/simple-test.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const puppeteer = require('puppeteer');
const RequestInterceptor = require('..').RequestInterceptor;
const RequestSpy = require('..').RequestSpy;
const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
const RequestSpy = require('puppeteer-request-spy').RequestSpy;
const minimatch = require('minimatch');
const assert = require('assert');

Expand All @@ -17,22 +17,25 @@ after(async () => {
});

describe('example-block', function () {

this.timeout(30000);
this.slow(10000);

let requestInterceptor;
let pngSpy;

before(() => {
requestInterceptor = new RequestInterceptor(minimatch, console);
pngSpy = new RequestSpy('**/*.png');

requestInterceptor.addSpy(pngSpy);
requestInterceptor.block('!https://www.example.org/');
});

describe('example-block', function () {

it('example-test', async function () {
let page = await browser.newPage();

page.setRequestInterception(true);
page.on('request', requestInterceptor.intercept.bind(requestInterceptor));

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "puppeteer-request-spy",
"version": "1.0.5",
"description": "simple request spy helper for testing with puppeteer",
"version": "1.0.6",
"description": "watch or block requests from puppeteer matching patterns",
"main": "build/src/index.js",
"scripts": {
"prebuild": "tslint --project tsconfig-lint.json",
Expand Down
20 changes: 11 additions & 9 deletions src/RequestInterceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,24 @@ export class RequestInterceptor {
}

private async blockUrl(interceptedUrl: Request): Promise<void> {
this.logger.log(`aborted: ${interceptedUrl.url}`);

try {
await interceptedUrl.abort();
this.logger.log(`aborted: ${interceptedUrl.url}`);
} catch (error) {
console.error(error);
this.logger.log((<Error> error).toString());
}
}

private async acceptUrl(interceptedUrl: Request): Promise<void> {
this.logger.log(`loaded: ${interceptedUrl.url}`);

try {
await interceptedUrl.continue();
} catch (error) {
console.error(error);
if (this.urlsToBlock.length > 0) {
try {
await interceptedUrl.continue();
this.logger.log(`loaded: ${interceptedUrl.url}`);
} catch (error) {
this.logger.log((<Error> error).toString());
}
} else {
this.logger.log(`loaded: ${interceptedUrl.url}`);
}
}
}
12 changes: 7 additions & 5 deletions src/RequestSpy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ export class RequestSpy {
private patterns: Array<string> = [];

public constructor(patterns: Array<string> | string) {
if (typeof patterns === 'string') {
this.patterns = [patterns];
} else if (patterns.constructor === Array) {
this.patterns = patterns;
} else {
if (typeof patterns !== 'string' && patterns.constructor !== Array) {
throw new Error('invalid pattern, pattern must be of type string or string[].');
}

if (typeof patterns === 'string') {
patterns = [patterns];
}

this.patterns = patterns;
}

public hasMatch(): boolean {
Expand Down
Loading

0 comments on commit 7206cdd

Please sign in to comment.