Skip to content

Commit

Permalink
feat(plugin): allow to transform entries before saving a HAR (#182)
Browse files Browse the repository at this point in the history
You can modify the entries before saving the HAR by using the
`transform` option. This option should be set to a path to a module that
exports a function, similar to the filter option, to change the Entry
object as desired.

```js
cy.recordHar({ transform: '../support/remove-sensitive-data.ts' });
```

Here's a simple example of what the `remove-sensitive-data.ts` module
might look like:

```ts
import { Entry } from 'har-format';

const PASSWORD_REGEXP = /("password":\s*")\w+("\s*)/;

export default async (entry: Entry) => {
  try {
    // Remove sensitive information from the request body
    if (entry.request.postData?.text) {
      entry.request.postData.text = entry.request.postData.text.replace(
        PASSWORD_REGEXP,
        '$1***$2'
      );
    }

    return entry;
  } catch {
    return entry;
  }
};
```


In this example, the transform function will replace any instances of
password with `***` in both the request and response bodies of each
entry.


closes #174
  • Loading branch information
derevnjuk committed Feb 1, 2023
1 parent 1cb3186 commit fceeb4e
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 71 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,44 @@ export default async (entry: Entry) => {
};
```

> ✴ The plugin also supports files with `.js`, `.mjs` and `.cjs` extensions.
In this example, the `filter` function will only exclude entries in the HAR where the request body contains a JSON object with a password field.

You can also modify the entries before saving the HAR by using the `transform` option. This option should be set to a path to a module that exports a function, similar to the filter option, to change the Entry object as desired.

```js
cy.recordHar({ transform: '../support/remove-sensitive-data.ts' });
```

Here's a simple example of what the `remove-sensitive-data.ts` module might look like:

```ts
import { Entry } from 'har-format';

const PASSWORD_REGEXP = /("password":\s*")\w+("\s*)/;

export default async (entry: Entry) => {
try {
// Remove sensitive information from the request body
if (entry.request.postData?.text) {
entry.request.postData.text = entry.request.postData.text.replace(
PASSWORD_REGEXP,
'$1***$2'
);
}

return entry;
} catch {
return entry;
}
};
```

The function should take an Entry object as a parameter and return the modified.

In this example, the transform function will replace any occurrences of password with `***` in the request body of each entry.

> ✴ The plugin also supports files with `.js`, `.mjs` and `.cjs` extensions.
By default, the path is relative to the spec folder. But by providing a `rootDir` it will look for the module in the provided directory:

```js
Expand Down
18 changes: 18 additions & 0 deletions cypress/e2e/record-har.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ describe('Record HAR', () => {
})
);

['.js', '.ts', '.cjs'].forEach(ext =>
it(`transforms a request using the custom postprocessor from ${ext} file`, () => {
cy.recordHar({ transform: `../fixtures/transform${ext}` });

cy.get('a[href$=fetch]').click();

cy.saveHar({ waitForIdle: true });

cy.findHar()
.its('log.entries')
.should('contain.something.like', {
response: {
content: { text: /\{"items":\[/ }
}
});
})
);

it('excludes a request by its path', () => {
cy.recordHar({ excludePaths: [/^\/api\/products$/, '^\\/api\\/users$'] });

Expand Down
4 changes: 2 additions & 2 deletions cypress/fixtures/filter.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = async req => {
module.exports = async entry => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
return /\{"products":\[/.test(entry.response.content.text ?? '');
} catch {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions cypress/fixtures/filter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = async req => {
module.exports = async entry => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
return /\{"products":\[/.test(entry.response.content.text ?? '');
} catch {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions cypress/fixtures/filter.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default async req => {
export default async entry => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
return /\{"products":\[/.test(entry.response.content.text ?? '');
} catch {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions cypress/fixtures/filter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Entry } from 'har-format';

export default async (req: Entry) => {
export default async (entry: Entry) => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
return /\{"products":\[/.test(entry.response.content.text ?? '');
} catch {
return false;
}
Expand Down
19 changes: 19 additions & 0 deletions cypress/fixtures/transform.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = async entry => {
try {
if (entry.response.content?.text) {
entry.response.content = {
...entry.response.content,
text: entry.response.content.text.replace(
/"products":\s*(.+)/g,
`"items":$1`
)
};
}

return entry;
} catch {
return entry;
}
};


17 changes: 17 additions & 0 deletions cypress/fixtures/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = async entry => {
try {
if (entry.response.content?.text) {
entry.response.content = {
...entry.response.content,
text: entry.response.content.text.replace(
/"products":\s*(.+)/g,
`"items":$1`
)
};
}

return entry;
} catch {
return entry;
}
};
17 changes: 17 additions & 0 deletions cypress/fixtures/transform.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export async entry => {
try {
if (entry.response.content?.text) {
entry.response.content = {
...entry.response.content,
text: entry.response.content.text.replace(
/"products":\s*(.+)/g,
`"items":$1`
)
};
}

return entry;
} catch {
return entry;
}
};
19 changes: 19 additions & 0 deletions cypress/fixtures/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Entry } from 'har-format';

export default async (entry: Entry) => {
try {
if (entry.response.content?.text) {
entry.response.content = {
...entry.response.content,
text: entry.response.content.text.replace(
/"products":\s*(.+)/g,
`"items":$1`
)
};
}

return entry;
} catch {
return entry;
}
};
4 changes: 2 additions & 2 deletions src/Plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RecordOptions, SaveOptions } from './Plugin';
import { Plugin } from './Plugin';
import { Logger } from './utils/Logger';
import { FileManager } from './utils/FileManager';
import type { Logger } from './utils/Logger';
import type { FileManager } from './utils/FileManager';
import type {
Observer,
ObserverFactory,
Expand Down
13 changes: 4 additions & 9 deletions src/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type {
HarExporterFactory,
NetworkObserverOptions,
Observer,
ObserverFactory
ObserverFactory,
HarExporterOptions
} from './network';
import { HarBuilder, NetworkIdleMonitor, NetworkRequest } from './network';
import { ErrorUtils } from './utils/ErrorUtils';
Expand All @@ -25,10 +26,7 @@ export interface SaveOptions {
waitForIdle?: boolean;
}

export type RecordOptions = NetworkObserverOptions & {
rootDir: string;
filter?: string;
};
export type RecordOptions = NetworkObserverOptions & HarExporterOptions;

interface Addr {
port: number;
Expand Down Expand Up @@ -81,10 +79,7 @@ export class Plugin {
);
}

this.exporter = await this.exporterFactory.create({
predicatePath: options.filter,
rootDir: options.rootDir
});
this.exporter = await this.exporterFactory.create(options);
this._connection = this.connectionFactory.create({
...this.addr,
maxRetries: 20,
Expand Down
2 changes: 1 addition & 1 deletion src/cdp/DefaultNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Network, NetworkEvent } from '../network';
import { ErrorUtils } from '../utils/ErrorUtils';
import { Logger } from '../utils/Logger';
import type { Logger } from '../utils/Logger';
import {
TARGET_OR_BROWSER_CLOSED,
UNABLE_TO_ATTACH_TO_TARGET
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const plugin = new Plugin(
FileManager.Instance,
new DefaultConnectionFactory(Logger.Instance),
new DefaultObserverFactory(Logger.Instance),
new DefaultHarExporterFactory(FileManager.Instance)
new DefaultHarExporterFactory(FileManager.Instance, Logger.Instance)
);

export const install = (on: Cypress.PluginEvents): void => {
Expand Down

0 comments on commit fceeb4e

Please sign in to comment.