13 |
14 |
17 | Video Demo | Player | Sorry Cypress | Currents 18 |
19 | 20 | ## Requirements 21 | 22 | - Cypress version 10+ 23 | - NodeJS [^14.17.0](https://docs.cypress.io/guides/getting-started/installing-cypress#:~:text=If%20you're%20using%20npm,Node.js%2014.x) 24 | - Chromium family browsers only 25 | - Requires [alternative cypress binaries](https://docs.currents.dev/getting-started/cypress/integrating-with-cypress/alternative-cypress-binaries) due to [Cypress.io blocking](https://currents.dev/posts/v13-blocking) 26 | 27 | ## Setup 28 | 29 | Install the package: 30 | 31 | ```sh 32 | npm install cypress-debugger 33 | ``` 34 | 35 | Add `cypress-debugger` to `cypress.config.{js|ts|mjs}` 36 | 37 | ```js 38 | // cypress.config.js 39 | const { defineConfig } = require('cypress'); 40 | const { debuggerPlugin } = require('cypress-debugger'); 41 | module.exports = defineConfig({ 42 | e2e: { 43 | setupNodeEvents(on, config) { 44 | debuggerPlugin(on, config, { 45 | meta: { 46 | key: 'value', 47 | }, 48 | // path: absolute path to the dump file 49 | // data: captured data 50 | callback: (path, data) => { 51 | console.log({ 52 | path, 53 | data, 54 | }); 55 | }, 56 | }); 57 | return config; 58 | }, 59 | }, 60 | }); 61 | ``` 62 | 63 | Add `cypress-debugger` to `cypress/support/e2e.{js|ts}` 64 | 65 | ```js 66 | // cypress/support/e2e.js 67 | const { debuggerSupport } = require('cypress-debugger'); 68 | debuggerSupport(); 69 | ``` 70 | 71 | ## Usage 72 | 73 | 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. 74 | 75 | Analyze the information using the debugger web app. 76 | 77 | ### Chrome / Chromium 78 | 79 | ```sh 80 | npx cypress run --browser chrome 81 | ``` 82 | 83 | ### Electron 84 | 85 | Set the `remote-debugging-port` via `ELECTRON_EXTRA_LAUNCH_ARGS` environment variable: 86 | 87 | ```sh 88 | ELECTRON_EXTRA_LAUNCH_ARGS=--remote-debugging-port=9222 npx cypress run --browser electron 89 | ``` 90 | 91 | ## Example 92 | 93 | - See an example in [apps/web](https://github.com/currents-dev/cypress-debugger//blob/main/apps/web) directory 94 | - Example of integrating with Currents: https://github.com/currents-dev/gh-actions-example/tree/debugger-example 95 | 96 | ## API 97 | 98 | ### Plugin: `debuggerPlugin` 99 | 100 | Installs cypress-debugger. 101 | 102 | ```ts 103 | debuggerPlugin(on: Cypress.PluginEvents, config: Cypress.PluginConfig, options?: PluginOptions): void 104 | ``` 105 | 106 | - `on` - [`Cypress.PluginEvents`](https://docs.cypress.io/guides/references/configuration#setupNodeEvents) `setupNodeEvents` method first argument 107 | - `config` - [`Cypress.PluginConfig`](https://docs.cypress.io/guides/references/configuration#setupNodeEvents) `setupNodeEvents` method second argument 108 | - `options` - [`PluginOptions`](./packages/plugin/src/types.ts): 109 | - `meta: Record35 | at {frame.functionName} ( 36 | {frame.url}:{frame.lineNumber} 37 | ) 38 |
39 |
72 | {expanded ? (
73 |
No logs
; 107 | } 108 | 109 | const orderedLogs: Log[] = orderBy( 110 | [ 111 | ...logs.logEntry.map((log) => ({ 112 | message: log.text, 113 | type: log.level, 114 | timestamp: log.timestamp, 115 | stackTrace: log.stackTrace, 116 | })), 117 | ...logs.runtimeConsoleApiCalled.map((log) => ({ 118 | message: log.args[0].value, 119 | type: log.type, 120 | timestamp: log.timestamp, 121 | stackTrace: log.stackTrace, 122 | })), 123 | ], 124 | (log) => log.timestamp, 125 | 'asc' 126 | ); 127 | 128 | return ( 129 |–
157 | )} 158 |67 | {payload.message ? ( 68 | “ {payload.message} ” 69 | ) : ( 70 | payload.message 71 | )} 72 |
73 | {showButtons && ( 74 |No event selected
; 43 | } 44 | 45 | const parameters = omit(event.payload, 'name', 'wallClockStartedAt'); 46 | 47 | const messageParts = event.payload.message.split(','); 48 | const message = 49 | event.payload.name === 'task' ? messageParts[0] : event.payload.message; 50 | const taskArgs = 51 | event.payload.name === 'task' ? messageParts.slice(1).join(',') : null; 52 | 53 | return ( 54 |
78 | Drop file to upload
79 |
80 | or
81 |
87 | Payload from: {origin} 88 |
89 | 92 |32 | {entry.response.status} {entry.request.method} {resource} 33 |
34 | {!!contentType && ( 35 |36 | {contentType} 37 |
38 | )} 39 |{content.text}88 |
Response Body
128 |No records
; 138 | } 139 | 140 | return ( 141 |
13 |
14 |
17 | Video Demo | Sorry Cypress | Currents 18 |
19 | 20 | ## Documentation 21 | 22 | https://github.com/currents-dev/cypress-debugger 23 | -------------------------------------------------------------------------------- /packages/cypress-debugger/index.ts: -------------------------------------------------------------------------------- 1 | import { installPlugin } from '@currents/cypress-debugger-plugin'; 2 | import { attachHandlers } from '@currents/cypress-debugger-support'; 3 | 4 | export type * from '@currents/cypress-debugger-plugin'; 5 | export type * from '@currents/cypress-debugger-support'; 6 | 7 | export const debuggerSupport = attachHandlers; 8 | export const debuggerPlugin = installPlugin; 9 | -------------------------------------------------------------------------------- /packages/cypress-debugger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-debugger", 3 | "version": "1.0.9", 4 | "main": "./dist/index.js", 5 | "types": "./src/index.ts", 6 | "author": "Currents Software Inc", 7 | "homepage": "https://github.com/currents-dev/cypress-debugger", 8 | "bugs": { 9 | "url": "https://github.com/currents-dev/cypress-debugger/issues", 10 | "email": "support@currents.dev" 11 | }, 12 | "license": "GPL-3.0-or-later", 13 | "engines": { 14 | "node": ">=14.7.0" 15 | }, 16 | "files": [ 17 | "dist", 18 | "LICENSE.md" 19 | ], 20 | "scripts": { 21 | "build": "tsup-node --dts", 22 | "dev": "tsup --watch", 23 | "lint": "eslint --fix", 24 | "publish-npm": "node ./publish.js" 25 | }, 26 | "keywords": [ 27 | "cypress", 28 | "e2e", 29 | "cypress-debugger", 30 | "currents", 31 | "cypress-replay", 32 | "cypress-traces", 33 | "replay", 34 | "sorry-cypress", 35 | "time-travel" 36 | ], 37 | "devDependencies": { 38 | "commander": "^10.0.0", 39 | "esbuild": "^0.17.7", 40 | "tsconfig": "*", 41 | "tsup": "^6.6.0", 42 | "typescript": "^5.1.6" 43 | }, 44 | "dependencies": { 45 | "@currents/cypress-debugger-plugin": "*", 46 | "@currents/cypress-debugger-support": "*" 47 | }, 48 | "peerDependencies": { 49 | "cypress": ">=10.0.0" 50 | }, 51 | "tsup": { 52 | "entry": [ 53 | "./index.ts" 54 | ], 55 | "external": [ 56 | "cypress" 57 | ], 58 | "format": [ 59 | "cjs", 60 | "esm" 61 | ], 62 | "splitting": false, 63 | "shims": true, 64 | "clean": true, 65 | "sourcemap": "inline", 66 | "platform": "node", 67 | "target": "es2018" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/cypress-debugger/publish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const fs = require('fs'); 5 | const pkg = require('./package.json'); 6 | const { argv } = require('process'); 7 | 8 | const tag = argv[2]; 9 | 10 | if (!tag) { 11 | console.error('ERROR: You must specify a tag (latest|beta)'); 12 | process.exit(1); 13 | } 14 | 15 | const newPkg = { 16 | ...pkg, 17 | types: undefined, 18 | files: ['*'], 19 | dependencies: { 20 | ...pkg.dependencies, 21 | '@currents/cypress-debugger-plugin': pkg.version, 22 | '@currents/cypress-debugger-support': pkg.version, 23 | }, 24 | exports: { 25 | '.': { 26 | import: './index.mjs', 27 | require: './index.js', 28 | types: './index.d.ts', 29 | }, 30 | }, 31 | }; 32 | delete newPkg['types']; 33 | 34 | fs.writeFileSync( 35 | './dist/package.json', 36 | JSON.stringify(newPkg, null, 2), 37 | 'utf-8' 38 | ); 39 | fs.copyFileSync('./README.md', './dist/README.md'); 40 | fs.copyFileSync('../../LICENSE.md', './dist/LICENSE.md'); 41 | execSync(`npm publish --tag ${tag} ${argv[3]}`, { 42 | cwd: './dist', 43 | stdio: 'inherit', 44 | }); 45 | -------------------------------------------------------------------------------- /packages/cypress-debugger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["index.ts"], 4 | "exclude": ["dist", "node_modules"], 5 | "compilerOptions": { 6 | "lib": ["ES2018"], 7 | "target": "ES2018" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/eslint-node.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | }, 5 | ignorePatterns: ['**/__tests__/*.ts'], 6 | 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'airbnb-base', 11 | 'airbnb-typescript/base', 12 | 'plugin:import/recommended', 13 | 'plugin:import/typescript', 14 | 'turbo', 15 | 'plugin:prettier/recommended', 16 | 'plugin:jest/recommended', 17 | ], 18 | plugins: ['prettier', 'jest'], 19 | parserOptions: { 20 | parser: '@typescript/eslint-parser', 21 | project: ['./tsconfig.json', 'packages/*/tsconfig.json'], 22 | }, 23 | settings: { 24 | 'import/parsers': { 25 | '@typescript-eslint/parser': ['.ts'], 26 | }, 27 | 'import/resolver': { 28 | node: { 29 | extensions: ['.js', '.ts'], 30 | moduleDirectory: ['node_modules', 'src/'], 31 | }, 32 | typescript: { 33 | alwaysTryTypes: true, 34 | project: ['./tsconfig.json', 'packages/*/tsconfig.json'], 35 | }, 36 | }, 37 | }, 38 | ignorePatterns: [ 39 | '**/*.json', 40 | 'node_modules', 41 | '.turbo', 42 | 'dist', 43 | '.eslintrc.js', 44 | ], 45 | rules: { 46 | '@typescript-eslint/ban-ts-comment': 0, 47 | 'import/prefer-default-export': 'off', 48 | 'prettier/prettier': 1, 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/eslint-react.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:react/recommended', 10 | 'plugin:react-hooks/recommended', 11 | 'plugin:jsx-a11y/recommended', 12 | 'airbnb', 13 | 'airbnb-typescript', 14 | 'plugin:import/recommended', 15 | 'plugin:import/typescript', 16 | 'turbo', 17 | 'plugin:prettier/recommended', 18 | ], 19 | plugins: ['jsx-a11y', 'prettier'], 20 | settings: { 21 | 'import/parsers': { 22 | '@typescript-eslint/parser': ['.ts', '.tsx'], 23 | }, 24 | 'import/resolver': { 25 | typescript: { 26 | alwaysTryTypes: true, 27 | project: ['./tsconfig.json', 'apps/*/tsconfig.json'], 28 | }, 29 | }, 30 | }, 31 | ignorePatterns: [ 32 | '**/*.json', 33 | 'node_modules', 34 | '.turbo', 35 | 'dist', 36 | 'public', 37 | 'eslintrc.js', 38 | 'vite.config.ts', 39 | 'cypress.config.js', 40 | 'postcss.config.js', 41 | 'tailwind.config.js', 42 | ], 43 | rules: { 44 | 'react/jsx-no-target-blank': 0, 45 | 'react/react-in-jsx-scope': 0, 46 | 'react/require-default-props': 0, 47 | 'react/jsx-props-no-spreading': 0, 48 | 'react/prop-types': 0, 49 | 'react/jsx-no-constructed-context-values': 0, 50 | 'react/no-array-index-key': 0, 51 | 'jsx-a11y/no-noninteractive-element-to-interactive-role': 0, 52 | 'import/prefer-default-export': 0, 53 | '@typescript-eslint/no-use-before-define': 0, 54 | '@typescript-eslint/no-shadow': 0, 55 | '@typescript-eslint/no-empty-interface': 0, 56 | '@typescript-eslint/no-empty-function': 0, 57 | 'import/no-named-as-default': 0, 58 | 'default-case': 0, 59 | 'consistent-return': 0, 60 | 'prefer-destructuring': 0, 61 | 'prettier/prettier': 1, 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "files": [ 6 | "eslint-node.js", 7 | "eslint-react.js" 8 | ], 9 | "devDependencies": { 10 | "@typescript-eslint/eslint-plugin": "^5.59.1", 11 | "@typescript-eslint/parser": "^5.59.1", 12 | "eslint": "^8.38.0", 13 | "eslint-config-airbnb": "^19.0.4", 14 | "eslint-config-airbnb-base": "^15.0.0", 15 | "eslint-config-airbnb-typescript": "^17.0.0", 16 | "eslint-config-prettier": "^8.8.0", 17 | "eslint-config-turbo": "^1.9.0", 18 | "eslint-import-resolver-typescript": "^3.5.5", 19 | "eslint-plugin-import": "^2.27.5", 20 | "eslint-plugin-jest": "^27.2.3", 21 | "eslint-plugin-jsx-a11y": "^6.7.1", 22 | "eslint-plugin-prettier": "^4.2.1", 23 | "eslint-plugin-react": "^7.32.2", 24 | "eslint-plugin-react-hooks": "^4.6.0", 25 | "typescript": "^5.1.6" 26 | }, 27 | "publishConfig": { 28 | "access": "public" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['custom/eslint-node'], 3 | ignorePatterns: ['**/__tests__/*.ts'], 4 | parserOptions: { 5 | root: true, 6 | tsconfigRootDir: __dirname, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @currents/cypress-debugger-plugin 2 | 3 | ## 1.0.7 4 | 5 | ### Patch Changes 6 | 7 | - CSR-780 - fix test saving path 8 | - Updated dependencies 9 | - @currents/cypress-debugger-support@1.0.7 10 | 11 | ## 1.0.6 12 | 13 | ### Patch Changes 14 | 15 | - Custom filenames #49, export TS, refine license 16 | - Updated dependencies 17 | - @currents/cypress-debugger-support@1.0.6 18 | 19 | ## 1.0.5 20 | 21 | ### Patch Changes 22 | 23 | - Allow inclusion of traces for failing tests only 24 | - Updated dependencies 25 | - @currents/cypress-debugger-support@1.0.5 26 | 27 | ## 1.0.4 28 | 29 | ### Patch Changes 30 | 31 | - Allow setting the path for saving the reports 32 | - Updated dependencies 33 | - @currents/cypress-debugger-support@1.0.4 34 | 35 | ## 1.0.3 36 | 37 | ### Patch Changes 38 | 39 | - Initial release 40 | - Updated dependencies 41 | - @currents/cypress-debugger-support@1.0.3 42 | -------------------------------------------------------------------------------- /packages/plugin/README.md: -------------------------------------------------------------------------------- 1 | # Cypress Debugger - plugin file implementation 2 | 3 | Capture and replay Cypress Tests. Debug your failed and flaky CI cypress tests by replaying execution traces. 4 | 5 | - Cypress test execution steps 6 | - DOM snapshots 7 | - network requests (HAR) 8 | - browser console logs 9 | 10 | The plugin captures and replays everything that's happening in Cypress tests, think of it as Playwright traces for Cypress. 11 | 12 |
13 |
14 |
17 | Video Demo | Sorry Cypress | Currents 18 |
19 | 20 | ## Documentation 21 | 22 | https://github.com/currents-dev/cypress-debugger 23 | -------------------------------------------------------------------------------- /packages/plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../../jest.config'); 2 | module.exports = config; 3 | -------------------------------------------------------------------------------- /packages/plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@currents/cypress-debugger-plugin", 3 | "version": "1.0.9", 4 | "main": "./dist/index.js", 5 | "types": "./src/index.ts", 6 | "license": "GPL-3.0-or-later", 7 | "scripts": { 8 | "build": "tsup-node", 9 | "dev": "tsup --watch", 10 | "lint": "eslint src --fix", 11 | "publish-npm": "node ./publish.js", 12 | "test": "jest" 13 | }, 14 | "keywords": [ 15 | "cypress", 16 | "e2e", 17 | "cypress-debugger", 18 | "currents", 19 | "cypress-replay", 20 | "cypress-traces", 21 | "replay", 22 | "sorry-cypress", 23 | "time-travel" 24 | ], 25 | "files": [ 26 | "dist", 27 | "LICENSE.md" 28 | ], 29 | "devDependencies": { 30 | "@types/chrome-remote-interface": "^0.31.9", 31 | "@types/debug": "^4.1.7", 32 | "tsconfig": "*", 33 | "tsup": "^6.6.3", 34 | "typescript": "^5.1.6" 35 | }, 36 | "dependencies": { 37 | "@currents/cypress-debugger-support": "*", 38 | "@neuralegion/cypress-har-generator": "^5.16.4", 39 | "chrome-remote-interface": "^0.32.1", 40 | "debug": "^4.3.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin/publish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const fs = require('fs'); 5 | const pkg = require('./package.json'); 6 | const { argv } = require('process'); 7 | 8 | const tag = argv[2]; 9 | 10 | if (!tag) { 11 | console.error('ERROR: You must specify a tag (latest|beta)'); 12 | process.exit(1); 13 | } 14 | 15 | const newPkg = { 16 | ...pkg, 17 | types: undefined, 18 | files: ['*'], 19 | exports: { 20 | '.': { 21 | import: './index.mjs', 22 | require: './index.js', 23 | types: './index.d.ts', 24 | }, 25 | }, 26 | }; 27 | delete newPkg['types']; 28 | 29 | fs.writeFileSync( 30 | './dist/package.json', 31 | JSON.stringify(newPkg, null, 2), 32 | 'utf-8' 33 | ); 34 | fs.copyFileSync('./README.md', './dist/README.md'); 35 | fs.copyFileSync('../../LICENSE.md', './dist/LICENSE.md'); 36 | execSync(`npm publish --tag ${tag} ${argv[3]}`, { 37 | cwd: './dist', 38 | stdio: 'inherit', 39 | }); 40 | -------------------------------------------------------------------------------- /packages/plugin/src/__tests__/lib.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from '@jest/globals'; 2 | import { 3 | MAX_FILE_PATH_LENGTH, 4 | isValidLength, 5 | sanitizeFilename, 6 | truncateMiddle, 7 | truncatePathParts, 8 | } from '../lib'; 9 | 10 | describe('isValidLength', () => { 11 | const testCases: [string, number | undefined, boolean][] = [ 12 | ['short.txt', undefined, true], // Valid filename with default maxLength 13 | ['a'.repeat(MAX_FILE_PATH_LENGTH), undefined, true], // Valid filename with maximum maxLength 14 | ['a'.repeat(MAX_FILE_PATH_LENGTH + 1), undefined, false], // Invalid filename exceeding maxLength 15 | ['abcdefghij', 10, true], // Valid filename with specified maxLength 16 | ['abcdefghijk', 10, false], // Invalid filename exceeding specified maxLength 17 | ['anystring.txt', 0, false], // Invalid filename with maxLength of 0 18 | ]; 19 | 20 | it.each(testCases)( 21 | 'should return the exepcted result for the provided string and max length', 22 | (str: string, maxLength: number | undefined, expectedResult: boolean) => { 23 | expect(isValidLength(str, maxLength)).toBe(expectedResult); 24 | } 25 | ); 26 | }); 27 | 28 | describe('truncateMiddle', () => { 29 | const longString = 30 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel odio eu ligula tempus viverra. Aenean vehicula, ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A'; 31 | const truncatedString = 32 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel o..., ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A'; 33 | const testCases = [ 34 | ['short.txt', MAX_FILE_PATH_LENGTH, 'short.txt'], // Short filename within maximum length 35 | [longString, MAX_FILE_PATH_LENGTH, truncatedString], // Long filename, truncated with default separator 36 | ['short.txt', 5, 's...t'], // Short filename within a custom maximum length 37 | ['longname.txt', 8, 'lon...xt'], // Long filename truncated with custom maximum length and default separator 38 | ['filename.txt', 10, 'file...txt'], // Medium-length filename truncated with default separator 39 | ['middleseparator.txt', 15, 'middle...or.txt'], // Medium-length filename truncated with default separator 40 | ['separatoratstart.txt', 15, 'separa...rt.txt'], // Medium-length filename truncated with default separator 41 | ] as Array<[string, number, string]>; 42 | 43 | it.each(testCases)( 44 | 'should truncate the string correctly for the provided filename and max length', 45 | (filename, maxLength, expected) => { 46 | const result = truncateMiddle(filename, maxLength); 47 | expect(result).toBe(expected); 48 | } 49 | ); 50 | 51 | const truncatedStringWithDashes = 52 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel od--, ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A'; 53 | 54 | const customSeparatorTestCases = [ 55 | ['short.txt', MAX_FILE_PATH_LENGTH, 'short.txt'], // Short filename within maximum length 56 | [longString, MAX_FILE_PATH_LENGTH, truncatedStringWithDashes], // Long filename, truncated with default separator 57 | ['short.txt', 5, 'sh--t'], // Short filename within a custom maximum length 58 | ['longname.txt', 8, 'lon--txt'], // Long filename truncated with custom maximum length and default separator 59 | ['filename.txt', 10, 'file--.txt'], // Medium-length filename truncated with default separator 60 | ['middleseparator.txt', 15, 'middles--or.txt'], // Medium-length filename truncated with default separator 61 | ['separatoratstart.txt', 15, 'separat--rt.txt'], // Medium-length filename truncated with default separator 62 | ] as Array<[string, number, string]>; 63 | 64 | it.each(customSeparatorTestCases)( 65 | 'truncates "%s" to "%s" with max length %d and custom separator', 66 | (filename, maxLength, expected) => { 67 | const result = truncateMiddle(filename, maxLength, '--'); 68 | expect(result).toBe(expected); 69 | } 70 | ); 71 | }); 72 | 73 | describe('sanitizeFilename', () => { 74 | const testCases = [ 75 | ['validFilename.txt', 'validFilename.txt'], // Valid filename with no invalid characters 76 | ['file?with?question?mark.txt', 'file-with-question-mark.txt'], // Replace '?' with '-' 77 | ['file*with*asterisk.txt', 'file-with-asterisk.txt'], // Replace '*' with '-' 78 | ['file
13 |
14 |
17 | Video Demo | Sorry Cypress | Currents 18 |
19 | 20 | ## Documentation 21 | 22 | https://github.com/currents-dev/cypress-debugger 23 | -------------------------------------------------------------------------------- /packages/support/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@currents/cypress-debugger-support", 3 | "version": "1.0.9", 4 | "main": "./dist/index.js", 5 | "types": "./src/index.ts", 6 | "license": "GPL-3.0-or-later", 7 | "scripts": { 8 | "build": "tsup-node", 9 | "dev": "tsup --watch", 10 | "lint": "eslint src --fix", 11 | "publish-npm": "node ./publish.js" 12 | }, 13 | "keywords": [ 14 | "cypress", 15 | "e2e", 16 | "cypress-debugger", 17 | "currents", 18 | "cypress-replay", 19 | "cypress-traces", 20 | "replay", 21 | "sorry-cypress", 22 | "time-travel" 23 | ], 24 | "files": [ 25 | "dist", 26 | "LICENSE.md" 27 | ], 28 | "devDependencies": { 29 | "@types/json-stringify-safe": "^5.0.0", 30 | "@types/lodash": "^4.14.191", 31 | "@types/node": "^18.15.3", 32 | "cypress": "^12.5.1", 33 | "esbuild": "^0.17.7", 34 | "tsconfig": "*", 35 | "tsup": "^6.6.0", 36 | "typescript": "^5.1.6" 37 | }, 38 | "dependencies": { 39 | "@lukeed/uuid": "^2.0.0", 40 | "@neuralegion/cypress-har-generator": "^5.16.4", 41 | "json-stringify-safe": "^5.0.1", 42 | "lodash": "^4.17.21", 43 | "rrweb": "^2.0.0-alpha.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/support/publish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const fs = require('fs'); 5 | const pkg = require('./package.json'); 6 | const { argv } = require('process'); 7 | 8 | const tag = argv[2]; 9 | 10 | if (!tag) { 11 | console.error('ERROR: You must specify a tag (latest|beta)'); 12 | process.exit(1); 13 | } 14 | 15 | const newPkg = { 16 | ...pkg, 17 | types: undefined, 18 | files: ['*'], 19 | exports: { 20 | '.': { 21 | import: './index.mjs', 22 | require: './index.js', 23 | types: './index.d.ts', 24 | }, 25 | }, 26 | }; 27 | delete newPkg['types']; 28 | 29 | fs.writeFileSync( 30 | './dist/package.json', 31 | JSON.stringify(newPkg, null, 2), 32 | 'utf-8' 33 | ); 34 | fs.copyFileSync('./README.md', './dist/README.md'); 35 | fs.copyFileSync('../../LICENSE.md', './dist/LICENSE.md'); 36 | execSync(`npm publish --tag ${tag} ${argv[3]}`, { 37 | cwd: './dist', 38 | stdio: 'inherit', 39 | }); 40 | -------------------------------------------------------------------------------- /packages/support/src/@types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | rrwebRecord: typeof import('rrweb').record; 4 | } 5 | 6 | interface Cypress { 7 | cy: { 8 | recordHar: typeof import('@neuralegion/cypress-har-generator').recordHar; 9 | saveHar: typeof import('@neuralegion/cypress-har-generator').saveHar; 10 | disposeOfHar: typeof import('@neuralegion/cypress-har-generator').disposeOfHar; 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/support/src/cy/cy.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | ///