├── .npmrc
├── test
├── integration
│ └── fakes
│ │ ├── remote.html
│ │ ├── redirected.html
│ │ ├── style.css
│ │ ├── index.html
│ │ ├── script.js
│ │ ├── ajaxWithPost.html
│ │ ├── ajaxWithUnavailableRequest.html
│ │ └── ajaxWithCustomHeaders.html
├── common
│ ├── TestDouble.ts
│ ├── ServerSettings.ts
│ ├── CustomRequestBlocker.ts
│ ├── ServerDouble.ts
│ ├── CustomFaker.ts
│ ├── AssertionHelpers.ts
│ ├── Browser.ts
│ ├── filterForbiddenHeaders.ts
│ ├── testServer.ts
│ └── testDoubleFactories.ts
├── isolation
│ ├── UrlAccessorResolver.dev.spec.ts
│ ├── ResponseFaker.dev.spec.ts
│ ├── RequestModifier.dev.spec.ts
│ ├── RequestSpy.dev.spec.ts
│ ├── RequestRedirector.dev.spec.ts
│ ├── ResponseModifier.dev.spec.ts
│ └── HttpRequestFactory.dev.spec.ts
├── unit
│ ├── unitTestHelpers.ts
│ └── puppeteer-request-spy.dev.spec.ts
└── regression
│ └── unit.spec.ts
├── custom-typings
└── clear-module
│ └── index.d.ts
├── src
├── common
│ ├── Logger.ts
│ ├── urlAccessor
│ │ ├── UrlAccessor.ts
│ │ ├── UrlFunctionAccessor.ts
│ │ ├── UrlStringAccessor.ts
│ │ └── UrlAccessorResolver.ts
│ ├── VoidLogger.ts
│ ├── resolveOptionalPromise.ts
│ ├── interfaceValidators
│ │ ├── instanceOfRequestSpy.ts
│ │ ├── instanceOfResponseFaker.ts
│ │ ├── instanceOfRequestModifier.ts
│ │ └── instanceOfRequestBlocker.ts
│ └── HttpRequestFactory.ts
├── types
│ ├── RequestMatcher.ts
│ └── ResponseModifierCallBack.ts
├── interface
│ ├── IRequestFactory.ts
│ ├── IRequestSpy.ts
│ ├── IRequestBlocker.ts
│ ├── IRequestModifier.ts
│ └── IResponseFaker.ts
├── RequestBlocker.ts
├── index.ts
├── ResponseFaker.ts
├── RequestModifier.ts
├── RequestRedirector.ts
├── RequestSpy.ts
├── ResponseModifier.ts
└── RequestInterceptor.ts
├── types
├── common
│ ├── Logger.d.ts
│ ├── VoidLogger.d.ts
│ ├── resolveOptionalPromise.d.ts
│ ├── interfaceValidators
│ │ ├── instanceOfRequestSpy.d.ts
│ │ ├── instanceOfResponseFaker.d.ts
│ │ ├── instanceOfRequestBlocker.d.ts
│ │ └── instanceOfRequestModifier.d.ts
│ ├── urlAccessor
│ │ ├── UrlAccessor.d.ts
│ │ ├── UrlStringAccessor.d.ts
│ │ ├── UrlAccessorResolver.d.ts
│ │ └── UrlFunctionAccessor.d.ts
│ └── HttpRequestFactory.d.ts
├── types
│ ├── RequestMatcher.d.ts
│ └── ResponseModifierCallBack.d.ts
├── interface
│ ├── IRequestFactory.d.ts
│ ├── IRequestSpy.d.ts
│ ├── IRequestBlocker.d.ts
│ ├── IRequestModifier.d.ts
│ └── IResponseFaker.d.ts
├── RequestBlocker.d.ts
├── RequestModifier.d.ts
├── RequestRedirector.d.ts
├── ResponseFaker.d.ts
├── RequestSpy.d.ts
├── ResponseModifier.d.ts
├── index.d.ts
└── RequestInterceptor.d.ts
├── documentation
├── activity.png
├── activity.iuml
└── API.md
├── task
├── runTestServer.js
├── clean.js
└── copyTypes.js
├── .idea
├── watcherTasks.xml
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── encodings.xml
├── vcs.xml
├── jsLibraryMappings.xml
├── misc.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── typescript-compiler.xml
├── modules.xml
├── puppeteer-request-spy.iml
└── codeStyleSettings.xml
├── .travis.yml
├── puppeteer-request-spy.iml
├── examples
├── CustomSpy.ts
├── CustomFaker.ts
├── block-test.spec.js
├── simple-test.spec.js
├── keyword-matcher.spec.js
└── fake-test.spec.js
├── tsconfig-lint.json
├── CONTRIBUTING.md
├── tsconfig.json
├── LICENSE
├── .npmignore
├── .gitignore
├── package.json
├── README.md
└── tslint.json
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/test/integration/fakes/remote.html:
--------------------------------------------------------------------------------
1 |
some stuff
2 |
--------------------------------------------------------------------------------
/custom-typings/clear-module/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'clear-module';
2 |
--------------------------------------------------------------------------------
/test/integration/fakes/redirected.html:
--------------------------------------------------------------------------------
1 | some redirected stuff
2 |
--------------------------------------------------------------------------------
/src/common/Logger.ts:
--------------------------------------------------------------------------------
1 | export interface ILogger {
2 | log(message: string): void;
3 | }
4 |
--------------------------------------------------------------------------------
/test/integration/fakes/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/types/common/Logger.d.ts:
--------------------------------------------------------------------------------
1 | export interface ILogger {
2 | log(message: string): void;
3 | }
4 |
--------------------------------------------------------------------------------
/types/common/VoidLogger.d.ts:
--------------------------------------------------------------------------------
1 | export declare class VoidLogger {
2 | log(logText: string): void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/RequestMatcher.ts:
--------------------------------------------------------------------------------
1 | export type RequestMatcher = (testString: string, pattern: string) => boolean;
2 |
--------------------------------------------------------------------------------
/documentation/activity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tabueeee/puppeteer-request-spy/HEAD/documentation/activity.png
--------------------------------------------------------------------------------
/task/runTestServer.js:
--------------------------------------------------------------------------------
1 | const app = require('../build/test/common/testServer').testServer;
2 |
3 | app.listen(1337);
4 |
--------------------------------------------------------------------------------
/types/types/RequestMatcher.d.ts:
--------------------------------------------------------------------------------
1 | export declare type RequestMatcher = (testString: string, pattern: string) => boolean;
2 |
--------------------------------------------------------------------------------
/test/common/TestDouble.ts:
--------------------------------------------------------------------------------
1 | // noinspection TsLint
2 | export type TestDouble = {
3 | [P in keyof T]?: any;
4 | };
5 |
--------------------------------------------------------------------------------
/types/common/resolveOptionalPromise.d.ts:
--------------------------------------------------------------------------------
1 | export declare function resolveOptionalPromise(subject: T | Promise): Promise;
2 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - stable
5 |
6 | install:
7 | - npm install
8 |
9 | script:
10 | - npm run test-coverage-travis
11 |
--------------------------------------------------------------------------------
/types/common/interfaceValidators/instanceOfRequestSpy.d.ts:
--------------------------------------------------------------------------------
1 | import { IRequestSpy } from '../../';
2 | export declare function instanceOfRequestSpy(object: object): object is IRequestSpy;
3 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/common/urlAccessor/UrlAccessor.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 |
3 | export abstract class UrlAccessor {
4 | public abstract getUrlFromRequest(request: Request): string;
5 | }
6 |
--------------------------------------------------------------------------------
/types/common/urlAccessor/UrlAccessor.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | export declare abstract class UrlAccessor {
3 | abstract getUrlFromRequest(request: Request): string;
4 | }
5 |
--------------------------------------------------------------------------------
/types/common/interfaceValidators/instanceOfResponseFaker.d.ts:
--------------------------------------------------------------------------------
1 | import { IResponseFaker } from '../../';
2 | export declare function instanceOfResponseFaker(object: object): object is IResponseFaker;
3 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/types/ResponseModifierCallBack.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 |
3 | export type ResponseModifierCallBack = (err: Error | undefined, response: string, request: Request) => string | Promise;
4 |
--------------------------------------------------------------------------------
/types/types/ResponseModifierCallBack.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | export declare type ResponseModifierCallBack = (err: Error | undefined, response: string, request: Request) => string | Promise;
3 |
--------------------------------------------------------------------------------
/types/common/interfaceValidators/instanceOfRequestBlocker.d.ts:
--------------------------------------------------------------------------------
1 | import { IRequestBlocker } from '../../interface/IRequestBlocker';
2 | export declare function instanceOfRequestBlocker(object: object): object is IRequestBlocker;
3 |
--------------------------------------------------------------------------------
/types/common/interfaceValidators/instanceOfRequestModifier.d.ts:
--------------------------------------------------------------------------------
1 | import { IRequestModifier } from '../../interface/IRequestModifier';
2 | export declare function instanceOfRequestModifier(object: object): object is IRequestModifier;
3 |
--------------------------------------------------------------------------------
/src/common/VoidLogger.ts:
--------------------------------------------------------------------------------
1 | export class VoidLogger {
2 | // @ts-ignore: logText
3 | // noinspection JSUnusedLocalSymbols, JSMethodCanBeStatic
4 | public log(logText: string): void {
5 | return undefined;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/types/interface/IRequestFactory.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, RespondOptions } from 'puppeteer';
2 | export interface IRequestFactory {
3 | createRequest(request: Request): Promise;
6 | }
7 |
--------------------------------------------------------------------------------
/types/common/urlAccessor/UrlStringAccessor.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { UrlAccessor } from './UrlAccessor';
3 | export declare class UrlStringAccessor extends UrlAccessor {
4 | getUrlFromRequest(request: Request): string;
5 | }
6 |
--------------------------------------------------------------------------------
/types/common/urlAccessor/UrlAccessorResolver.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { UrlAccessor } from './UrlAccessor';
3 | export declare module UrlAccessorResolver {
4 | function getUrlAccessor(interceptedRequest: Request): UrlAccessor;
5 | }
6 |
--------------------------------------------------------------------------------
/types/common/urlAccessor/UrlFunctionAccessor.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { UrlAccessor } from './UrlAccessor';
3 | export declare class UrlFunctionAccessor extends UrlAccessor {
4 | getUrlFromRequest(request: Request): string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/resolveOptionalPromise.ts:
--------------------------------------------------------------------------------
1 | export async function resolveOptionalPromise(subject: T | Promise): Promise {
2 | if (typeof subject === 'object' && subject instanceof Promise) {
3 | return await subject;
4 | }
5 |
6 | return subject;
7 | }
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/common/urlAccessor/UrlFunctionAccessor.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {UrlAccessor} from './UrlAccessor';
3 |
4 | export class UrlFunctionAccessor extends UrlAccessor {
5 | public getUrlFromRequest(request: Request): string {
6 | return request.url();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/common/ServerSettings.ts:
--------------------------------------------------------------------------------
1 | export interface ServerSettings {
2 | rootPath: string;
3 | port: number;
4 | host: string;
5 | }
6 |
7 | export const serverSettings: ServerSettings = {
8 | rootPath: 'test/integration/fakes',
9 | port: 1337,
10 | host: 'localhost'
11 | };
12 |
--------------------------------------------------------------------------------
/test/integration/fakes/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | hello world
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/typescript-compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/common/interfaceValidators/instanceOfRequestSpy.ts:
--------------------------------------------------------------------------------
1 | import {IRequestSpy} from '../../';
2 |
3 | export function instanceOfRequestSpy(object: object): object is IRequestSpy {
4 | return typeof (object).addMatch === 'function'
5 | && typeof (object).isMatchingRequest === 'function';
6 | }
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/types/interface/IRequestSpy.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { RequestMatcher } from '../types/RequestMatcher';
3 | export interface IRequestSpy {
4 | isMatchingRequest(request: Request, matcher: RequestMatcher): Promise | boolean;
5 | addMatch(matchedRequest: Request): Promise | void;
6 | }
7 |
--------------------------------------------------------------------------------
/src/common/interfaceValidators/instanceOfResponseFaker.ts:
--------------------------------------------------------------------------------
1 | import {IResponseFaker} from '../../';
2 |
3 | export function instanceOfResponseFaker(object: object): object is IResponseFaker {
4 | return typeof (object).getResponseFake === 'function'
5 | && typeof (object).isMatchingRequest === 'function';
6 | }
7 |
--------------------------------------------------------------------------------
/src/common/urlAccessor/UrlStringAccessor.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {UrlAccessor} from './UrlAccessor';
3 |
4 | export class UrlStringAccessor extends UrlAccessor {
5 | public getUrlFromRequest(request: Request): string {
6 | // @ts-ignore: support old puppeteer version
7 | return request.url;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/types/interface/IRequestBlocker.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { RequestMatcher } from '../types/RequestMatcher';
3 | export interface IRequestBlocker {
4 | shouldBlockRequest(request: Request, matcher: RequestMatcher): Promise | boolean;
5 | clearUrlsToBlock(): void;
6 | addUrlsToBlock(urlsToBlock: Array | string): void;
7 | }
8 |
--------------------------------------------------------------------------------
/types/interface/IRequestModifier.d.ts:
--------------------------------------------------------------------------------
1 | import { Overrides, Request } from 'puppeteer';
2 | import { RequestMatcher } from '../types/RequestMatcher';
3 | export interface IRequestModifier {
4 | isMatchingRequest(interceptedRequest: Request, matcher: RequestMatcher): Promise | boolean;
5 | getOverride(interceptedRequest: Request): Promise | Overrides;
6 | }
7 |
--------------------------------------------------------------------------------
/types/interface/IResponseFaker.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, RespondOptions } from 'puppeteer';
2 | import { RequestMatcher } from '../types/RequestMatcher';
3 | export interface IResponseFaker {
4 | isMatchingRequest(interceptedRequest: Request, matcher: RequestMatcher): Promise | boolean;
5 | getResponseFake(request: Request): RespondOptions | Promise;
6 | }
7 |
--------------------------------------------------------------------------------
/src/common/interfaceValidators/instanceOfRequestModifier.ts:
--------------------------------------------------------------------------------
1 | import {IRequestModifier} from '../../interface/IRequestModifier';
2 |
3 | export function instanceOfRequestModifier(object: object): object is IRequestModifier {
4 | return typeof (object).getOverride === 'function'
5 | && typeof (object).isMatchingRequest === 'function';
6 | }
7 |
--------------------------------------------------------------------------------
/types/common/HttpRequestFactory.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, RespondOptions } from 'puppeteer';
2 | import { IRequestFactory } from '../interface/IRequestFactory';
3 | export declare class HttpRequestFactory implements IRequestFactory {
4 | private timeout;
5 | constructor(timeout?: number);
6 | createRequest(request: Request): Promise;
9 | private convertHeaders;
10 | }
11 |
--------------------------------------------------------------------------------
/task/clean.js:
--------------------------------------------------------------------------------
1 | const promisify = require('util').promisify;
2 | const glob = promisify(require('glob'));
3 | const del = require('del');
4 |
5 | (async () => {
6 | try {
7 | let files = await glob('build/**/*');
8 | for (let file of files) {
9 | await del(file);
10 | }
11 | } catch (error) {
12 | console.error(error);
13 | process.exit(2);
14 | }
15 | })();
16 |
17 |
--------------------------------------------------------------------------------
/test/common/CustomRequestBlocker.ts:
--------------------------------------------------------------------------------
1 | import {IRequestBlocker} from '../../src/interface/IRequestBlocker';
2 |
3 | export class CustomRequestBlocker implements IRequestBlocker {
4 |
5 | public shouldBlockRequest(): boolean {
6 | return true;
7 | }
8 |
9 | public clearUrlsToBlock(): void {
10 | return undefined;
11 | }
12 |
13 | public addUrlsToBlock(): void {
14 | return undefined;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/common/interfaceValidators/instanceOfRequestBlocker.ts:
--------------------------------------------------------------------------------
1 | import {IRequestBlocker} from '../../interface/IRequestBlocker';
2 |
3 | export function instanceOfRequestBlocker(object: object): object is IRequestBlocker {
4 | return typeof (object).shouldBlockRequest === 'function'
5 | && typeof (object).clearUrlsToBlock === 'function'
6 | && typeof (object).addUrlsToBlock === 'function';
7 | }
8 |
--------------------------------------------------------------------------------
/types/RequestBlocker.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { IRequestBlocker } from './interface/IRequestBlocker';
3 | import { RequestMatcher } from './types/RequestMatcher';
4 | export declare class RequestBlocker implements IRequestBlocker {
5 | private urlsToBlock;
6 | shouldBlockRequest(request: Request, matcher: RequestMatcher): boolean;
7 | addUrlsToBlock(urls: Array | string): void;
8 | clearUrlsToBlock(): void;
9 | }
10 |
--------------------------------------------------------------------------------
/src/interface/IRequestFactory.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 |
3 | export interface IRequestFactory {
4 | /**
5 | * @param request : puppeteers Request object
6 | * @return Overrides: RespondOptions as defined by puppeteer but with mandatory body string
7 | *
8 | * loads a http request in the node environment
9 | */
10 | createRequest(request: Request): Promise;
11 | }
12 |
--------------------------------------------------------------------------------
/test/integration/fakes/script.js:
--------------------------------------------------------------------------------
1 | console.log('hello world');
2 |
3 | const xhrElement = document.getElementById('xhr');
4 |
5 | var data = null;
6 |
7 | var xhr = new XMLHttpRequest();
8 | xhr.withCredentials = true;
9 |
10 | xhr.addEventListener("readystatechange", function () {
11 | if (this.readyState === 4) {
12 | xhrElement.innerHTML = this.responseText;
13 | }
14 | });
15 |
16 | xhr.open("GET", "/fakes/remote.html");
17 | xhr.setRequestHeader("cache-control", "no-cache");
18 |
19 | xhr.send(data);
20 |
--------------------------------------------------------------------------------
/task/copyTypes.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const promisify = require('util').promisify;
3 | const glob = promisify(require('glob'));
4 |
5 | (async () => {
6 | try {
7 | let files = await glob('build/src/**/*.d.ts');
8 | console.log(files);
9 | for (let file of files) {
10 | await fs.copy(file, file.replace('build/src', 'types'), {overwrite: true});
11 | }
12 | } catch (error) {
13 | console.error(error);
14 | process.exit(2);
15 | }
16 | })();
17 |
18 |
--------------------------------------------------------------------------------
/test/common/ServerDouble.ts:
--------------------------------------------------------------------------------
1 | import {serverSettings} from './ServerSettings';
2 | import {testServer} from './testServer';
3 |
4 | export class ServerDouble {
5 | // tslint:disable-next-line:no-any
6 | private server: any;
7 |
8 | public async start(): Promise<{}> {
9 | return new Promise((resolve: () => void): void => {
10 | this.server = testServer.listen(serverSettings.port);
11 | resolve();
12 | });
13 | }
14 |
15 | public stop(): void {
16 | this.server.close();
17 | }
18 | }
19 |
20 | export const serverDouble: ServerDouble = new ServerDouble();
21 |
--------------------------------------------------------------------------------
/types/RequestModifier.d.ts:
--------------------------------------------------------------------------------
1 | import { Overrides, Request } from 'puppeteer';
2 | import { IRequestModifier } from './interface/IRequestModifier';
3 | import { RequestMatcher } from './types/RequestMatcher';
4 | export declare class RequestModifier implements IRequestModifier {
5 | private patterns;
6 | private requestOverrideFactory;
7 | constructor(patterns: Array | string, requestOverride: ((request: Request) => Promise | Overrides) | Overrides);
8 | getOverride(request: Request): Promise;
9 | isMatchingRequest(request: Request, matcher: RequestMatcher): boolean;
10 | getPatterns(): Array;
11 | }
12 |
--------------------------------------------------------------------------------
/types/RequestRedirector.d.ts:
--------------------------------------------------------------------------------
1 | import { Overrides, Request } from 'puppeteer';
2 | import { IRequestModifier } from './interface/IRequestModifier';
3 | import { RequestMatcher } from './types/RequestMatcher';
4 | export declare class RequestRedirector implements IRequestModifier {
5 | private patterns;
6 | private redirectionUrlFactory;
7 | constructor(patterns: Array | string, redirectionUrl: ((request: Request) => Promise | string) | string);
8 | isMatchingRequest(request: Request, matcher: RequestMatcher): boolean;
9 | getPatterns(): Array;
10 | getOverride(interceptedRequest: Request): Promise;
11 | }
12 |
--------------------------------------------------------------------------------
/types/ResponseFaker.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, RespondOptions } from 'puppeteer';
2 | import { IResponseFaker } from './interface/IResponseFaker';
3 | import { RequestMatcher } from './types/RequestMatcher';
4 | export declare class ResponseFaker implements IResponseFaker {
5 | private patterns;
6 | private responseFakeFactory;
7 | constructor(patterns: Array | string, responseFake: ((request: Request) => RespondOptions | Promise) | RespondOptions);
8 | getResponseFake(request: Request): Promise;
9 | isMatchingRequest(request: Request, matcher: RequestMatcher): boolean;
10 | getPatterns(): Array;
11 | }
12 |
--------------------------------------------------------------------------------
/test/integration/fakes/ajaxWithPost.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/integration/fakes/ajaxWithUnavailableRequest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/types/RequestSpy.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { IRequestSpy } from './interface/IRequestSpy';
3 | import { RequestMatcher } from './types/RequestMatcher';
4 | export declare class RequestSpy implements IRequestSpy {
5 | private hasMatchingUrl;
6 | private matchCount;
7 | private patterns;
8 | private matchedRequests;
9 | constructor(patterns: Array | string);
10 | getPatterns(): Array;
11 | getMatchedRequests(): Array;
12 | hasMatch(): boolean;
13 | addMatch(matchedRequest: Request): void;
14 | isMatchingRequest(request: Request, matcher: RequestMatcher): boolean;
15 | getMatchedUrls(): Array;
16 | getMatchCount(): number;
17 | }
18 |
--------------------------------------------------------------------------------
/src/interface/IRequestSpy.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {RequestMatcher} from '../types/RequestMatcher';
3 |
4 | export interface IRequestSpy {
5 | /**
6 | * @param request : puppeteers Request object to get the url from.
7 | * @param matcher <(url: string, pattern: string): Promise | boolean)>: matches the url with the pattern to block.
8 | *
9 | * checks if the RequestSpy matches the Request.
10 | */
11 | isMatchingRequest(request: Request, matcher: RequestMatcher): Promise | boolean;
12 | /**
13 | * React to requests.
14 | * @param matchedRequest - The request to react to
15 | */
16 | addMatch(matchedRequest: Request): Promise | void;
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/urlAccessor/UrlAccessorResolver.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {UrlAccessor} from './UrlAccessor';
3 | import {UrlFunctionAccessor} from './UrlFunctionAccessor';
4 | import {UrlStringAccessor} from './UrlStringAccessor';
5 |
6 | export module UrlAccessorResolver {
7 | let accessor: UrlAccessor;
8 |
9 | export function getUrlAccessor(interceptedRequest: Request): UrlAccessor {
10 | if (accessor instanceof UrlAccessor === false) {
11 | if (typeof interceptedRequest.url === 'string') {
12 | accessor = new UrlStringAccessor();
13 | } else {
14 | accessor = new UrlFunctionAccessor();
15 | }
16 | }
17 |
18 | return accessor;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/common/CustomFaker.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 | import {IResponseFaker, RequestMatcher} from '../../src';
3 |
4 | export class CustomFaker implements IResponseFaker {
5 |
6 | public getResponseFake(request: Request): RespondOptions {
7 | if (request.method() === 'GET') {
8 | return {
9 | status: 200,
10 | contentType: 'text/plain',
11 | body: 'Just a mock!'
12 | };
13 | }
14 |
15 | return {
16 | status: 404,
17 | contentType: 'text/plain',
18 | body: 'Not Found!'
19 | };
20 | }
21 |
22 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
23 | return matcher(request.url(), '**/remote.html');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/interface/IRequestBlocker.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {RequestMatcher} from '../types/RequestMatcher';
3 |
4 | export interface IRequestBlocker {
5 | /**
6 | * @param request : request to get the url from.
7 | * @param matcher <(url: string, pattern: string): Promise | boolean)>: matches the url with the pattern to block.
8 | *
9 | * determines if a request should be blocked.
10 | */
11 | shouldBlockRequest(request: Request, matcher: RequestMatcher): Promise | boolean;
12 |
13 | /**
14 | * removes all patterns added to the RequestBlocker.
15 | */
16 | clearUrlsToBlock(): void;
17 |
18 | /**
19 | * adds new patterns to the RequestBlocker.
20 | */
21 | addUrlsToBlock(urlsToBlock: Array | string): void;
22 | }
23 |
--------------------------------------------------------------------------------
/types/ResponseModifier.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, RespondOptions } from 'puppeteer';
2 | import { IRequestFactory } from './interface/IRequestFactory';
3 | import { IResponseFaker } from './interface/IResponseFaker';
4 | import { RequestMatcher } from './types/RequestMatcher';
5 | import { ResponseModifierCallBack } from './types/ResponseModifierCallBack';
6 | export declare class ResponseModifier implements IResponseFaker {
7 | private patterns;
8 | private responseModifierCallBack;
9 | private httpRequestFactory;
10 | constructor(patterns: Array | string, responseModifierCallBack: ResponseModifierCallBack, httpRequestFactory?: IRequestFactory);
11 | isMatchingRequest(request: Request, matcher: RequestMatcher): boolean;
12 | getResponseFake(request: Request): Promise;
13 | getPatterns(): Array;
14 | }
15 |
--------------------------------------------------------------------------------
/test/common/AssertionHelpers.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 |
3 | export async function assertThrowsAsync(fn: () => void, regExp: RegExp): Promise {
4 | // noinspection TsLint
5 | let f: () => void = (): void => {
6 | };
7 | try {
8 | await fn();
9 | } catch (e) {
10 | f = (): void => {
11 | throw e;
12 | };
13 | } finally {
14 | assert.throws(f, regExp);
15 | }
16 | }
17 |
18 | export async function assertDoesNotThrowAsync(fn: () => void, regExp: RegExp): Promise {
19 | // noinspection TsLint
20 | let f: () => void = (): void => {
21 | };
22 | try {
23 | await fn();
24 | } catch (e) {
25 | f = (): void => {
26 | throw e;
27 | };
28 | } finally {
29 | assert.doesNotThrow(f, regExp);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/puppeteer-request-spy.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/CustomSpy.ts:
--------------------------------------------------------------------------------
1 | // import {IRequestSpy, RequestMatcher} from 'puppeteer-request-spy';
2 | import {IRequestSpy, RequestMatcher} from '../..';
3 | import {Request, Response} from 'puppeteer';
4 | import * as assert from "assert";
5 |
6 |
7 | export class CustomSpy implements IRequestSpy {
8 | private matches: Array = [];
9 |
10 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
11 |
12 | return matcher(request.url(), '**/*');
13 | }
14 |
15 | public addMatch(matchedRequest: Request): void {
16 |
17 | this.matches.push(matchedRequest);
18 | }
19 |
20 | public assertRequestsOk() {
21 | for (let match of this.matches) {
22 | let response: Response | null = match.response();
23 | if (response !== null) {
24 | assert.ok(response.ok());
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.idea/puppeteer-request-spy.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/interface/IRequestModifier.ts:
--------------------------------------------------------------------------------
1 | import {Overrides, Request} from 'puppeteer';
2 | import {RequestMatcher} from '../types/RequestMatcher';
3 |
4 | export interface IRequestModifier {
5 | /**
6 | * @param interceptedRequest : puppeteers Request object to get the url from.
7 | * @param matcher <(url: string, pattern: string): Promise | boolean)>: matches the url with the pattern to block.
8 | *
9 | * checks if the ResponseFaker matches the Request and will provide a ResponseFake
10 | */
11 | isMatchingRequest(interceptedRequest: Request, matcher: RequestMatcher): Promise | boolean;
12 |
13 | /**
14 | * @param interceptedRequest : puppeteers Request object
15 | * @return Overrides: Overrides as defined by puppeteer
16 | *
17 | * provides the RequestOverrides to be used by the RequestInterceptor to modify the request
18 | */
19 | getOverride(interceptedRequest: Request): Promise | Overrides;
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig-lint.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "module": "commonjs",
5 | "noImplicitAny": true,
6 | "removeComments": true,
7 | "preserveConstEnums": false,
8 | "target": "es2015",
9 | "lib": ["es6", "DOM"],
10 | "moduleResolution": "node",
11 | "experimentalDecorators": true,
12 | "noEmitOnError": true,
13 | "allowUnreachableCode": false,
14 | "downlevelIteration": true,
15 | "locale": "en-us",
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitReturns": true,
18 | "noImplicitThis": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "strict": true,
22 | "strictNullChecks": true,
23 | "stripInternal": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "outDir": "./build",
26 | "typeRoots": [
27 | ]
28 | },
29 | "exclude": [
30 | "./src/**/VoidLogger.ts",
31 | "./test/**/*.ts",
32 | "./examples/**/*",
33 | "./types/**/*"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/src/interface/IResponseFaker.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 | import {RequestMatcher} from '../types/RequestMatcher';
3 |
4 | export interface IResponseFaker {
5 | /**
6 | * @param interceptedRequest : puppeteers Request object to get the url from.
7 | * @param matcher <(url: string, pattern: string): Promise | boolean)>: matches the url with the pattern to block.
8 | *
9 | * checks if the ResponseFaker matches the Request and will provide a ResponseFake
10 | */
11 | isMatchingRequest(interceptedRequest: Request, matcher: RequestMatcher): Promise | boolean;
12 |
13 | /**
14 | * @param request : puppeteers Request object
15 | * @return RespondOptions: RespondOptions as defined by puppeteer
16 | *
17 | * provides the ResponseFake to be used by the RequestInterceptor to fake the response of the request
18 | */
19 | getResponseFake(request: Request): RespondOptions | Promise;
20 | }
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This Project uses Semantic Versioning. More information can be found [here](https://semver.org/).
2 |
3 | The branch and tag names also follow the following convention:
4 | - branches: ```v-x.y.z```
5 | - tags: ```vx.y.z```
6 |
7 | You only need to follow this convention when creating a Pull Request for a full npm release.
8 |
9 | Please make sure your branch passes the build process by running ```npm test```.
10 | You can check the code coverage by generating a html report using ```npm run test-coverage```.
11 |
12 | The tslint setting may seem harsh but they are usually useful to determine problems.
13 | Try to fix as much as possible but I am not contempt on keeping every rule.
14 | Some are a matter of choice after all.
15 |
16 | If you want to ensure a proper release, bump the version in the package.json and run ```npm run release-dry```.
17 | This will run all required steps for a successful release like ts-lint, build, test, generating types
18 | and creates a preview of the final package pushed to npm.
19 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IRequestBlocker } from './interface/IRequestBlocker';
2 | import { IRequestFactory } from './interface/IRequestFactory';
3 | import { IRequestModifier } from './interface/IRequestModifier';
4 | import { IRequestSpy } from './interface/IRequestSpy';
5 | import { IResponseFaker } from './interface/IResponseFaker';
6 | import { RequestInterceptor } from './RequestInterceptor';
7 | import { RequestModifier } from './RequestModifier';
8 | import { RequestRedirector } from './RequestRedirector';
9 | import { RequestSpy } from './RequestSpy';
10 | import { ResponseFaker } from './ResponseFaker';
11 | import { ResponseModifier } from './ResponseModifier';
12 | import { RequestMatcher } from './types/RequestMatcher';
13 | import { ResponseModifierCallBack } from './types/ResponseModifierCallBack';
14 | export { RequestInterceptor, RequestModifier, RequestRedirector, RequestSpy, ResponseFaker, ResponseModifier, IRequestBlocker, IRequestModifier, IRequestSpy, IResponseFaker, IRequestFactory, RequestMatcher, ResponseModifierCallBack };
15 |
--------------------------------------------------------------------------------
/test/integration/fakes/ajaxWithCustomHeaders.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "module": "commonjs",
5 | "noImplicitAny": true,
6 | "removeComments": true,
7 | "preserveConstEnums": false,
8 | "target": "ES2015",
9 | "lib": [
10 | "es6",
11 | "dom"
12 | ],
13 | "moduleResolution": "node",
14 | "experimentalDecorators": true,
15 | "noEmitOnError": true,
16 | "allowUnreachableCode": false,
17 | "downlevelIteration": true,
18 | "locale": "en-us",
19 | "noFallthroughCasesInSwitch": true,
20 | "noImplicitReturns": true,
21 | "noImplicitThis": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "strict": true,
25 | "strictNullChecks": true,
26 | "stripInternal": true,
27 | "suppressImplicitAnyIndexErrors": true,
28 | "outDir": "./build",
29 | "typeRoots": [
30 | "custom-typings",
31 | "./node_modules/@types"
32 | ]
33 | },
34 | "include": [
35 | "./src/**/*.ts",
36 | "./test/**/*.ts",
37 | "./custom-typings/index"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Tobias Nießen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/src/RequestBlocker.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
3 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
4 | import {IRequestBlocker} from './interface/IRequestBlocker';
5 | import {RequestMatcher} from './types/RequestMatcher';
6 |
7 | export class RequestBlocker implements IRequestBlocker {
8 |
9 | private urlsToBlock: Array = [];
10 |
11 | public shouldBlockRequest(request: Request, matcher: RequestMatcher): boolean {
12 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
13 |
14 | for (let urlToBlock of this.urlsToBlock) {
15 | if (matcher(urlAccessor.getUrlFromRequest(request), urlToBlock)) {
16 | return true;
17 | }
18 | }
19 |
20 | return false;
21 | }
22 |
23 | public addUrlsToBlock(urls: Array | string): void {
24 | this.urlsToBlock = this.urlsToBlock.concat(urls);
25 | }
26 |
27 | public clearUrlsToBlock(): void {
28 | this.urlsToBlock = [];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {IRequestBlocker} from './interface/IRequestBlocker';
2 | import {IRequestFactory} from './interface/IRequestFactory';
3 | import {IRequestModifier} from './interface/IRequestModifier';
4 | import {IRequestSpy} from './interface/IRequestSpy';
5 | import {IResponseFaker} from './interface/IResponseFaker';
6 | import {RequestInterceptor} from './RequestInterceptor';
7 | import {RequestModifier} from './RequestModifier';
8 | import {RequestRedirector} from './RequestRedirector';
9 | import {RequestSpy} from './RequestSpy';
10 | import {ResponseFaker} from './ResponseFaker';
11 | import {ResponseModifier} from './ResponseModifier';
12 | import {RequestMatcher} from './types/RequestMatcher';
13 | import {ResponseModifierCallBack} from './types/ResponseModifierCallBack';
14 |
15 | export {
16 | RequestInterceptor,
17 | RequestModifier,
18 | RequestRedirector,
19 | RequestSpy,
20 | ResponseFaker,
21 | ResponseModifier,
22 | IRequestBlocker,
23 | IRequestModifier,
24 | IRequestSpy,
25 | IResponseFaker,
26 | IRequestFactory,
27 | RequestMatcher,
28 | ResponseModifierCallBack
29 | };
30 |
--------------------------------------------------------------------------------
/examples/CustomFaker.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 | // import {IResponseFaker, RequestMatcher} from 'puppeteer-request-spy';
3 | import {IResponseFaker, RequestMatcher} from '../..';
4 |
5 | export class CustomFaker implements IResponseFaker {
6 |
7 | private patterns: Array = [];
8 | private fakesMap = {
9 | 'GET': 'some text',
10 | 'POST': 'Not Found!'
11 | };
12 |
13 | public constructor(patterns: Array) {
14 | this.patterns = patterns;
15 | }
16 |
17 | public getResponseFake(request: Request): RespondOptions | Promise {
18 | return Promise.resolve(
19 | {
20 | status: 200,
21 | contentType: 'text/plain',
22 | body: this.fakesMap[request.method()]
23 | }
24 | );
25 | }
26 |
27 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
28 | for (let pattern of this.patterns) {
29 | if (matcher(request.url(), pattern)) {
30 |
31 | return true;
32 | }
33 | }
34 |
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/common/Browser.ts:
--------------------------------------------------------------------------------
1 | import * as puppeteer from 'puppeteer';
2 | import {Browser, LaunchOptions} from 'puppeteer';
3 |
4 | export class BrowserLauncher {
5 |
6 | private browser: Browser | undefined;
7 | private initialized: boolean = false;
8 |
9 | public async initialize(options?: LaunchOptions): Promise {
10 | this.initialized = true;
11 | if (typeof options === 'undefined') {
12 | options = {
13 | headless: true
14 | };
15 | }
16 |
17 | this.browser = await puppeteer.launch(options);
18 | }
19 |
20 | public async closeBrowser(): Promise {
21 | if (typeof this.browser !== 'undefined') {
22 | await this.browser.close();
23 | }
24 | }
25 |
26 | public async getBrowser(): Promise {
27 | if (typeof this.browser === 'undefined') {
28 | if (this.initialized) {
29 | throw new Error('unable to initialize browser.');
30 | } else {
31 | await this.initialize();
32 | }
33 | }
34 |
35 | return Promise.resolve( this.browser);
36 | }
37 | }
38 |
39 | export const browserLauncher: BrowserLauncher = new BrowserLauncher();
40 |
--------------------------------------------------------------------------------
/types/RequestInterceptor.d.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'puppeteer';
2 | import { ILogger } from './common/Logger';
3 | import { IRequestBlocker } from './interface/IRequestBlocker';
4 | import { IRequestModifier } from './interface/IRequestModifier';
5 | import { IRequestSpy } from './interface/IRequestSpy';
6 | import { IResponseFaker } from './interface/IResponseFaker';
7 | import { RequestMatcher } from './types/RequestMatcher';
8 | export declare class RequestInterceptor {
9 | private requestSpies;
10 | private responseFakers;
11 | private requestModifiers;
12 | private matcher;
13 | private logger;
14 | private requestBlocker;
15 | constructor(matcher: RequestMatcher, logger?: ILogger);
16 | intercept(interceptedRequest: Request): Promise;
17 | addSpy(requestSpy: IRequestSpy): void;
18 | addFaker(responseFaker: IResponseFaker): void;
19 | addRequestModifier(requestModifier: IRequestModifier): void;
20 | block(urlsToBlock: Array | string): void;
21 | clearSpies(): void;
22 | clearFakers(): void;
23 | clearRequestModifiers(): void;
24 | clearUrlsToBlock(): void;
25 | setUrlsToBlock(urlsToBlock: Array): void;
26 | setRequestBlocker(requestBlocker: IRequestBlocker): void;
27 | private getMatchingFaker;
28 | private matchSpies;
29 | private getMatchingOverride;
30 | private blockUrl;
31 | private acceptUrl;
32 | }
33 |
--------------------------------------------------------------------------------
/documentation/activity.iuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam shadowing false
3 |
4 | skinparam activity {
5 | DiamondFontSize 16
6 | FontSize 16
7 | BorderThickness 2
8 |
9 | FontColor black
10 | DiamondFontColor black
11 | BackgroundColor white
12 | DiamondBackgroundColor white
13 | BorderColor RoyalBlue
14 | DiamondBorderColor Seagreen
15 |
16 | StartColor 343131
17 | EndColor 343131
18 | }
19 |
20 | skinparam arrow {
21 | color 343131
22 | fontColor black
23 | FontSize 16
24 | }
25 |
26 | start
27 |
28 | if (\nRequest matches pattern\nof any spies?\n) then ( yes )
29 | :Notify all matching spies;
30 | else ( no )
31 | endif
32 |
33 |
34 | if (\nRequest matches any pattern\nof the RequestBlocker?\n) then ( no )
35 |
36 | else ( yes )
37 | :Block request\nwith request.abort;
38 | stop
39 |
40 | endif
41 |
42 |
43 | if (\nRequest matches pattern\nof any RequestModifiers?\n) then ( yes )
44 | :Get __first__ matching\nRequestModifier;
45 | :Return modified response\nwith request.continue(overrides);
46 | stop
47 | else ( no )
48 | if (\nRequest matches pattern\nof any ResponseFakers?\n) then ( yes )
49 | :Get __first__ matching\nResponseFaker;
50 | :Respond Fake with\nrequest.respond;
51 | stop
52 | else ( no )
53 | :Return original response\nwith request.continue();
54 | stop
55 | endif
56 | endif
57 |
58 |
59 |
60 | @enduml
61 |
--------------------------------------------------------------------------------
/test/common/filterForbiddenHeaders.ts:
--------------------------------------------------------------------------------
1 | const FORBIDDEN_STATIC_HEADERS: Array = [
2 | 'Accept-Charset',
3 | 'Accept-Encoding',
4 | 'Access-Control-Request-Headers',
5 | 'Access-Control-Request-Method',
6 | 'Connection',
7 | 'Content-Length',
8 | 'Cookie',
9 | 'Cookie2',
10 | 'Date',
11 | 'DNT',
12 | 'Expect',
13 | 'Feature-Policy',
14 | 'Host',
15 | 'Keep-Alive',
16 | 'Origin',
17 | 'Referer',
18 | 'TE',
19 | 'Trailer',
20 | 'Transfer-Encoding',
21 | 'Upgrade',
22 | 'Via'
23 | ];
24 |
25 | // Filters user-agent controlled headers for tests
26 | // more information here: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
27 | export function filterForbiddenHeaders(headers: { [index: string]: string }): { [index: string]: string } {
28 |
29 | Object.keys(headers)
30 | .map((header: string) => {
31 | if (FORBIDDEN_STATIC_HEADERS.indexOf(header) > -1) {
32 | delete headers[header];
33 | }
34 |
35 | if (FORBIDDEN_STATIC_HEADERS.map((item: string) => item.toLowerCase()).indexOf(header.toLowerCase()) > -1) {
36 | delete headers[header.toLowerCase()];
37 | }
38 |
39 | if (header.indexOf('Sec-') === 0 || header.indexOf('sec-') === 0) {
40 | delete headers[header];
41 | }
42 |
43 | if (header.indexOf('Proxy-') === 0 || header.indexOf('proxy-') === 0) {
44 | delete headers[header];
45 | }
46 | });
47 |
48 | return headers;
49 | }
50 |
--------------------------------------------------------------------------------
/examples/block-test.spec.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer');
2 | const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
3 | const RequestSpy = require('puppeteer-request-spy').RequestSpy;
4 | const minimatch = require('minimatch');
5 | const assert = require('assert');
6 |
7 | let browser;
8 |
9 | before(async () => {
10 | browser = await puppeteer.launch({
11 | headless: true
12 | });
13 | });
14 |
15 | after(async () => {
16 | await browser.close();
17 | });
18 |
19 | describe('example-block', function () {
20 |
21 | this.timeout(30000);
22 | this.slow(10000);
23 |
24 | let requestInterceptor;
25 | let secondaryRequestSpy;
26 |
27 | before(() => {
28 | requestInterceptor = new RequestInterceptor(minimatch, console);
29 | secondaryRequestSpy = new RequestSpy('!https://www.example.org/');
30 |
31 | requestInterceptor.addSpy(secondaryRequestSpy);
32 | requestInterceptor.block('!https://www.example.org/');
33 | });
34 |
35 | describe('example-block', function () {
36 | it('example-test', async function () {
37 | let page = await browser.newPage();
38 |
39 | page.setRequestInterception(true);
40 | page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
41 |
42 | await page.goto('https://www.example.org', {
43 | waitUntil: 'networkidle0',
44 | timeout: 3000000
45 | });
46 |
47 | assert.ok(secondaryRequestSpy.hasMatch() && secondaryRequestSpy.getMatchCount() > 0);
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/examples/simple-test.spec.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer');
2 | const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
3 | const RequestSpy = require('puppeteer-request-spy').RequestSpy;
4 | const minimatch = require('minimatch');
5 | const assert = require('assert');
6 |
7 | let browser;
8 |
9 | before(async () => {
10 | browser = await puppeteer.launch({
11 | headless: true
12 | });
13 | });
14 |
15 | after(async () => {
16 | await browser.close();
17 | });
18 |
19 | describe('example-block', function () {
20 |
21 | this.timeout(30000);
22 | this.slow(10000);
23 |
24 | let requestInterceptor;
25 | let cssSpy;
26 |
27 | before(() => {
28 | requestInterceptor = new RequestInterceptor(minimatch, console);
29 | cssSpy = new RequestSpy('**/*.css');
30 |
31 | requestInterceptor.addSpy(cssSpy);
32 | requestInterceptor.block('**/specific.css');
33 | });
34 |
35 | describe('example-block', function () {
36 | it('example-test', async function () {
37 | let page = await browser.newPage();
38 |
39 | page.setRequestInterception(true);
40 | page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
41 |
42 | await page.goto('https://www.example.org/', {
43 | waitUntil: 'networkidle0',
44 | timeout: 3000000
45 | });
46 |
47 | assert.ok(cssSpy.hasMatch() && cssSpy.getMatchCount() > 0);
48 |
49 | for (let match of cssSpy.getMatchedRequests()) {
50 | // excludes specific.css since blocking requests will cause a failure 'net::ERR_FAILED' and response will be null
51 | if (!match.failure()) {
52 | assert.ok(match.response().ok());
53 | }
54 | }
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/examples/keyword-matcher.spec.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer');
2 | const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
3 | const RequestSpy = require('puppeteer-request-spy').RequestSpy;
4 | const assert = require('assert');
5 |
6 | let browser;
7 |
8 | before(async () => {
9 | browser = await puppeteer.launch({
10 | headless: true
11 | });
12 | });
13 |
14 | after(async () => {
15 | await browser.close();
16 | });
17 |
18 | describe('example-block', function () {
19 |
20 | this.timeout(30000);
21 | this.slow(20000);
22 |
23 | let requestInterceptor;
24 | let imagesSpy;
25 |
26 | before(() => {
27 | imagesSpy = new RequestSpy('pictures');
28 | requestInterceptor = new RequestInterceptor(
29 | (testee, pattern) => testee.indexOf(pattern) > -1,
30 | console
31 | );
32 |
33 | requestInterceptor.addSpy(imagesSpy);
34 | });
35 |
36 | describe('example-block', function () {
37 | it('example-test', async function () {
38 | let page = await browser.newPage();
39 | await page.setRequestInterception(true);
40 |
41 | page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
42 |
43 | // waiting for networkidle0 ensures that all request have been loaded before the page.goto promise resolves
44 | await page.goto('https://www.example.org/', {
45 | waitUntil: 'networkidle0',
46 | timeout: 3000000
47 | });
48 |
49 | // verify spy found matches
50 | assert.ok(imagesSpy.hasMatch() && imagesSpy.getMatchCount() > 0);
51 |
52 | // verify status code for all matching requests
53 | for (let match of imagesSpy.getMatchedRequests()) {
54 | assert.ok(match.response().ok());
55 | }
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/src/ResponseFaker.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 | import {resolveOptionalPromise} from './common/resolveOptionalPromise';
3 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
4 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
5 | import {IResponseFaker} from './interface/IResponseFaker';
6 | import {RequestMatcher} from './types/RequestMatcher';
7 |
8 | export class ResponseFaker implements IResponseFaker {
9 | private patterns: Array = [];
10 | private responseFakeFactory: (request: Request) => RespondOptions | Promise;
11 |
12 | public constructor(
13 | patterns: Array | string,
14 | responseFake: ((request: Request) => RespondOptions | Promise) | RespondOptions
15 | ) {
16 | if (typeof patterns !== 'string' && patterns.constructor !== Array) {
17 | throw new Error('invalid pattern, pattern must be of type string or string[].');
18 | }
19 |
20 | if (typeof patterns === 'string') {
21 | patterns = [patterns];
22 | }
23 |
24 | this.patterns = patterns;
25 | this.responseFakeFactory = typeof responseFake === 'function' ? responseFake : (): RespondOptions => responseFake;
26 | }
27 |
28 | public async getResponseFake(request: Request): Promise {
29 | return await resolveOptionalPromise(this.responseFakeFactory(request));
30 | }
31 |
32 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
33 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
34 | for (let pattern of this.patterns) {
35 | if (matcher(urlAccessor.getUrlFromRequest(request), pattern)) {
36 | return true;
37 | }
38 | }
39 |
40 | return false;
41 | }
42 |
43 | public getPatterns(): Array {
44 | return this.patterns;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/RequestModifier.ts:
--------------------------------------------------------------------------------
1 | import {Overrides, Request} from 'puppeteer';
2 | import {resolveOptionalPromise} from './common/resolveOptionalPromise';
3 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
4 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
5 | import {IRequestModifier} from './interface/IRequestModifier';
6 | import {RequestMatcher} from './types/RequestMatcher';
7 |
8 | export class RequestModifier implements IRequestModifier {
9 | private patterns: Array;
10 | private requestOverrideFactory: (request: Request) => Promise | Overrides;
11 |
12 | public constructor(
13 | patterns: Array | string,
14 | requestOverride: ((request: Request) => Promise | Overrides) | Overrides
15 | ) {
16 | if (typeof patterns !== 'string' && patterns.constructor !== Array) {
17 | throw new Error('invalid pattern, pattern must be of type string or string[].');
18 | }
19 |
20 | if (typeof patterns === 'string') {
21 | patterns = [patterns];
22 | }
23 |
24 | this.patterns = patterns;
25 | this.requestOverrideFactory = typeof requestOverride === 'function'
26 | ? requestOverride
27 | : (): Overrides => requestOverride;
28 | }
29 |
30 | public async getOverride(request: Request): Promise {
31 | return resolveOptionalPromise(this.requestOverrideFactory(request));
32 | }
33 |
34 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
35 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
36 |
37 | for (let pattern of this.patterns) {
38 | if (matcher(urlAccessor.getUrlFromRequest(request), pattern)) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 |
46 | public getPatterns(): Array {
47 | return this.patterns;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/RequestRedirector.ts:
--------------------------------------------------------------------------------
1 | import {Overrides, Request} from 'puppeteer';
2 | import {resolveOptionalPromise} from './common/resolveOptionalPromise';
3 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
4 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
5 | import {IRequestModifier} from './interface/IRequestModifier';
6 | import {RequestMatcher} from './types/RequestMatcher';
7 |
8 | export class RequestRedirector implements IRequestModifier {
9 | private patterns: Array;
10 | private redirectionUrlFactory: ((request: Request) => string | Promise);
11 |
12 | public constructor(
13 | patterns: Array | string,
14 | redirectionUrl: ((request: Request) => Promise | string) | string
15 | ) {
16 | if (typeof patterns !== 'string' && patterns.constructor !== Array) {
17 | throw new Error('invalid pattern, pattern must be of type string or string[].');
18 | }
19 |
20 | if (typeof patterns === 'string') {
21 | patterns = [patterns];
22 | }
23 |
24 | this.patterns = patterns;
25 | this.redirectionUrlFactory = typeof redirectionUrl === 'function'
26 | ? redirectionUrl
27 | : (): string => redirectionUrl;
28 | }
29 |
30 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
31 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
32 | for (let pattern of this.patterns) {
33 | if (matcher(urlAccessor.getUrlFromRequest(request), pattern)) {
34 | return true;
35 | }
36 | }
37 |
38 | return false;
39 | }
40 |
41 | public getPatterns(): Array {
42 | return this.patterns;
43 | }
44 |
45 | public async getOverride(interceptedRequest: Request): Promise {
46 | return {
47 | url: (await resolveOptionalPromise(this.redirectionUrlFactory(interceptedRequest))),
48 | method: interceptedRequest.method(),
49 | headers: interceptedRequest.headers(),
50 | postData: interceptedRequest.postData()
51 | };
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/RequestSpy.ts:
--------------------------------------------------------------------------------
1 | import {Request} from 'puppeteer';
2 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
3 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
4 | import {IRequestSpy} from './interface/IRequestSpy';
5 | import {RequestMatcher} from './types/RequestMatcher';
6 |
7 | export class RequestSpy implements IRequestSpy {
8 |
9 | private hasMatchingUrl: boolean = false;
10 | private matchCount: number = 0;
11 | private patterns: Array = [];
12 | private matchedRequests: Array = [];
13 |
14 | public constructor(patterns: Array | string) {
15 | if (typeof patterns !== 'string' && patterns.constructor !== Array) {
16 | throw new Error('invalid pattern, pattern must be of type string or string[].');
17 | }
18 |
19 | if (typeof patterns === 'string') {
20 | patterns = [patterns];
21 | }
22 |
23 | this.patterns = patterns;
24 | }
25 |
26 | public getPatterns(): Array {
27 | return this.patterns;
28 | }
29 |
30 | public getMatchedRequests(): Array {
31 | return this.matchedRequests;
32 | }
33 |
34 | public hasMatch(): boolean {
35 | return this.hasMatchingUrl;
36 | }
37 |
38 | public addMatch(matchedRequest: Request): void {
39 | this.hasMatchingUrl = true;
40 | this.matchedRequests.push(matchedRequest);
41 | this.matchCount++;
42 | }
43 |
44 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
45 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
46 |
47 | for (let pattern of this.patterns) {
48 | if (matcher(urlAccessor.getUrlFromRequest(request), pattern)) {
49 | return true;
50 | }
51 | }
52 |
53 | return false;
54 | }
55 |
56 | public getMatchedUrls(): Array {
57 | let matchedUrls: Array = [];
58 | for (let match of this.matchedRequests) {
59 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(match);
60 |
61 | let url: string = urlAccessor.getUrlFromRequest(match);
62 | matchedUrls.push(url);
63 | }
64 |
65 | return matchedUrls;
66 | }
67 |
68 | public getMatchCount(): number {
69 | return this.matchCount;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/isolation/UrlAccessorResolver.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import * as clearModule from 'clear-module';
3 | import {Request} from 'puppeteer';
4 | import {UrlAccessor} from '../../src/common/urlAccessor/UrlAccessor';
5 | import {UrlFunctionAccessor} from '../../src/common/urlAccessor/UrlFunctionAccessor';
6 | import {UrlStringAccessor} from '../../src/common/urlAccessor/UrlStringAccessor';
7 | import {TestDouble} from '../common/TestDouble';
8 | import {getLowVersionRequestDouble, getRequestDouble} from '../common/testDoubleFactories';
9 |
10 | describe('module: UrlAccessorResolver', (): void => {
11 | beforeEach(() => {
12 | clearModule('../../src/common/urlAccessor/UrlAccessorResolver');
13 | });
14 |
15 | afterEach(() => {
16 | clearModule('../../src/common/urlAccessor/UrlAccessorResolver');
17 | });
18 |
19 | it('old puppeteer version resolves to UrlStringAccessor', async (): Promise => {
20 | let request: TestDouble = getLowVersionRequestDouble();
21 | let UrlAccessorResolver: any = (await import('../../src/common/urlAccessor/UrlAccessorResolver')).UrlAccessorResolver;
22 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor( request);
23 |
24 | assert.ok(urlAccessor instanceof UrlStringAccessor);
25 | assert.strictEqual(urlAccessor.getUrlFromRequest( request), 'any-url');
26 | });
27 |
28 | it('new puppeteer version resolves to UrlFunctionAccessor', async (): Promise => {
29 | let request: TestDouble = getRequestDouble();
30 | let UrlAccessorResolver: any = (await import('../../src/common/urlAccessor/UrlAccessorResolver')).UrlAccessorResolver;
31 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor( request);
32 |
33 | assert.ok(urlAccessor instanceof UrlFunctionAccessor);
34 | assert.strictEqual(urlAccessor.getUrlFromRequest( request), 'any-url');
35 | });
36 |
37 | it('running test twice provides same cached accessor', async (): Promise => {
38 | let request: TestDouble = getRequestDouble();
39 | let UrlAccessorResolver: any = (await import('../../src/common/urlAccessor/UrlAccessorResolver')).UrlAccessorResolver;
40 | let urlAccessor1: UrlAccessor = UrlAccessorResolver.getUrlAccessor( request);
41 | let urlAccessor2: UrlAccessor = UrlAccessorResolver.getUrlAccessor( request);
42 |
43 | assert.deepStrictEqual(urlAccessor1, urlAccessor2);
44 | assert.ok(urlAccessor1 === urlAccessor2);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | # User-specific stuff:
7 | .idea/
8 |
9 | # CMake
10 | cmake-build-debug/
11 |
12 | ## File-based project format:
13 | *.iws
14 |
15 | ## Plugin-specific files:
16 |
17 | # IntelliJ
18 | out/
19 |
20 | # mpeltonen/sbt-idea plugin
21 | .idea_modules/
22 |
23 | # JIRA plugin
24 | atlassian-ide-plugin.xml
25 |
26 | # Cursive Clojure plugin
27 | .idea/replstate.xml
28 |
29 | # Crashlytics plugin (for Android Studio and IntelliJ)
30 | com_crashlytics_export_strings.xml
31 | crashlytics.properties
32 | crashlytics-build.properties
33 | fabric.properties
34 | ### Windows template
35 | # Windows thumbnail cache files
36 | Thumbs.db
37 | ehthumbs.db
38 | ehthumbs_vista.db
39 |
40 | # Dump file
41 | *.stackdump
42 |
43 | # Folder config file
44 | Desktop.ini
45 |
46 | # Recycle Bin used on file shares
47 | $RECYCLE.BIN/
48 |
49 | # Windows Installer files
50 | *.cab
51 | *.msi
52 | *.msm
53 | *.msp
54 |
55 | # Windows shortcuts
56 | *.lnk
57 | ### Node template
58 | # Logs
59 | logs
60 | *.log
61 | npm-debug.log*
62 | yarn-debug.log*
63 | yarn-error.log*
64 |
65 | # Runtime data
66 | pids
67 | *.pid
68 | *.seed
69 | *.pid.lock
70 |
71 | # Directory for instrumented libs generated by jscoverage/JSCover
72 | lib-cov
73 |
74 | # Coverage directory used by tools like istanbul
75 | coverage
76 |
77 | # nyc test coverage
78 | .nyc_output
79 |
80 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
81 | .grunt
82 |
83 | # Bower dependency directory (https://bower.io/)
84 | bower_components
85 |
86 | # node-waf configuration
87 | .lock-wscript
88 |
89 | # Compiled binary addons (https://nodejs.org/api/addons.html)
90 | build/Release
91 |
92 | # Dependency directories
93 | node_modules/
94 | jspm_packages/
95 |
96 | # Typescript v1 declaration files
97 | typings/
98 | examples/
99 |
100 | # Optional npm cache directory
101 | .npm
102 |
103 | # Optional eslint cache
104 | .eslintcache
105 |
106 | # Optional REPL history
107 | .node_repl_history
108 |
109 | # Output of 'npm pack'
110 | *.tgz
111 |
112 | # Yarn Integrity file
113 | .yarn-integrity
114 |
115 | # dotenv environment variables file
116 | .env
117 | src/
118 | build/test/
119 | test/
120 | task/
121 | tsconfig.json
122 | tsconfig-lint.json
123 | tslint.json
124 | typings.json
125 | build/**/*.js.map
126 | custom-typings/
127 | .travis.yml
128 | .coveralls.yml
129 | CONTRIBUTING.md
130 | notes.md
131 | documentation/
132 | puppeteer-request-spy.iml
133 |
--------------------------------------------------------------------------------
/examples/fake-test.spec.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer');
2 | const RequestInterceptor = require('puppeteer-request-spy').RequestInterceptor;
3 | const ResponseFaker = require('puppeteer-request-spy').ResponseFaker;
4 | const minimatch = require('minimatch');
5 | const assert = require('assert');
6 | const fs = require('fs');
7 |
8 | let browser;
9 |
10 | before(async () => {
11 | browser = await puppeteer.launch({
12 | headless: true
13 | });
14 | });
15 |
16 | after(async () => {
17 | await browser.close();
18 | });
19 |
20 | describe('example-block', function () {
21 |
22 | this.timeout(30000);
23 | this.slow(10000);
24 |
25 | let requestInterceptor;
26 | let defaultPicture;
27 | let jsonResponseFaker;
28 | let imageResponseFaker;
29 | let textResponseFaker;
30 | let htmlResponseFaker;
31 |
32 | before(() => {
33 | requestInterceptor = new RequestInterceptor(minimatch, console);
34 | defaultPicture = fs.readFileSync('./some-picture.png');
35 | imageResponseFaker = new ResponseFaker('**/*.jpg', {
36 | status: 200,
37 | contentType: 'image/png',
38 | body: defaultPicture
39 | });
40 |
41 | textResponseFaker = new ResponseFaker('**/some-path', {
42 | status: 200,
43 | contentType: 'text/plain',
44 | body: 'some static text'
45 | });
46 |
47 | htmlResponseFaker = new ResponseFaker('**/some-path', {
48 | status: 200,
49 | contentType: 'text/html',
50 | body: 'some static html
'
51 | });
52 |
53 | jsonResponseFaker = new ResponseFaker('**/*.json', {
54 | status: 200,
55 | contentType: 'application/json',
56 | body: JSON.stringify({data: []})
57 | });
58 |
59 | requestInterceptor.addFaker(imageResponseFaker);
60 | requestInterceptor.addFaker(textResponseFaker);
61 | requestInterceptor.addFaker(htmlResponseFaker);
62 | requestInterceptor.addFaker(jsonResponseFaker);
63 | });
64 |
65 | describe('example-block', function () {
66 | it('example-test', async function () {
67 | let page = await browser.newPage();
68 |
69 | page.setRequestInterception(true);
70 | page.on('request', requestInterceptor.intercept.bind(requestInterceptor));
71 |
72 | await page.goto('https://www.example.org', {
73 | waitUntil: 'networkidle0',
74 | timeout: 3000000
75 | });
76 |
77 | let ajaxContent = await page.evaluate(() => {
78 | return document.getElementById('some-id').innerHTML;
79 | });
80 |
81 | assert.strictEqual(ajaxContent, 'some static html
');
82 | });
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/ResponseModifier.ts:
--------------------------------------------------------------------------------
1 | import {Request, RespondOptions} from 'puppeteer';
2 | import {HttpRequestFactory} from './common/HttpRequestFactory';
3 | import {resolveOptionalPromise} from './common/resolveOptionalPromise';
4 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
5 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
6 | import {IRequestFactory} from './interface/IRequestFactory';
7 | import {IResponseFaker} from './interface/IResponseFaker';
8 | import {RequestMatcher} from './types/RequestMatcher';
9 | import {ResponseModifierCallBack} from './types/ResponseModifierCallBack';
10 |
11 | export class ResponseModifier implements IResponseFaker {
12 | private patterns: Array;
13 | private responseModifierCallBack: ResponseModifierCallBack;
14 | private httpRequestFactory: IRequestFactory;
15 |
16 | public constructor(
17 | patterns: Array | string,
18 | responseModifierCallBack: ResponseModifierCallBack,
19 | httpRequestFactory: IRequestFactory = (new HttpRequestFactory())
20 | ) {
21 | if (typeof patterns !== 'string' && patterns.constructor !== Array) {
22 | throw new Error('invalid pattern, pattern must be of type string or string[].');
23 | }
24 |
25 | if (typeof patterns === 'string') {
26 | patterns = [patterns];
27 | }
28 |
29 | this.patterns = patterns;
30 | this.responseModifierCallBack = responseModifierCallBack;
31 | this.httpRequestFactory = httpRequestFactory;
32 | }
33 |
34 | public isMatchingRequest(request: Request, matcher: RequestMatcher): boolean {
35 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
36 | for (let pattern of this.patterns) {
37 | if (matcher(urlAccessor.getUrlFromRequest(request), pattern)) {
38 | return true;
39 | }
40 | }
41 |
42 | return false;
43 | }
44 |
45 | public async getResponseFake(request: Request): Promise {
46 | let originalResponse: RespondOptions = {};
47 | let error: Error | undefined;
48 | let body: string;
49 |
50 | try {
51 | originalResponse = await this.httpRequestFactory.createRequest(request);
52 | body = originalResponse.body;
53 | } catch (err) {
54 | error = err;
55 | body = '';
56 | }
57 |
58 | return Object.assign(
59 | {},
60 | originalResponse,
61 | {
62 | body: await resolveOptionalPromise(this.responseModifierCallBack(
63 | error,
64 | body,
65 | request
66 | ))
67 | }
68 | );
69 | }
70 |
71 | public getPatterns(): Array {
72 | return this.patterns;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/test/common/testServer.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as Koa from 'koa';
3 | import {ExtendableContext} from 'koa';
4 | import * as bodyParser from 'koa-bodyparser';
5 | import * as mime from 'mime-types';
6 | import * as path from 'path';
7 | import {serverSettings} from './ServerSettings';
8 | import Timer = NodeJS.Timer;
9 |
10 |
11 | function buildTestServer(): Koa {
12 | let app: Koa = new Koa();
13 |
14 | app.use(bodyParser());
15 |
16 |
17 | async function wait(time: number): Promise {
18 | return new Promise(((resolve: () => void): Timer => setTimeout(resolve, time)));
19 | }
20 |
21 |
22 | app.use(async (ctx: ExtendableContext, next: () => Promise) => {
23 | await next();
24 |
25 | if (ctx.path === '/test-post-unavailable') {
26 | await wait(20);
27 | }
28 | });
29 |
30 |
31 | app.use(async (ctx: ExtendableContext, next: () => Promise) => {
32 | await next();
33 | if (!ctx.body) {
34 | ctx.body = 'ServerDouble: 404!';
35 | }
36 | });
37 |
38 |
39 | app.use(async (ctx: ExtendableContext, next: () => Promise) => {
40 | const ROOT_DIR: string = path.resolve(global.process.cwd(), serverSettings.rootPath);
41 | let requestPath: string = path.join(ROOT_DIR, ctx.path.replace('/fakes/', ''));
42 |
43 | if (fs.existsSync(requestPath)) {
44 | ctx.type = mime.lookup(requestPath) || 'text/plain';
45 | ctx.body = fs.readFileSync(requestPath);
46 | }
47 |
48 | await next();
49 | });
50 |
51 |
52 | app.use(async (ctx: ExtendableContext, next: () => Promise) => {
53 |
54 | if (ctx.path === '/test-post-fake') {
55 | ctx.status = 200;
56 | ctx.type = 'application/json';
57 | ctx.body = JSON.stringify({a: ctx.request.body.a * 1, n: ctx.request.body.n * 1}, null, 2);
58 |
59 | return;
60 | }
61 |
62 | if (ctx.path === '/test-post-real') {
63 | ctx.status = 200;
64 | ctx.type = 'application/json';
65 | ctx.body = JSON.stringify({a: ctx.request.body.a * 100, n: ctx.request.body.n * 100}, null, 2);
66 |
67 | return;
68 | }
69 |
70 | await next();
71 | });
72 |
73 | app.use(async (ctx: ExtendableContext) => {
74 | if (ctx.path === '/show-headers-real') {
75 | ctx.status = 200;
76 | ctx.type = 'application/json';
77 | ctx.body = JSON.stringify(ctx.request.headers, null, 2);
78 | }
79 |
80 | if (ctx.path === '/show-headers-fake') {
81 | ctx.status = 200;
82 | ctx.type = 'application/json';
83 | ctx.body = JSON.stringify(ctx.request.headers, null, 2);
84 | }
85 |
86 | return;
87 | });
88 |
89 | return app;
90 | }
91 |
92 |
93 | export const testServer: Koa = buildTestServer();
94 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | # User-specific stuff:
7 | .idea/**/workspace.xml
8 | .idea/**/tasks.xml
9 | .idea/dictionaries
10 | .idea/sonarlint/
11 | .idea/StickySelectionHighlights.xml
12 | .idea/deployment.xml
13 |
14 | # Sensitive or high-churn files:
15 | .idea/**/dataSources/
16 | .idea/**/dataSources.ids
17 | .idea/**/dataSources.xml
18 | .idea/**/dataSources.local.xml
19 | .idea/**/sqlDataSources.xml
20 | .idea/**/dynamic.xml
21 | .idea/**/uiDesigner.xml
22 |
23 | # Gradle:
24 | .idea/**/gradle.xml
25 | .idea/**/libraries
26 |
27 | # CMake
28 | cmake-build-debug/
29 |
30 | # Mongo Explorer plugin:
31 | .idea/**/mongoSettings.xml
32 |
33 | ## File-based project format:
34 | *.iws
35 |
36 | ## Plugin-specific files:
37 |
38 | # IntelliJ
39 | out/
40 |
41 | # mpeltonen/sbt-idea plugin
42 | .idea_modules/
43 |
44 | # JIRA plugin
45 | atlassian-ide-plugin.xml
46 |
47 | # Cursive Clojure plugin
48 | .idea/replstate.xml
49 |
50 | # Crashlytics plugin (for Android Studio and IntelliJ)
51 | com_crashlytics_export_strings.xml
52 | crashlytics.properties
53 | crashlytics-build.properties
54 | fabric.properties
55 | ### Windows template
56 | # Windows thumbnail cache files
57 | Thumbs.db
58 | ehthumbs.db
59 | ehthumbs_vista.db
60 |
61 | # Dump file
62 | *.stackdump
63 |
64 | # Folder config file
65 | Desktop.ini
66 |
67 | # Recycle Bin used on file shares
68 | $RECYCLE.BIN/
69 |
70 | # Windows Installer files
71 | *.cab
72 | *.msi
73 | *.msm
74 | *.msp
75 |
76 | # Windows shortcuts
77 | *.lnk
78 | ### Node template
79 | # Logs
80 | logs
81 | *.log
82 | npm-debug.log*
83 | yarn-debug.log*
84 | yarn-error.log*
85 |
86 | # Runtime data
87 | pids
88 | *.pid
89 | *.seed
90 | *.pid.lock
91 |
92 | # Directory for instrumented libs generated by jscoverage/JSCover
93 | lib-cov
94 |
95 | # Coverage directory used by tools like istanbul
96 | coverage
97 |
98 | # nyc test coverage
99 | .nyc_output
100 |
101 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
102 | .grunt
103 |
104 | # Bower dependency directory (https://bower.io/)
105 | bower_components
106 |
107 | # node-waf configuration
108 | .lock-wscript
109 |
110 | # Compiled binary addons (https://nodejs.org/api/addons.html)
111 | build/Release
112 |
113 | # Dependency directories
114 | node_modules/
115 | jspm_packages/
116 | .coveralls.yml
117 | # Optional npm cache directory
118 | .npm
119 |
120 | # Optional eslint cache
121 | .eslintcache
122 |
123 | # Optional REPL history
124 | .node_repl_history
125 |
126 | # Output of 'npm pack'
127 | *.tgz
128 |
129 | # Yarn Integrity file
130 | .yarn-integrity
131 |
132 | # dotenv environment variables file
133 | .env
134 |
135 | build/
136 | notes.md
137 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "puppeteer-request-spy",
3 | "version": "1.4.0",
4 | "description": "watch, fake, modify or block requests from puppeteer matching patterns",
5 | "main": "build/src/index.js",
6 | "scripts": {
7 | "clean": "node task/clean.js",
8 | "pregenerate-typings": "npm run clean",
9 | "generate-typings": "tsc -p tsconfig.json -d",
10 | "preupdate-typings": "npm run generate-typings",
11 | "update-typings": "node task/copyTypes.js",
12 | "prebuild": "node_modules/.bin/tslint --project tsconfig-lint.json && npm run clean",
13 | "prebuild-dev": "npm run prebuild",
14 | "build": "node_modules/.bin/tsc -p tsconfig.json",
15 | "build-dev": "node_modules/.bin/tsc -p tsconfig.json --sourcemap",
16 | "pretest": "npm run build",
17 | "test": "node_modules/.bin/mocha --timeout 10000 --require source-map-support/register build/test/**/*.spec.js",
18 | "pretest-coverage": "npm run build-dev",
19 | "pretest-silent-full": "npm run build",
20 | "test-silent-full": "node_modules/.bin/mocha --timeout 10000 --require source-map-support/register build/test/**/*.spec.js > test-ts.log",
21 | "test-coverage": "node_modules/.bin/nyc --all --reporter=html npm run test-silent",
22 | "pretest-coverage-travis": "npm run build-dev",
23 | "test-silent": "node_modules/.bin/mocha --timeout 10000 --require source-map-support/register build/test/**/*.dev.spec.js > test-ts.log",
24 | "test-coverage-travis": "node_modules/.bin/nyc --all --reporter=text-lcov npm run test-silent | node node_modules/coveralls/bin/coveralls.js",
25 | "prerelease-dry": "npm run update-typings && npm run test-silent-full",
26 | "release-dry": "npm pack"
27 | },
28 | "engines": {
29 | "node": ">=6.4.0"
30 | },
31 | "nyc": {
32 | "extension": [
33 | ".ts",
34 | ".tsx"
35 | ],
36 | "include": [
37 | "build/src/**/*.js"
38 | ],
39 | "exclude": [
40 | "**/Logger.js",
41 | "**/index.js",
42 | "**/interface/*.js",
43 | "**/types/*.js"
44 | ],
45 | "reporter": [
46 | "html"
47 | ],
48 | "all": true
49 | },
50 | "keywords": [
51 | "puppeteer",
52 | "request",
53 | "spy",
54 | "testing",
55 | "test",
56 | "chrome",
57 | "headless"
58 | ],
59 | "types": "types/index.d.ts",
60 | "author": "Tobias Nießen",
61 | "license": "MIT",
62 | "devDependencies": {
63 | "@types/fs-extra": "^5.0.4",
64 | "@types/koa": "^2.11.4",
65 | "@types/koa-bodyparser": "^4.3.0",
66 | "@types/mime-types": "^2.1.0",
67 | "@types/minimatch": "^3.0.3",
68 | "@types/mocha": "^5.2.0",
69 | "@types/nock": "^9.3.1",
70 | "@types/node": "^10.0.4",
71 | "@types/puppeteer": "^1.3.1",
72 | "@types/sinon": "^4.3.1",
73 | "clear-module": "^3.1.0",
74 | "coveralls": "^3.0.1",
75 | "del": "^3.0.0",
76 | "fs-extra": "^7.0.1",
77 | "koa": "^2.13.0",
78 | "koa-bodyparser": "^4.3.0",
79 | "mime-types": "^2.1.27",
80 | "minimatch": "^3.0.4",
81 | "mocha": "^5.1.1",
82 | "nock": "^10.0.6",
83 | "nyc": "^11.7.1",
84 | "puppeteer": "^1.20.0",
85 | "sinon": "^4.5.0",
86 | "source-map-support": "^0.5.5",
87 | "ts-node": "^7.0.1",
88 | "tslint": "^5.8.0",
89 | "tslint-microsoft-contrib": "^5.0.1",
90 | "typescript": "^3.3.3333"
91 | },
92 | "repository": {
93 | "type": "git",
94 | "url": "https://github.com/Tabueeee/puppeteer-request-spy.git"
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/common/HttpRequestFactory.ts:
--------------------------------------------------------------------------------
1 | import {ClientRequest, IncomingHttpHeaders, IncomingMessage, request as httpRequest, RequestOptions} from 'http';
2 | import {request as httpsRequest} from 'https';
3 | import {Request, RespondOptions} from 'puppeteer';
4 | import {URL} from 'url';
5 | import {IRequestFactory} from '../interface/IRequestFactory';
6 | import {UrlAccessor} from './urlAccessor/UrlAccessor';
7 | import {UrlAccessorResolver} from './urlAccessor/UrlAccessorResolver';
8 |
9 | export class HttpRequestFactory implements IRequestFactory {
10 | private timeout: number;
11 |
12 | public constructor(timeout: number = 30000) {
13 | this.timeout = timeout;
14 | }
15 |
16 | public createRequest(request: Request): Promise {
17 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(request);
18 | let urlString: string = urlAccessor.getUrlFromRequest(request);
19 |
20 | return new Promise((resolve: (options: RespondOptions & { body: string }) => void, reject: (error: Error) => void): void => {
21 | let url: URL = new URL(urlString);
22 |
23 | let headers: { [index: string]: string } = {};
24 | Object.assign(headers, request.headers());
25 |
26 | let options: RequestOptions = {
27 | protocol: url.protocol,
28 | method: request.method(),
29 | hostname: url.hostname,
30 | port: url.port,
31 | path: url.pathname + url.search,
32 | headers: headers
33 | };
34 |
35 | let timeout: NodeJS.Timeout;
36 | let requestInterface: typeof httpsRequest = url.protocol === 'https:' ? httpsRequest : httpRequest;
37 |
38 | let req: ClientRequest = requestInterface(options, (res: IncomingMessage) => {
39 |
40 | let chunks: Array = [];
41 |
42 | res.on('data', (chunk: Buffer) => {
43 | chunks.push(chunk);
44 | });
45 |
46 | res.on('end', () => {
47 | let body: Buffer = Buffer.concat(chunks);
48 |
49 | clearTimeout(timeout);
50 |
51 | resolve(
52 | {
53 | body: body.toString(),
54 | contentType: res.headers['content-type'],
55 | status: res.statusCode,
56 | headers: Object
57 | .keys(res.headers)
58 | .reduce(this.convertHeaders.bind(this, res.headers), {})
59 | }
60 | );
61 | });
62 | });
63 |
64 | timeout = setTimeout(
65 | () => {
66 | req.end();
67 | reject(new Error(`unable to load: ${url}. request timed out after ${this.timeout / 1000} seconds.`));
68 | },
69 | this.timeout
70 | );
71 |
72 | req.end();
73 | });
74 | }
75 |
76 | private convertHeaders(
77 | responseHeaders: IncomingHttpHeaders,
78 | prev: { [index: string]: string },
79 | key: string
80 | ): { [index: string]: string } {
81 | let currentHeader: string | Array | undefined = responseHeaders[key];
82 |
83 | if (typeof currentHeader === 'string') {
84 | prev[key] = currentHeader;
85 | } else if (Array.isArray(currentHeader)) {
86 | prev[key] = currentHeader.join(', ');
87 | } else {
88 | prev[key] = '';
89 | }
90 |
91 | return prev;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/test/isolation/ResponseFaker.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import {Request, RespondOptions} from 'puppeteer';
3 | import {ResponseFaker} from '../../src/ResponseFaker';
4 | import {getRequestDouble} from '../common/testDoubleFactories';
5 |
6 | describe('class: ResponseFaker', (): void => {
7 | describe('happy path', (): void => {
8 |
9 | it('accepts single string pattern', (): void => {
10 | assert.doesNotThrow((): void => {
11 | // noinspection TsLint
12 | new ResponseFaker('some-pattern/**/*', {});
13 | });
14 | });
15 |
16 | it('accepts multiple string patterns as array', (): void => {
17 | assert.doesNotThrow(() => {
18 | // noinspection TsLint
19 | new ResponseFaker(['some-pattern/**/*', 'some-pattern/**/*', 'some-pattern/**/*'], {});
20 | });
21 | });
22 |
23 | it('returns accepted fake', async (): Promise => {
24 | let responseFaker: ResponseFaker = new ResponseFaker('some-pattern/**/*', {
25 | status: 200,
26 | contentType: 'text/plain',
27 | body: 'payload'
28 | });
29 |
30 | assert.deepStrictEqual(await responseFaker.getResponseFake(( getRequestDouble())), {
31 | status: 200,
32 | contentType: 'text/plain',
33 | body: 'payload'
34 | });
35 | });
36 |
37 |
38 | it('returns accepted fake', async (): Promise => {
39 | let responseFaker: ResponseFaker = new ResponseFaker('some-pattern/**/*', (): RespondOptions => ({
40 | status: 200,
41 | contentType: 'text/plain',
42 | body: 'payload'
43 | }));
44 |
45 | assert.deepStrictEqual(await responseFaker.getResponseFake(( getRequestDouble())), {
46 | status: 200,
47 | contentType: 'text/plain',
48 | body: 'payload'
49 | });
50 | });
51 |
52 | it('returns accepted patterns', (): void => {
53 | let responseFaker: ResponseFaker = new ResponseFaker('some-pattern/**/*', {
54 | status: 200,
55 | contentType: 'text/plain',
56 | body: 'payload'
57 | });
58 |
59 | assert.deepStrictEqual(responseFaker.getPatterns(), ['some-pattern/**/*']);
60 | });
61 |
62 | it('confirms request matches when matcher function matches', (): void => {
63 | let responseFaker: ResponseFaker = new ResponseFaker('some-pattern/**/*', {
64 | status: 200,
65 | contentType: 'text/plain',
66 | body: 'payload'
67 | });
68 |
69 | assert.deepStrictEqual(responseFaker.isMatchingRequest(({url: (): string => ''}), () => true), true);
70 | });
71 |
72 | it('confirms request does not matches when matcher function does not match', (): void => {
73 | let responseFaker: ResponseFaker = new ResponseFaker('some-pattern/**/*', {
74 | status: 200,
75 | contentType: 'text/plain',
76 | body: 'payload'
77 | });
78 |
79 | assert.deepStrictEqual(responseFaker.isMatchingRequest(({url: (): string => ''}), () => false), false);
80 | });
81 | });
82 |
83 | describe('sad path', (): void => {
84 | it('rejects other input', (): void => {
85 | assert.throws((): void => {
86 | // @ts-ignore: ignore error to test invalid input from js
87 | new ResponseFaker(3, {}).getPatterns();
88 | });
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/test/isolation/RequestModifier.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import {Request} from 'puppeteer';
3 | import {RequestModifier} from '../../src/RequestModifier';
4 | import {getRequestDouble} from '../common/testDoubleFactories';
5 |
6 | describe('class: RequestModifier', (): void => {
7 | describe('happy path', (): void => {
8 |
9 | it('accepts single string pattern', (): void => {
10 | assert.doesNotThrow((): void => {
11 | // noinspection TsLint
12 | new RequestModifier('some-pattern/**/*', {});
13 | });
14 | });
15 |
16 | it('accepts multiple string patterns as array', (): void => {
17 | assert.doesNotThrow(() => {
18 | // noinspection TsLint
19 | new RequestModifier(['some-pattern/**/*', 'some-pattern/**/*', 'some-pattern/**/*'], {});
20 | });
21 | });
22 |
23 | it('returns accepted fake', async (): Promise => {
24 | let requestModifier: RequestModifier = new RequestModifier('some-pattern/**/*', {
25 | url: '',
26 | method: 'GET',
27 | postData: '',
28 | headers: {}
29 | });
30 |
31 | assert.deepStrictEqual(await requestModifier.getOverride( getRequestDouble()), {
32 | url: '',
33 | method: 'GET',
34 | postData: '',
35 | headers: {}
36 | });
37 | });
38 |
39 |
40 | it('returns accepted fake', async (): Promise => {
41 | let requestModifier: RequestModifier = new RequestModifier('some-pattern/**/*', (request: Request) => ({
42 | url: request.url(),
43 | method: 'GET',
44 | postData: '',
45 | headers: {}
46 | }));
47 |
48 | assert.deepStrictEqual(await requestModifier.getOverride( getRequestDouble()), {
49 | url: 'any-url',
50 | method: 'GET',
51 | postData: '',
52 | headers: {}
53 | });
54 | });
55 |
56 | it('returns accepted patterns', (): void => {
57 | let requestModifier: RequestModifier = new RequestModifier('some-pattern/**/*', {
58 | url: '',
59 | method: 'GET',
60 | postData: '',
61 | headers: {}
62 | });
63 |
64 | assert.deepStrictEqual(requestModifier.getPatterns(), ['some-pattern/**/*']);
65 | });
66 |
67 | it('confirms request matches when matcher function matches', (): void => {
68 | let requestModifier: RequestModifier = new RequestModifier('some-pattern/**/*', {
69 | url: '',
70 | method: 'GET',
71 | postData: '',
72 | headers: {}
73 | });
74 |
75 | assert.deepStrictEqual(requestModifier.isMatchingRequest(({url: (): string => ''}), () => true), true);
76 | });
77 |
78 | it('confirms request does not matches when matcher function does not match', (): void => {
79 | let requestModifier: RequestModifier = new RequestModifier('some-pattern/**/*', {
80 | url: '',
81 | method: 'GET',
82 | postData: '',
83 | headers: {}
84 | });
85 |
86 | assert.deepStrictEqual(requestModifier.isMatchingRequest(({url: (): string => ''}), () => false), false);
87 | });
88 | });
89 |
90 | describe('sad path', (): void => {
91 | it('rejects other input', (): void => {
92 | assert.throws((): void => {
93 | // @ts-ignore: ignore error to test invalid input from js
94 | new RequestModifier(3, {}).getPatterns();
95 | });
96 | });
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/test/isolation/RequestSpy.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import {Request} from 'puppeteer';
3 | import {RequestSpy} from '../../src/RequestSpy';
4 |
5 | describe('class: RequestSpy', (): void => {
6 | describe('happy path', (): void => {
7 | it('accepts single string pattern', (): void => {
8 | assert.doesNotThrow((): void => {
9 | new RequestSpy('some-pattern/**/*').hasMatch();
10 | });
11 | });
12 |
13 | it('accepts multiple string patterns in an array', (): void => {
14 | assert.doesNotThrow(() => {
15 | new RequestSpy(['some-pattern/**/*', 'some-pattern/**/*', 'some-pattern/**/*']).hasMatch();
16 | });
17 | });
18 |
19 | it('returns accepted pattern', (): void => {
20 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
21 | assert.deepStrictEqual(requestSpy.getPatterns(), ['some-pattern/**/*']);
22 | });
23 |
24 | it('multiple matched requests increases matchCount', (): void => {
25 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
26 |
27 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern'});
28 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern_2'});
29 |
30 | assert.strictEqual(requestSpy.getMatchCount(), 2, '');
31 | });
32 |
33 | it('multiple matched requests are stored in matchedUrls', (): void => {
34 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
35 |
36 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern'});
37 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern_2'});
38 |
39 | let matches: Array = requestSpy.getMatchedRequests();
40 |
41 | let expected: Array<{url: string}> = [
42 | {url: 'some-pattern/pattern'},
43 | {url: 'some-pattern/pattern_2'}
44 | ];
45 |
46 | let actual: Array<{url: string}> = [];
47 |
48 | for (let match of matches) {
49 | actual.push({url: match.url()});
50 | }
51 |
52 | assert.deepStrictEqual(
53 | actual,
54 | expected,
55 | 'requestSpy didn\'t add all urls'
56 | );
57 |
58 | assert.deepStrictEqual(requestSpy.getMatchedUrls(), ['some-pattern/pattern', 'some-pattern/pattern_2'], '');
59 | });
60 |
61 | it('multiple matched requests sets matched to true', (): void => {
62 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
63 |
64 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern'});
65 | requestSpy.addMatch( {url: (): string => 'some-pattern/pattern_2'});
66 |
67 | assert.strictEqual(requestSpy.hasMatch(), true, '');
68 | });
69 |
70 | it('confirms request matches when matcher function matches', (): void => {
71 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
72 |
73 | assert.deepStrictEqual(requestSpy.isMatchingRequest(({url: (): string => ''}), () => true), true);
74 | });
75 |
76 | it('confirms request does not matches when matcher function does not match', (): void => {
77 | let requestSpy: RequestSpy = new RequestSpy('some-pattern/**/*');
78 |
79 | assert.deepStrictEqual(requestSpy.isMatchingRequest(({url: (): string => ''}), () => false), false);
80 | });
81 | });
82 |
83 | describe('sad path', (): void => {
84 | it('rejects other input', (): void => {
85 | assert.throws((): void => {
86 | // @ts-ignore: ignore error to test invalid input from js
87 | new RequestSpy(3).hasMatch();
88 | });
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/test/isolation/RequestRedirector.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import {Request} from 'puppeteer';
3 | import {HttpRequestFactory} from '../../src/common/HttpRequestFactory';
4 | import {RequestRedirector} from '../../src/RequestRedirector';
5 | import {getHttpRequestFactoryDouble, getRequestDouble} from '../common/testDoubleFactories';
6 |
7 | describe('class: RequestRedirector', (): void => {
8 | describe('happy path', (): void => {
9 |
10 | it('accepts single string pattern', (): void => {
11 | assert.doesNotThrow((): void => {
12 | // noinspection TsLint
13 | new RequestRedirector( 'some-pattern/**/*', (): string => {
14 | return '';
15 | });
16 | });
17 | });
18 |
19 | it('accepts multiple string patterns as array', (): void => {
20 | assert.doesNotThrow(() => {
21 | // noinspection TsLint
22 | new RequestRedirector(['some-pattern/**/*', 'some-pattern/**/*', 'some-pattern/**/*'], '');
23 | });
24 | });
25 |
26 | it('returns accepted fake', async (): Promise => {
27 | let requestRedirector: RequestRedirector = new RequestRedirector(
28 | 'some-pattern/**/*',
29 | (): string => 'some-url'
30 | );
31 |
32 | let request: Request = getRequestDouble();
33 |
34 | assert.deepStrictEqual(await requestRedirector.getOverride(request), {
35 | url: 'some-url',
36 | method: request.method(),
37 | headers: request.headers(),
38 | postData: request.postData()
39 | });
40 | });
41 |
42 |
43 | it('returns accepted fake', async (): Promise => {
44 | let requestRedirector: RequestRedirector = new RequestRedirector(
45 | 'some-pattern/**/*',
46 | 'some-url'
47 | );
48 |
49 | let request: Request = getRequestDouble();
50 |
51 | assert.deepStrictEqual(await requestRedirector.getOverride(request), {
52 | url: 'some-url',
53 | method: request.method(),
54 | headers: request.headers(),
55 | postData: request.postData()
56 | });
57 | });
58 |
59 | it('returns accepted patterns', (): void => {
60 | let requestRedirector: RequestRedirector = new RequestRedirector(
61 | 'some-pattern/**/*',
62 | (): string => 'some-url'
63 | );
64 |
65 | assert.deepStrictEqual(requestRedirector.getPatterns(), ['some-pattern/**/*']);
66 | });
67 |
68 | it('confirms request matches when matcher function matches', (): void => {
69 | let requestRedirector: RequestRedirector = new RequestRedirector(
70 | 'some-pattern/**/*',
71 | (): string => 'some-url'
72 | );
73 |
74 | assert.deepStrictEqual(requestRedirector.isMatchingRequest(({url: (): string => ''}), () => true), true);
75 | });
76 |
77 | it('confirms request does not matches when matcher function does not match', (): void => {
78 | let requestRedirector: RequestRedirector = new RequestRedirector(
79 | 'some-pattern/**/*',
80 | (): string => 'some-url'
81 | );
82 |
83 | assert.deepStrictEqual(requestRedirector.isMatchingRequest(({url: (): string => ''}), () => false), false);
84 | });
85 | });
86 |
87 | describe('sad path', (): void => {
88 | it('rejects other input', (): void => {
89 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
90 | assert.throws((): void => {
91 | // @ts-ignore: ignore error to test invalid input from js
92 | new RequestRedirector(httpRequestFactory, 3, {}).getPatterns();
93 | });
94 | });
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/test/unit/unitTestHelpers.ts:
--------------------------------------------------------------------------------
1 | import {Overrides, Request, RespondOptions} from 'puppeteer';
2 | import {RequestInterceptor, RequestModifier, RequestRedirector, ResponseModifier} from '../../src';
3 | import {HttpRequestFactory} from '../../src/common/HttpRequestFactory';
4 | import {RequestSpy} from '../../src/RequestSpy';
5 | import {ResponseFaker} from '../../src/ResponseFaker';
6 | import {
7 | getRequestDouble,
8 | respondingNock
9 | } from '../common/testDoubleFactories';
10 |
11 | export module unitTestHelpers {
12 |
13 | export type RequestHandlers = {
14 | requestSpy: RequestSpy,
15 | responseFaker: ResponseFaker,
16 | requestModifier: RequestModifier,
17 | responseModifier: ResponseModifier,
18 | requestRedirector: RequestRedirector
19 | };
20 |
21 |
22 | export function getRequestDoubles(): Array {
23 | let requestMatchingSpy: Request = getRequestDouble('spy');
24 | let requestMatchingFaker: Request = getRequestDouble('faker');
25 | let requestMatchingBlocker: Request = getRequestDouble('blocker');
26 | let requestMatchingRequestModifier: Request = getRequestDouble('modifier');
27 | // noinspection TsLint
28 | let requestMatchingRequestRedirector: Request = getRequestDouble('http://www.some-domain.com/requestRedirector', {
29 | nock: respondingNock.bind(null, 'requestRedirector', 'redirected'),
30 | requestCount: 3
31 | });
32 | let requestMatchingResponseModifier: Request = getRequestDouble('http://www.example.com/responseModifier', {
33 | nock: respondingNock.bind(null, 'responseModifier', 'original'),
34 | requestCount: 3
35 | });
36 |
37 | return [
38 | requestMatchingSpy,
39 | requestMatchingFaker,
40 | requestMatchingBlocker,
41 | requestMatchingRequestModifier,
42 | requestMatchingResponseModifier,
43 | requestMatchingRequestRedirector
44 | ];
45 | }
46 |
47 |
48 | export function createRequestHandlers(
49 | responseFake: RespondOptions,
50 | overrides: Overrides
51 | ): RequestHandlers {
52 | let requestSpy: RequestSpy = new RequestSpy('spy');
53 | let responseFaker: ResponseFaker = new ResponseFaker('faker', responseFake);
54 | let requestModifier: RequestModifier = new RequestModifier('modifier', overrides);
55 | let responseModifier: ResponseModifier = new ResponseModifier(
56 | 'responseModifier',
57 | (err: Error | undefined, response: string): string => err ? err.toString() : response.replace(' body', ''),
58 | new HttpRequestFactory()
59 | );
60 |
61 | let requestRedirector: RequestRedirector = new RequestRedirector(
62 | 'requestRedirector',
63 | (request: Request): string => {
64 | return request.url().replace('some-domain', 'example');
65 | }
66 | );
67 |
68 | return {
69 | requestSpy,
70 | responseFaker,
71 | requestModifier,
72 | responseModifier,
73 | requestRedirector
74 | };
75 | }
76 |
77 | export function addRequestHandlers(requestInterceptor: RequestInterceptor, requestHandlers: RequestHandlers): void {
78 | requestInterceptor.block('blocker');
79 | requestInterceptor.addSpy(requestHandlers.requestSpy);
80 | requestInterceptor.addFaker(requestHandlers.responseFaker);
81 | requestInterceptor.addRequestModifier(requestHandlers.requestModifier);
82 | requestInterceptor.addFaker(requestHandlers.responseModifier);
83 | requestInterceptor.addRequestModifier(requestHandlers.requestRedirector);
84 | }
85 |
86 | export async function simulateUsage(requestInterceptor: RequestInterceptor, requestDoubles: Array): Promise {
87 | for (let requestDouble of requestDoubles) {
88 | await requestInterceptor.intercept(requestDouble);
89 | await requestInterceptor.intercept(requestDouble);
90 | await requestInterceptor.intercept(requestDouble);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/test/common/testDoubleFactories.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 | import {Overrides, Request, RespondOptions} from 'puppeteer';
3 | import * as sinon from 'sinon';
4 | import {SinonSpy} from 'sinon';
5 | import {RequestModifier} from '../../src';
6 | import {HttpRequestFactory} from '../../src/common/HttpRequestFactory';
7 | import {ILogger} from '../../src/common/Logger';
8 | import {RequestSpy} from '../../src/RequestSpy';
9 | import {ResponseFaker} from '../../src/ResponseFaker';
10 | import {serverSettings} from './ServerSettings';
11 | import {TestDouble} from './TestDouble';
12 |
13 | export function getRequestSpyDouble(matches: boolean): TestDouble {
14 | return {
15 | isMatchingRequest: sinon.stub().returns(matches),
16 | addMatch: sinon.spy(),
17 | hasMatch: undefined,
18 | getMatchedUrls: undefined,
19 | getMatchCount: undefined
20 | };
21 | }
22 |
23 | export function getRequestModifierDouble(matches: boolean, override: Overrides): TestDouble {
24 | return {
25 | isMatchingRequest: sinon.stub().returns(matches),
26 | getOverride: sinon.stub().returns(override)
27 | };
28 | }
29 |
30 | export function getRequestDouble(url: string = 'any-url', requestMock?: { nock(): void, requestCount: number }): TestDouble {
31 | if (typeof requestMock !== 'undefined') {
32 | for (let index: number = 0; index < requestMock.requestCount; index++) {
33 | requestMock.nock();
34 | }
35 | }
36 |
37 | return {
38 | continue: sinon.spy(),
39 | abort: sinon.spy(),
40 | respond: sinon.spy(),
41 | url: (): string => url,
42 | method: (): string => 'GET',
43 | failure: (): boolean => false,
44 | headers: (): { [index: string]: string } => ({
45 | 'test-header-single': 'val',
46 | 'test-header-multi': 'val, val2',
47 | 'text-header-empty': ''
48 | }),
49 | postData: (): { [index: string]: string } => ({})
50 | };
51 | }
52 |
53 | export function getHttpRequestFactoryDouble(fakeResponse: string, spy?: SinonSpy): TestDouble {
54 | return {
55 | createRequest: (request: Request): RespondOptions => {
56 | if (typeof spy !== 'undefined') {
57 | spy(request);
58 | }
59 |
60 | return {
61 | status: 200,
62 | body: fakeResponse,
63 | contentType: 'text/plain'
64 | };
65 | }
66 | };
67 | }
68 |
69 | export function getLowVersionRequestDouble(): TestDouble {
70 | return {
71 | continue: sinon.spy(),
72 | abort: sinon.spy(),
73 | respond: sinon.spy(),
74 | url: 'any-url'
75 | };
76 | }
77 |
78 | export function getResponseFakerDouble(matches: boolean): TestDouble {
79 | return {
80 | getResponseFake: sinon.spy(),
81 | isMatchingRequest: sinon.stub().returns(matches)
82 | };
83 | }
84 |
85 | export function getErrorRequestDouble(): TestDouble {
86 | return {
87 | continue: async (): Promise => {
88 | throw new Error('requestInterception is not set');
89 | },
90 | abort: async (): Promise => {
91 | throw new Error('requestInterception is not set');
92 | },
93 | respond: async (): Promise => {
94 | throw new Error('requestInterception is not set');
95 | },
96 | url: (): string => 'any-url'
97 | };
98 | }
99 |
100 | const FAVICON_URL: string = `http://${serverSettings.host}/favicon.ico`;
101 |
102 | export function getLoggerFake(arrayPointer: Array): ILogger {
103 | return {
104 | log: (log: string): void => {
105 | if (log !== FAVICON_URL) {
106 | arrayPointer.push(log);
107 | }
108 | }
109 | };
110 | }
111 |
112 | export function respondingNock(path: string, bodyPrefix: string): void {
113 | nock('http://www.example.com')
114 | .get(`/${path}`)
115 | .reply(200, `${bodyPrefix} response body`, {'content-type': 'text/html'});
116 | }
117 |
--------------------------------------------------------------------------------
/test/isolation/ResponseModifier.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import {Request} from 'puppeteer';
3 | import * as sinon from 'sinon';
4 | import {HttpRequestFactory} from '../../src/common/HttpRequestFactory';
5 | import {ResponseModifier} from '../../src/ResponseModifier';
6 | import {getHttpRequestFactoryDouble, getRequestDouble} from '../common/testDoubleFactories';
7 |
8 | describe('class: ResponseModifier', (): void => {
9 | describe('happy path', (): void => {
10 |
11 | it('accepts single string pattern', (): void => {
12 | assert.doesNotThrow((): void => {
13 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
14 | // noinspection TsLint
15 | new ResponseModifier(
16 | 'some-pattern/**/*',
17 | (): any => {
18 | return {};
19 | },
20 | httpRequestFactory
21 | );
22 | });
23 | });
24 |
25 | it('accepts multiple string patterns as array', (): void => {
26 | assert.doesNotThrow(() => {
27 | // noinspection TsLint
28 | new ResponseModifier(
29 | ['some-pattern/**/*', 'some-pattern/**/*', 'some-pattern/**/*'],
30 | (): any => {
31 | return {};
32 | }
33 | );
34 | });
35 | });
36 |
37 | it('returns accepted fake', async (): Promise => {
38 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('payload');
39 | let responseModifier: ResponseModifier = new ResponseModifier(
40 | 'some-pattern/**/*',
41 | (err: Error | undefined, response: string): string => err ? err.toString() : `${response}1`,
42 | httpRequestFactory
43 | );
44 |
45 | let request: Request = getRequestDouble();
46 |
47 | assert.deepStrictEqual(await responseModifier.getResponseFake(request), {
48 | status: 200,
49 | contentType: 'text/plain',
50 | body: 'payload1'
51 | });
52 | });
53 |
54 | it('returns accepted patterns', (): void => {
55 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
56 | let responseModifier: ResponseModifier = new ResponseModifier(
57 | 'some-pattern/**/*',
58 | (): string => 'payload',
59 | httpRequestFactory
60 | );
61 |
62 | assert.deepStrictEqual(responseModifier.getPatterns(), ['some-pattern/**/*']);
63 | });
64 |
65 | it('confirms request matches when matcher function matches', (): void => {
66 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
67 | let responseModifier: ResponseModifier = new ResponseModifier(
68 | 'some-pattern/**/*',
69 | (): string => 'payload',
70 | httpRequestFactory
71 | );
72 |
73 | assert.deepStrictEqual(responseModifier.isMatchingRequest(({url: (): string => ''}), () => true), true);
74 | });
75 |
76 | it('confirms request does not matches when matcher function does not match', (): void => {
77 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
78 | let responseModifier: ResponseModifier = new ResponseModifier(
79 | 'some-pattern/**/*',
80 | (): string => 'payload',
81 | httpRequestFactory
82 | );
83 |
84 | assert.deepStrictEqual(responseModifier.isMatchingRequest(({url: (): string => ''}), () => false), false);
85 | });
86 | });
87 |
88 | describe('sad path', (): void => {
89 | it('rejects other input', (): void => {
90 | let httpRequestFactory: HttpRequestFactory = getHttpRequestFactoryDouble('any-response');
91 | assert.throws((): void => {
92 | // @ts-ignore: ignore error to test invalid input from js
93 | new ResponseModifier(httpRequestFactory, 3, {}).getPatterns();
94 | });
95 | });
96 |
97 | it('passes error when resource is unavailable', async (): Promise => {
98 | let expectedError: Error = new Error('ERROR!');
99 |
100 | let httpRequestFactory: HttpRequestFactory = ({
101 | createRequest: () => Promise.reject(expectedError)
102 | });
103 |
104 | let modifierCallbackSpy: sinon.SinonSpy = sinon.spy();
105 |
106 | let responseModifier: ResponseModifier = new ResponseModifier(
107 | 'some-pattern/**/*',
108 | (err: Error | undefined, body: string) => {
109 | modifierCallbackSpy(err, body);
110 |
111 | return err ? 'oh no!' : body;
112 | },
113 | httpRequestFactory
114 | );
115 |
116 |
117 | let request: Request = getRequestDouble();
118 |
119 | assert.deepStrictEqual(await responseModifier.getResponseFake(request), {
120 | body: 'oh no!'
121 | });
122 | sinon.assert.callCount(modifierCallbackSpy, 1);
123 | sinon.assert.calledWithExactly(modifierCallbackSpy, expectedError, '');
124 | });
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/test/isolation/HttpRequestFactory.dev.spec.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import * as nock from 'nock';
3 | import {HttpHeaders} from 'nock';
4 | import {Request, RespondOptions} from 'puppeteer';
5 | import {HttpRequestFactory} from '../../src/common/HttpRequestFactory';
6 | import {assertThrowsAsync} from '../common/AssertionHelpers';
7 | import {getRequestDouble} from '../common/testDoubleFactories';
8 |
9 | describe('class: HttpRequestFactory', () => {
10 |
11 | before(() => {
12 | if (!nock.isActive()) {
13 | nock.activate();
14 | }
15 | nock.disableNetConnect();
16 | });
17 |
18 | after(() => {
19 | nock.cleanAll();
20 | nock.enableNetConnect();
21 | nock.restore();
22 | });
23 |
24 | describe('happy path', () => {
25 | it('should create a promise based loader from an url string', async () => {
26 | // noinspection TsLint
27 | nock('http://www.example.com')
28 | .get('/resource')
29 | .reply(200, 'path matched', {'content-type': 'text/plain'});
30 |
31 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory();
32 | let response: RespondOptions = await httpRequestFactory.createRequest(
33 | getRequestDouble('http://www.example.com/resource')
34 | );
35 |
36 | assert.deepStrictEqual(response, {
37 | body: 'path matched',
38 | contentType: 'text/plain',
39 | headers: {
40 | 'content-type': 'text/plain'
41 | },
42 | status: 200
43 | });
44 | });
45 |
46 | it('should create a promise based loader from a https url string', async () => {
47 | // noinspection TsLint
48 | nock('https://www.example.com')
49 | .get('/resource')
50 | .reply(200, 'path matched', {'content-type': 'text/plain'});
51 |
52 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory();
53 | let response: RespondOptions = await httpRequestFactory.createRequest(
54 | getRequestDouble('https://www.example.com/resource')
55 | );
56 |
57 | assert.deepStrictEqual(response, {
58 | body: 'path matched',
59 | contentType: 'text/plain',
60 | headers: {
61 | 'content-type': 'text/plain'
62 | },
63 | status: 200
64 | });
65 | });
66 |
67 |
68 | it('should create a promise based loader from an url string with get params', async () => {
69 | // noinspection TsLint
70 | nock('https://www.example.com')
71 | .get('/resource?some=1&get=params')
72 | .reply(200, 'path matched', {'content-type': 'text/plain'});
73 |
74 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory();
75 | let response: RespondOptions = await httpRequestFactory.createRequest(
76 | getRequestDouble('https://www.example.com/resource?some=1&get=params')
77 | );
78 |
79 | assert.deepStrictEqual(response, {
80 | body: 'path matched',
81 | contentType: 'text/plain',
82 | headers: {
83 | 'content-type': 'text/plain'
84 | },
85 | status: 200
86 | });
87 | });
88 |
89 |
90 | it('should create correct headers from response', async () => {
91 | let headers: HttpHeaders = {
92 | 'content-type': 'text/plain',
93 | 'test-header-single': 'val',
94 | 'test-header-multi': ['val', 'val2'],
95 | 'text-header-empty': undefined
96 | };
97 |
98 | nock('http://www.example.com')
99 | .get('/resource')
100 | .reply(
101 | 200,
102 | () => 'path matched',
103 | headers
104 | );
105 |
106 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory();
107 | let response: RespondOptions = await httpRequestFactory.createRequest(
108 | getRequestDouble('http://www.example.com/resource')
109 | );
110 |
111 | assert.deepStrictEqual(response, {
112 | body: 'path matched',
113 | contentType: 'text/plain',
114 | headers: {
115 | 'content-type': 'text/plain',
116 | 'test-header-single': 'val',
117 | 'test-header-multi': 'val, val2',
118 | 'text-header-empty': ''
119 | },
120 | status: 200
121 | });
122 | });
123 |
124 |
125 | it('should create a promise based loader from a request', async () => {
126 | // noinspection TsLint
127 | nock('http://www.example.com')
128 | .get('/resource')
129 | .reply(200, 'path matched', {'content-type': 'text/plain'});
130 |
131 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory();
132 | let response: RespondOptions = await httpRequestFactory.createRequest(
133 | {
134 | url: (): string => 'http://www.example.com/resource', method: (): string => 'GET',
135 | headers: (): { [index: string]: string } => ({})
136 | }
137 | );
138 |
139 | assert.deepStrictEqual(response, {
140 | body: 'path matched',
141 | contentType: 'text/plain',
142 | headers: {
143 | 'content-type': 'text/plain'
144 | },
145 | status: 200
146 | });
147 | });
148 | });
149 | describe('sad path', () => {
150 | it('should throw if timeout is reached', async () => {
151 | // noinspection TsLint
152 | nock('http://www.example.com')
153 | .get('/resource')
154 | .delay(7)
155 | .reply(200, 'path matched');
156 |
157 | let httpRequestFactory: HttpRequestFactory = new HttpRequestFactory(5);
158 | await assertThrowsAsync(
159 | async () => {
160 | await httpRequestFactory.createRequest(
161 | getRequestDouble('http://www.example.com/resource')
162 | );
163 | },
164 | /Error/
165 | );
166 | });
167 | });
168 | });
169 |
--------------------------------------------------------------------------------
/src/RequestInterceptor.ts:
--------------------------------------------------------------------------------
1 | import {Overrides, Request, RespondOptions} from 'puppeteer';
2 | import {instanceOfRequestBlocker} from './common/interfaceValidators/instanceOfRequestBlocker';
3 | import {instanceOfRequestModifier} from './common/interfaceValidators/instanceOfRequestModifier';
4 | import {instanceOfRequestSpy} from './common/interfaceValidators/instanceOfRequestSpy';
5 | import {instanceOfResponseFaker} from './common/interfaceValidators/instanceOfResponseFaker';
6 | import {ILogger} from './common/Logger';
7 | import {resolveOptionalPromise} from './common/resolveOptionalPromise';
8 | import {UrlAccessor} from './common/urlAccessor/UrlAccessor';
9 | import {UrlAccessorResolver} from './common/urlAccessor/UrlAccessorResolver';
10 | import {VoidLogger} from './common/VoidLogger';
11 | import {IRequestBlocker} from './interface/IRequestBlocker';
12 | import {IRequestModifier} from './interface/IRequestModifier';
13 | import {IRequestSpy} from './interface/IRequestSpy';
14 | import {IResponseFaker} from './interface/IResponseFaker';
15 | import {RequestBlocker} from './RequestBlocker';
16 | import {RequestMatcher} from './types/RequestMatcher';
17 |
18 | export class RequestInterceptor {
19 |
20 | private requestSpies: Array = [];
21 | private responseFakers: Array = [];
22 | private requestModifiers: Array = [];
23 | private matcher: RequestMatcher;
24 | private logger: ILogger;
25 | private requestBlocker: IRequestBlocker;
26 |
27 | public constructor(matcher: RequestMatcher, logger?: ILogger) {
28 | if (typeof logger === 'undefined') {
29 | logger = new VoidLogger();
30 | }
31 |
32 | this.logger = logger;
33 | this.matcher = matcher;
34 | this.requestBlocker = new RequestBlocker();
35 | }
36 |
37 | public async intercept(interceptedRequest: Request): Promise {
38 | await this.matchSpies(interceptedRequest);
39 |
40 | if (await resolveOptionalPromise(this.requestBlocker.shouldBlockRequest(interceptedRequest, this.matcher))) {
41 | await this.blockUrl(interceptedRequest);
42 |
43 | return;
44 | }
45 |
46 | let requestOverride: Overrides | undefined = await this.getMatchingOverride(interceptedRequest);
47 |
48 | if (typeof requestOverride !== 'undefined') {
49 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(interceptedRequest);
50 | await interceptedRequest.continue(requestOverride);
51 | this.logger.log(`modified: ${urlAccessor.getUrlFromRequest(interceptedRequest)}`);
52 |
53 | return;
54 | }
55 |
56 | let responseFaker: undefined | IResponseFaker = await this.getMatchingFaker(interceptedRequest);
57 | if (typeof responseFaker !== 'undefined') {
58 | let responseFake: RespondOptions | Promise = responseFaker.getResponseFake(interceptedRequest);
59 | await interceptedRequest.respond(await resolveOptionalPromise(responseFake));
60 | this.logger.log(`faked: ${interceptedRequest.url()}`);
61 |
62 | return;
63 | }
64 |
65 | await this.acceptUrl(interceptedRequest);
66 | }
67 |
68 | public addSpy(requestSpy: IRequestSpy): void {
69 | if (!instanceOfRequestSpy(requestSpy)) {
70 | throw new Error('invalid RequestSpy provided. Please make sure to match the interface provided.');
71 | }
72 |
73 | this.requestSpies.push(requestSpy);
74 | }
75 |
76 | public addFaker(responseFaker: IResponseFaker): void {
77 | if (!instanceOfResponseFaker(responseFaker)) {
78 | throw new Error('invalid ResponseFaker provided. Please make sure to match the interface provided.');
79 | }
80 |
81 | this.responseFakers.push(responseFaker);
82 | }
83 |
84 | public addRequestModifier(requestModifier: IRequestModifier): void {
85 | if (!instanceOfRequestModifier(requestModifier)) {
86 | throw new Error('invalid RequestModifier provided. Please make sure to match the interface provided.');
87 | }
88 |
89 | this.requestModifiers.push(requestModifier);
90 | }
91 |
92 | public block(urlsToBlock: Array | string): void {
93 | this.requestBlocker.addUrlsToBlock(urlsToBlock);
94 | }
95 |
96 | public clearSpies(): void {
97 | this.requestSpies = [];
98 | }
99 |
100 | public clearFakers(): void {
101 | this.responseFakers = [];
102 | }
103 |
104 | public clearRequestModifiers(): void {
105 | this.requestModifiers = [];
106 | }
107 |
108 | public clearUrlsToBlock(): void {
109 | this.requestBlocker.clearUrlsToBlock();
110 | }
111 |
112 | public setUrlsToBlock(urlsToBlock: Array): void {
113 | this.requestBlocker.clearUrlsToBlock();
114 | this.requestBlocker.addUrlsToBlock(urlsToBlock);
115 | }
116 |
117 | public setRequestBlocker(requestBlocker: IRequestBlocker): void {
118 | if (!instanceOfRequestBlocker(requestBlocker)) {
119 | throw new Error('invalid RequestBlocker provided. Please make sure to match the interface provided.');
120 | }
121 |
122 | this.requestBlocker = requestBlocker;
123 | }
124 |
125 | private async getMatchingFaker(interceptedRequest: Request): Promise {
126 | for (let faker of this.responseFakers) {
127 | if (await resolveOptionalPromise(faker.isMatchingRequest(interceptedRequest, this.matcher))) {
128 | return faker;
129 | }
130 | }
131 |
132 | return undefined;
133 | }
134 |
135 | private async matchSpies(interceptedRequest: Request): Promise {
136 | for (let spy of this.requestSpies) {
137 | if (await resolveOptionalPromise(spy.isMatchingRequest(interceptedRequest, this.matcher))) {
138 | await resolveOptionalPromise(spy.addMatch(interceptedRequest));
139 | }
140 | }
141 | }
142 |
143 | private async getMatchingOverride(interceptedRequest: Request): Promise {
144 | let requestOverride: Overrides | undefined;
145 |
146 | for (let requestModifier of this.requestModifiers) {
147 | if (await resolveOptionalPromise(requestModifier.isMatchingRequest(interceptedRequest, this.matcher))) {
148 | requestOverride = await resolveOptionalPromise(requestModifier.getOverride(interceptedRequest));
149 | }
150 | }
151 |
152 | return requestOverride;
153 | }
154 |
155 | private async blockUrl(interceptedRequest: Request): Promise {
156 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(interceptedRequest);
157 |
158 | try {
159 | await interceptedRequest.abort();
160 | this.logger.log(`aborted: ${urlAccessor.getUrlFromRequest(interceptedRequest)}`);
161 | } catch (error) {
162 | this.logger.log((error).toString());
163 | }
164 | }
165 |
166 | private async acceptUrl(interceptedRequest: Request): Promise {
167 | let urlAccessor: UrlAccessor = UrlAccessorResolver.getUrlAccessor(interceptedRequest);
168 | try {
169 | await interceptedRequest.continue();
170 | this.logger.log(`loaded: ${urlAccessor.getUrlFromRequest(interceptedRequest)}`);
171 | } catch (error) {
172 | this.logger.log((error).toString());
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/documentation/API.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | ## Table of Content
4 |
5 | - [Class: RequestInterceptor](#class-requestinterceptor)
6 | - [Class: RequestSpy](#class-requestspy-implements-irequestspy)
7 | - [Class: ResponseFaker](#class-responsefaker-implements-iresponsefaker)
8 | - [Class: ResponseModifier](#class-responsemodifier-implements-iresponsefaker)
9 | - [Class: RequestModifier](#class-requestmodifier-implements-irequestmodifier)
10 | - [Class: RequestRedirector](#class-requestredirector-implements-irequestmodifier)
11 | - [Class: RequestBlocker](#class-requestblocker-implements-iresponseblocker)
12 |
13 | ## class: RequestInterceptor
14 | The `RequestInterceptor` will call all spies, fakers and blocker to dertermine if an intercepted request matches. against the `matcher` function and notify all spies with a matching pattern and block requests matching any pattern in `urlsToBlock`.
15 |
16 | ### RequestInterceptor constructor(matcher, logger?)
17 | - `matcher`: \<(url: string, pattern: string) =\> boolean\>\>
18 | - `logger?`: \<{log: (text: string) =\> void}\>
19 |
20 | The `matcher` will be called for every url, testing the url against patterns of any `RequestSpy` provided and also any url added to `urlsToBlock`.
21 |
22 | 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.
23 | ### RequestInterceptor.intercept(interceptedRequest)
24 | - interceptedRequest: interceptedRequest provided by puppeteer's 'request' event
25 |
26 | Function to be registered with puppeteer's request event.
27 |
28 | ### RequestInterceptor.addSpy(requestSpy)
29 | - `requestSpy`: \ spy to register
30 |
31 | Register a `RequestSpy` with the `RequestInterceptor`.
32 |
33 | ### RequestInterceptor.clearSpies()
34 | Clears all registered spies.
35 |
36 | ### RequestInterceptor.addFaker(responseFaker)
37 | - `responseFaker`: \ faker to register
38 |
39 | Register a `ResonseFaker` with the `RequestInterceptor`.
40 |
41 | ### RequestInterceptor.clearFakers()
42 | Clears all registered fakers.
43 |
44 | ### RequestInterceptor.addRequestModifier(requestModifier)
45 | - `responseModifier`: \ modifier to register
46 |
47 | Register a `RequestModifier` with the `RequestInterceptor`.
48 |
49 | ### RequestInterceptor.clearRequestModifiers()
50 | Clears all registered modifiers.
51 |
52 | ### RequestInterceptor.block(urlsToBlock)
53 | - `urlsToBlock`: \ | \\> urls to be blocked if matched
54 |
55 | `block` will always add urls to the list of urls to block. Passed arrays will be merged with existing urls to block.
56 |
57 | ### RequestInterceptor.setUrlsToBlock(urlsToBlock)
58 | - `urlsToBlock`: > setter for `urlsToBlock`
59 |
60 | Setter to overwrite existing urls to block.
61 |
62 | ### RequestInterceptor.clearUrlsToBlock()
63 | Clears all registered patterns of urls to block.
64 |
65 | ### RequestInterceptor.setRequestBlocker(requestBlocker)
66 | - `requestBlocker` \
67 |
68 | Allows you to replace the default RequestBlocker by your own implementation.
69 |
70 | -----
71 |
72 | ## class: RequestSpy implements IRequestSpy
73 | `RequestSpy` is used to count and verify intercepted requests matching a specific pattern.
74 |
75 | ### RequestSpy constructor(pattern)
76 | - `pattern`: \>
77 |
78 | `pattern` passed to the `matcher` function of the `RequestInterceptor`.
79 |
80 | ### RequestSpy.hasMatch()
81 | - returns: \ returns whether any url matched the `pattern`
82 |
83 | ### RequestSpy.getMatchedUrls()
84 | - returns: \\> returns a list of urls that matched the `pattern`
85 |
86 | ### RequestSpy.getMatchedRequests()
87 | - returns: \\> returns a list of requests that matched the `pattern`
88 |
89 | ### RequestSpy.getMatchCount()
90 | - returns: \ number of urls that matched the `pattern`
91 |
92 | ### RequestSpy.isMatchingRequest(request, matcher)
93 | - request \ request object provided by puppeteer
94 | - matcher \<(url: string, pattern: string) =\> boolean\>\> matching function passed to RequestInterceptor's constructor
95 | - returns: \ returns true if any pattern provided to the `RequestSpy` matches the request url
96 |
97 | The `RequestInterceptor` calls this method to determine if an interceptedRequest matches the RequestSpy.
98 |
99 | ### RequestSpy.addMatch(matchedRequest)
100 | - matchedRequest: \ request that was matched
101 |
102 | The `RequestInterceptor` calls this method when an interceptedRequest matches the pattern.
103 |
104 | -----
105 |
106 | ## class: ResponseFaker implements IResponseFaker
107 | `ResponseFaker` is used to provide a fake response when matched to a specific pattern.
108 |
109 | ### ResponseFaker constructor(pattern, responseFake)
110 | - `pattern`: \>
111 | - `responseFake`: \<\(\(request: Request\) => RespondOptions | Promise\\) | RespondOptions\> for details refer to [puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#requestrespondresponse)
112 |
113 | ### ResponseFaker.getPatterns()
114 | - returns: \\> return the `pattern` list of the faker
115 |
116 | ### ResponseFaker.getResponseFake()
117 | - returns: \\> return the fake response
118 |
119 | The `RequestInterceptor` calls this method when an interceptedUrl matches the pattern.
120 |
121 | ### ResponseFaker.isMatchingRequest(request, matcher)
122 | - request \ request object provided by puppeteer
123 | - matcher \<(url: string, pattern: string) =\> boolean\>\> matching function passed to RequestInterceptor's constructor
124 | - returns: \ returns true if any pattern provided to the `ResponseFaker` matches the request url
125 |
126 | The `RequestInterceptor` calls this method to determine if an interceptedRequest matches.
127 |
128 | -----
129 |
130 | ## class: ResponseModifier implements IResponseFaker
131 | `ResponseModifier` is used to load the original response and modify it on the fly as a fake response when matched to a specific pattern.
132 |
133 | ### ResponseModifier constructor(pattern, responseModifierCallback)
134 | - `pattern`: \>
135 | - `responseModifierCallback`: \<\(err: Error | undefined, response: string, request: Request\) => string | Promise>
136 |
137 | ### ResponseModifier.getPatterns()
138 | - returns: \\> return the `pattern` list of the faker
139 |
140 | ### ResponseModifier.getResponseFake(request)
141 | - `request`: \