Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions CONTRIBUTE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

## Project Structure

The project is a monorepo created with [turbo](https://turbo.build/repo). It consists of a cypress plugin and a web application.

The plugin generates a `json` file for each test into the `dump` folder inside the working directory. Each file contains the following fields:

- `cy` - a list of cypress events. The data is collected from the cypress [`log:added`](https://docs.cypress.io/api/cypress-api/catalog-of-events) event.

- `rr` - a list of [rrweb](https://www.npmjs.com/package/rrweb) records, which represents the mutations in the DOM. The entries are linked to `cy` events on cypress `log:added` and `log:changed` events.

- `har` - an [HTTPArchive(HAR)](http://www.softwareishard.com/blog/har-12-spec/) object, recorded by the [HttpArchive Generator](https://github.com/NeuraLegion/cypress-har-generator).

- `meta` - [`RunContextData`](./packages/support/src/cy/runContext.ts) an object with the following fields:
```typescript
{
spec: string; // spec filename
test: string[]; // test title
retryAttempt: number; // https://docs.cypress.io/guides/guides/test-retries
}
```

- `browserLogs` - the browser logs at a moment in time. The data is collected using [chrome-remote-interface](https://www.npmjs.com/package/chrome-remote-interface).

- `pluginMeta` - the data passed down to the optional `meta` field of the `debuggerPlugin` options argument.

### Folder structure:

- `packages/cypress-debugger` - the plugin entry point. It exports the `debuggerSupport` and `debuggerPlugin` functions, required for plugin installation and a set of types used in the web application.

- `packages/support` - exports the function that attaches handlers to [Cypress Events](https://docs.cypress.io/api/cypress-api/catalog-of-events). Here are made the following actions:
- collect cypress events
- collect [rrweb](https://www.npmjs.com/package/rrweb) events and match them with rrweb events
- recording network events (using the [HAR generator cypress plugin](https://github.com/NeuraLegion/cypress-har-generator))

- `packages/plugin` - exports a function that attaches [Cypress Plugin Events](https://docs.cypress.io/api/plugins/writing-a-plugin) handlers used inside the `setupNodeEvents` function of the cypress configuration. Here are collected the browser logs, and created the files with the collected information.
The browser logs are accessed using the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) by connecting to the cypress browser using the remote debugging port.
The result files are created in the `dump` folder relative to the cypress config file.
- `apps/web` - the web application.
The Web application is a basic UI created with [Vite](https://vitejs.dev/), [React](https://react.dev/) and [Typescript](https://www.typescriptlang.org/). It is a tool that helps to analyze the information from the files generated by the plugin. It also serves as an example for plugin integration.
- `packages/eslint-config-custom` - shared [eslint](https://eslint.org/) configuration.

- `packages/tsconfig` - shared [tsconfig](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

## Development

Start the packages in development mode

```sh
npm install
npm run dev
```

Runs a few tests

```sh
cd apps/web
npx cypress run --browser chrome
```
82 changes: 42 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
# Cypress Debugger

Debug cypress tests in CI - collect and analyze rrweb records, browser network information, and browser console logs for each cypress step.
Debug your failed and flaky CI cypress tests. Capture everything that's happening in Cypress tests:

The plugin generates a `json` file for each test into the `dump` folder inside the working directory. Each file contains the following fields:
- Cypress test execution steps
- DOM snapshots
- network requests (HAR)
- browser console logs

- `cy` - a list of cypress events. The data is collected from the cypress [`log:added`](https://docs.cypress.io/api/cypress-api/catalog-of-events) event.

- `rr` - a list of [rrweb](https://www.npmjs.com/package/rrweb) records, which represents the mutations in the DOM. The entries are linked to `cy` events on cypress `log:added` and `log:changed` events.

- `har` - an [HTTPArchive(HAR)](http://www.softwareishard.com/blog/har-12-spec/) object, recorded by the [HttpArchive Generator](https://github.com/NeuraLegion/cypress-har-generator).

- `meta` - [`RunContextData`](./packages/support/src/cy/runContext.ts) an object with the following fields:
```typescript
{
spec: string; // spec filename
test: string[]; // test title
retryAttempt: number; // https://docs.cypress.io/guides/guides/test-retries
}
```

- `browserLogs` - the browser logs at a moment in time. The data is collected using [chrome-remote-interface](https://www.npmjs.com/package/chrome-remote-interface).

- `pluginMeta` - the data passed down to the optional `meta` field of the `debuggerPlugin` options argument.

The collected data can be visualized by uploading a file to the web app.
The plugin captures all the information, saving it in a file that you can later replay in the web player.

## Requirements

- Cypress version 10+
- NodeJS [14+](https://docs.cypress.io/guides/getting-started/installing-cypress#:~:text=If%20you're%20using%20npm,Node.js%2014.x)
- Chromium family browsers only

## Setup

Expand All @@ -47,8 +32,20 @@ const { debuggerPlugin } = require("cypress-debugger");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
debuggerPlugin(on);
return config;
debuggerPlugin(on, {
meta: {
key: "value",
},
// path: abosulte path to the dump file
// data: captured data
callback: (path, data) => {
console.log({
path,
data,
});
},
});
return config;
},
},
});
Expand All @@ -64,19 +61,24 @@ debuggerSupport();

## Usage

Start running the tests with the following command:
Configure the plugin as documented above. Use the `callback` function to fetch the location of the replay file you can open in the player. Get the test execution information from the `dump` directory, relative to the cypress configuration file.

Analyze the information using the debugger web app.

### Chore / Chromium

```sh
npx cypress run --chrome
```

To run the plugin with the Electron app you need to set the remote-debugging-port launch argument:
### Electron

Set the `remote-debugging-port` via `ELECTRON_EXTRA_LAUNCH_ARGS` environment variable:

```sh
ELECTRON_EXTRA_LAUNCH_ARGS=--remote-debugging-port=9222 npx cypress run --browser electron
```

Please refer to the [Electron documentation](https://www.electronjs.org/docs/latest/api/command-line-switches#--remote-debugging-portport) and the [Cypress documentation](https://docs.cypress.io/api/plugins/browser-launch-api#Modify-Electron-app-switches) for more information on usage with Electron.

## Example

See an example in [apps/web](https://github.com/currents-dev/cypress-debugger//blob/main/apps/web) directory.
Expand All @@ -92,9 +94,9 @@ debuggerPlugin(on: Cypress.PluginEvents, options?: PluginOptions): void
```

- `on` - [`Cypress.PluginEvents`](https://docs.cypress.io/guides/tooling/plugins-guide) `setupNodeEvents` method first argument
- `options` - [`PluginOptions`](./packages/plugin/src/types.ts) an object with the following fields:
- `meta`: an optional field which is added to the `TestExecutionResult` as `pluginMeta`
- `callback`: a callback function which is called after each test having the current test results as argument
- `options` - [`PluginOptions`](./packages/plugin/src/types.ts):
- `meta: Record<string, unknown>`: an optional field that is added to the `TestExecutionResult` as `pluginMeta`
- `callback: (path: string, data: TestExecutionResult`: a callback function that will be called after each test

Example:

Expand All @@ -105,15 +107,15 @@ const { debuggerPlugin } = require("cypress-debugger");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
debuggerPlugin(on, {
meta: {
key: 'value'
},
callback: (data) => {
console.log(data)
}
});
return config;
debuggerPlugin(on, {
meta: {
key: "value",
},
callback: (path, data) => {
console.log({ path, data });
},
});
return config;
},
},
});
Expand Down
10 changes: 7 additions & 3 deletions apps/web/cypress.config.js → apps/web/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { defineConfig } = require("cypress");
const { debuggerPlugin } = require("@currents/cypress-debugger");
import { debuggerPlugin } from "@currents/cypress-debugger";
import { defineConfig } from "cypress";

module.exports = defineConfig({
e2e: {
Expand All @@ -11,8 +11,12 @@ module.exports = defineConfig({
meta: {
key: "value",
},
callback: (val) => {
callback: (file, data) => {
// executed after each test
console.log("results", {
file,
data,
});
},
});

Expand Down
29 changes: 14 additions & 15 deletions packages/plugin/src/install.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { HttpArchiveLog } from "@currents/cypress-debugger-support";
import {
ensureBrowserFlags,
install,
} from "@neuralegion/cypress-har-generator";
import path from "path";
import {
browserLaunchHandler,
getLogs,
clearLogs,
getLogs,
recordLogs,
} from "./browserLogs";
import {
install,
ensureBrowserFlags,
} from "@neuralegion/cypress-har-generator";
import { PluginOptions, TestExecutionResult } from "./types";
import { HttpArchiveLog } from "@currents/cypress-debugger-support";
import { createDir, readFile, removeDir, removeFile, writeFile } from "./fs";
import { PluginOptions, TestExecutionResult } from "./types";

const dumpDir = "./dump";
const harDir = "dump_har";

const createDumpFile = (data: TestExecutionResult) => {
const createDumpFile = (data: TestExecutionResult): string => {
createDir(dumpDir);

writeFile(
path.join(dumpDir, `${data.id}.raw.json`),
JSON.stringify(data, null, 2)
);
const resultsPath = path.join(dumpDir, `${data.id}.raw.json`);
writeFile(resultsPath, JSON.stringify(data, null, 2));
return resultsPath;
};

const getHar = (filename: string): HttpArchiveLog => {
Expand Down Expand Up @@ -61,10 +59,11 @@ export const installPlugin = (
pluginMeta: options?.meta,
};

const resultsFilePath = createDumpFile(dumpData);

if (options && options.callback) {
options.callback(dumpData);
options.callback(resultsFilePath, dumpData);
}
createDumpFile(dumpData);

return null;
},
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@ export type TestExecutionResult = {
};

export type PluginOptions = {
meta?: any;
callback?: (result: TestExecutionResult) => void;
meta?: Record<string, unknown>;
callback?: (path: string, result: TestExecutionResult) => void;
};