Skip to content

Commit

Permalink
feat(plugin): add support for mjs and cjs filters
Browse files Browse the repository at this point in the history
closes #163
  • Loading branch information
derevnjuk committed Jan 23, 2023
1 parent ccbb4f8 commit 3fdfc8d
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 46 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ cy.recordHar({ includeBlobs: false });

> ✴ As of version 6, this flag will be disabled by default.
You can specify the `filter` option as a path to a JS/TS module that exports a function to filter out unwanted entries from the HAR. The function should take an [Entry object](http://www.softwareishard.com/blog/har-12-spec/#entries) as a parameter and return a boolean indicating whether the entry should be included in the final HAR or not.
You can specify the `filter` option as a path to a module that exports a function (it can be sync or async) to filter out unwanted entries from the HAR. The function should take an [Entry object](http://www.softwareishard.com/blog/har-12-spec/#entries) as a parameter and return a boolean indicating whether the entry should be included in the final HAR or not.

Here's an example of how to use the `filter` option:

Expand All @@ -242,9 +242,11 @@ 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 specify a `rootDir` option that will be used to resolve the path of the `filter` option. 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:
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
cy.recordHar({
Expand Down
42 changes: 14 additions & 28 deletions cypress/e2e/record-har.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,23 @@ describe('Record HAR', () => {
});
});

it('excludes a request using the custom filter from .ts file', () => {
cy.recordHar({ filter: '../support/ts-filter.ts' });
['.js', '.mjs', '.ts', '.cjs'].forEach(ext =>
it.only(`excludes a request using the custom filter from ${ext} file`, () => {
cy.recordHar({ filter: `../support/filter${ext}` });

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

cy.saveHar({ waitForIdle: true });

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

it('excludes a request using the custom filter from .js file', () => {
cy.recordHar({ filter: '../support/js-filter.js' });
cy.get('a[href$=fetch]').click();

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

cy.saveHar({ waitForIdle: true });
cy.saveHar({ waitForIdle: true });

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

it('excludes a request by its path', () => {
cy.recordHar({ excludePaths: ['^\\/api\\/products$', '^\\/api\\/users$'] });
Expand Down
File renamed without changes.
7 changes: 7 additions & 0 deletions cypress/support/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = async req => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
} catch {
return false;
}
};
7 changes: 7 additions & 0 deletions cypress/support/filter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default async req => {
try {
return /\{"products":\[/.test(req.response.content.text ?? '');
} catch {
return false;
}
};
File renamed without changes.
2 changes: 1 addition & 1 deletion src/network/DefaultHarExporterFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('DefaultHarExporterFactory', () => {
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writeStreamMock)
);
when(loaderSpy.load('/root/predicate.js')).thenReturn(predicate);
when(loaderSpy.load('/root/predicate.js')).thenResolve(predicate);

// act
const result = await factory.create(options);
Expand Down
2 changes: 1 addition & 1 deletion src/network/DefaultHarExporterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class DefaultHarExporterFactory implements HarExporterFactory {

if (predicatePath) {
const absolutePath = resolve(rootDir, predicatePath);
predicate = Loader.load(absolutePath);
predicate = await Loader.load(absolutePath);
}

return new DefaultHarExporter(
Expand Down
28 changes: 24 additions & 4 deletions src/utils/Loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('Loader', () => {
});

describe('load', () => {
it('should load the module from the given path using the default export', () => {
it('should load the module from the given path using the default export', async () => {
// arrange
jest.mock(
modulePath,
Expand All @@ -21,18 +21,38 @@ describe('Loader', () => {
);

// act
const result = Loader.load<string>(modulePath);
const result = await Loader.load<string>(modulePath);

// assert
expect(result).toBe(module);
});

it('should load the module from the given path using the default module.exports', () => {
it('should load the es module from the given path', async () => {
// arrange
jest.mock(
modulePath,
jest
.fn<() => string>()
.mockImplementationOnce(() => {
throw new Error('[ERR_REQUIRE_ESM]');
})
.mockReturnValue(module),
{ virtual: true }
);

// act
const result = await Loader.load<string>(modulePath);

// assert
expect(result).toBe(module);
});

it('should load the module from the given path using the default module.exports', async () => {
// arrange
jest.mock(modulePath, () => module, { virtual: true });

// act
const result = Loader.load<string>(modulePath);
const result = await Loader.load<string>(modulePath);

// assert
expect(result).toBe(module);
Expand Down
34 changes: 24 additions & 10 deletions src/utils/Loader.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import { ErrorUtils } from './ErrorUtils';

export class Loader {
public static load<T>(path: string): T | undefined {
const module = this.interopRequireDefault(
// eslint-disable-next-line @typescript-eslint/no-var-requires -- `ts-node` does not handle the dynamic imports like `import(path)`
require(/* webpackIgnore: true */ path)
);
public static async load<T>(path: string): Promise<T | undefined> {
let module: unknown;

try {
module = require(/* webpackIgnore: true */ path);
} catch (err) {
const stack = ErrorUtils.isError(err) ? err.stack : undefined;

if (
!stack?.includes('[ERR_REQUIRE_ESM]') &&
!stack?.includes(
'SyntaxError: Cannot use import statement outside a module'
)
) {
throw err;
}

module = (await import(/* webpackIgnore: true */ path)).default;
}

return module?.default as T | undefined;
return this.interopRequireDefault<T | undefined>(module);
}

private static interopRequireDefault(obj: unknown): {
default: unknown;
} {
private static interopRequireDefault<T>(m: unknown): T | undefined {
// @ts-expect-error unknown is not assignable to the module type
return obj?.__esModule ? obj : { default: obj };
return m && m.__esModule && m.default ? m.default : m;
}
}

0 comments on commit 3fdfc8d

Please sign in to comment.