Skip to content

Commit

Permalink
feat(plugin): allow to transform entries before saving a HAR file
Browse files Browse the repository at this point in the history
closes #174
  • Loading branch information
derevnjuk committed Feb 1, 2023
1 parent 1cb3186 commit 5fd661e
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 27 deletions.
6 changes: 2 additions & 4 deletions src/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface SaveOptions {
export type RecordOptions = NetworkObserverOptions & {
rootDir: string;
filter?: string;
transform?: string;
};

interface Addr {
Expand Down Expand Up @@ -81,10 +82,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
11 changes: 10 additions & 1 deletion src/network/DefaultHarExporter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NetworkRequest } from './NetworkRequest';
import { DefaultHarExporter } from './DefaultHarExporter';
import { Logger } from '../utils/Logger';
import {
anyString,
instance,
Expand All @@ -16,6 +17,7 @@ import { EOL } from 'os';

describe('DefaultHarExporter', () => {
const buffer = mock<WriteStream>();
const logger = mock<Logger>();
const networkRequest = new NetworkRequest(
'1',
'https://example.com',
Expand All @@ -24,10 +26,17 @@ describe('DefaultHarExporter', () => {
);
const predicate: jest.Mock<(entry: Entry) => Promise<unknown> | unknown> =
jest.fn<(entry: Entry) => Promise<unknown> | unknown>();
const transform: jest.Mock<(entry: Entry) => Promise<Entry> | Entry> =
jest.fn<(entry: Entry) => Promise<Entry> | Entry>();
let harExporter!: DefaultHarExporter;

beforeEach(() => {
harExporter = new DefaultHarExporter(instance(buffer), predicate);
harExporter = new DefaultHarExporter(
instance(logger),
instance(buffer),
predicate,
transform
);
});

afterEach(() => {
Expand Down
59 changes: 54 additions & 5 deletions src/network/DefaultHarExporter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { EntryBuilder } from './EntryBuilder';
import type { NetworkRequest } from './NetworkRequest';
import type { HarExporter } from './HarExporter';
import type { Logger } from '../utils/Logger';
import { ErrorUtils } from '../utils/ErrorUtils';
import type {
DefaultHarExporterOptions,
Predicate,
Transformer
} from './DefaultHarExporterOptions';
import type { Entry } from 'har-format';
import type { WriteStream } from 'fs';
import { EOL } from 'os';
import { format } from 'util';

export class DefaultHarExporter implements HarExporter {
get path(): string {
Expand All @@ -12,9 +20,18 @@ export class DefaultHarExporter implements HarExporter {
return Buffer.isBuffer(path) ? path.toString('utf-8') : path;
}

private get predicate(): Predicate | undefined {
return this.options?.predicate;
}

private get transform(): Transformer | undefined {
return this.options?.transform;
}

constructor(
private readonly logger: Logger,
private readonly buffer: WriteStream,
private readonly predicate?: (entry: Entry) => Promise<unknown> | unknown
private readonly options?: DefaultHarExporterOptions
) {}

public async write(networkRequest: NetworkRequest): Promise<void> {
Expand All @@ -24,24 +41,56 @@ export class DefaultHarExporter implements HarExporter {
return;
}

const json = JSON.stringify(entry);
const json = await this.serializeEntry(entry);

// @ts-expect-error type mismatch
if (!this.buffer.closed) {
if (!this.buffer.closed && json) {
this.buffer.write(`${json}${EOL}`);
}
}

public async serializeEntry(entry: Entry): Promise<string | undefined> {
try {
const result =
typeof this.transform === 'function'
? await this.transform(entry)
: entry;

return JSON.stringify(result);
} catch (e) {
const stack = ErrorUtils.isError(e) ? e.stack : e;
const formattedEntry = format('%j', entry);

this.logger.err(
`The entry is missing as a result of an error in the 'transform' function.
The passed entry:
${formattedEntry}
The stack trace for this error is:
${stack}`
);

return undefined;
}
}

public end(): void {
this.buffer.end();
}

private async applyPredicate(entry: Entry) {
private async applyPredicate(entry: Entry): Promise<unknown> {
try {
return (
typeof this.predicate === 'function' && (await this.predicate?.(entry))
);
} catch {
} catch (e) {
const message = ErrorUtils.isError(e) ? e.message : e;

this.logger.debug(
`The operation has encountered an error while processing the entry. ${message}`
);

return false;
}
}
Expand Down
57 changes: 42 additions & 15 deletions src/network/DefaultHarExporterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,54 @@ import type {
import type { HarExporter } from './HarExporter';
import { DefaultHarExporter } from './DefaultHarExporter';
import { Loader } from '../utils/Loader';
import { FileManager } from '../utils/FileManager';
import type { Entry } from 'har-format';
import type { FileManager } from '../utils/FileManager';
import type { Logger } from '../utils/Logger';
import type {
DefaultHarExporterOptions,
Predicate,
Transformer
} from './DefaultHarExporterOptions';
import { resolve } from 'path';

export class DefaultHarExporterFactory implements HarExporterFactory {
constructor(private readonly fileManager: FileManager) {}
constructor(
private readonly fileManager: FileManager,
private readonly logger: Logger
) {}

public async create(options: HarExporterOptions): Promise<HarExporter> {
const settings = await this.createSettings(options);
const stream = await this.fileManager.createTmpWriteStream();

return new DefaultHarExporter(this.logger, stream, settings);
}

private async createSettings({
predicatePath,
transformPath,
rootDir
}: HarExporterOptions) {
const [predicate, transform]: (Predicate | Transformer | undefined)[] =
await Promise.all(
[predicatePath, transformPath].map(path =>
this.loadCustomProcessor(path, rootDir)
)
);

return { predicate, transform } as DefaultHarExporterOptions;
}

public async create({
rootDir,
predicatePath
}: HarExporterOptions): Promise<HarExporter> {
let predicate: ((request: Entry) => unknown) | undefined;
private async loadCustomProcessor<T extends Predicate | Transformer>(
path: string | undefined,
rootDir: string
): Promise<T | undefined> {
let processor: T | undefined;

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

return new DefaultHarExporter(
await this.fileManager.createTmpWriteStream(),
predicate
);
return processor;
}
}
9 changes: 9 additions & 0 deletions src/network/DefaultHarExporterOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Entry } from 'har-format';

export type Predicate = (entry: Entry) => Promise<unknown> | unknown;
export type Transformer = (entry: Entry) => Promise<Entry> | Entry;

export interface DefaultHarExporterOptions {
predicate?: Predicate;
transform?: Transformer;
}
1 change: 1 addition & 0 deletions src/network/HarExporterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { HarExporter } from './HarExporter';
export interface HarExporterOptions {
rootDir: string;
predicatePath?: string;
transformPath?: string;
}

export interface HarExporterFactory {
Expand Down
1 change: 1 addition & 0 deletions src/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type { Observer } from './Observer';
export type { ObserverFactory } from './ObserverFactory';
export { DefaultHarExporter } from './DefaultHarExporter';
export { DefaultHarExporterFactory } from './DefaultHarExporterFactory';
export { DefaultHarExporterOptions } from './DefaultHarExporterOptions';
export { DefaultObserverFactory } from './DefaultObserverFactory';
export { EntryBuilder } from './EntryBuilder';
export { HarBuilder } from './HarBuilder';
Expand Down

0 comments on commit 5fd661e

Please sign in to comment.