cypress-validate 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/cypress-validate.svg)](https://www.npmjs.com/package/cypress-validate)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+ [![Cypress Support](https://img.shields.io/badge/cypress-%3E%3D10.0.0--15.x-brightgreen.svg)](https://cypress.io)
7
8
  [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen.svg)](https://nodejs.org)
8
9
 
9
10
  ---
@@ -18,15 +19,15 @@ npm install --save-dev cypress-validate
18
19
  ```
19
20
 
20
21
  ### 2. Scaffold Your First Test
21
- Generate a new spec file using the `generate` command (like `codegen`):
22
+ Scaffold a new spec file:
22
23
 
23
24
  ```bash
24
- npx cypress-validate generate --name login --type spec --url http://localhost:3000
25
+ npx cypress-validate generate --name login --type spec
25
26
  ```
26
- *This creates `cypress/e2e/login.cy.js` with a boilerplate test.*
27
+ *This creates `cypress/e2e/login.cy.js` using your project's `baseUrl`.*
27
28
 
28
29
  ### 3. Run Your Tests
29
- Execute your tests in headless mode with a simple command:
30
+ Run all tests headlessly with a simple command:
30
31
 
31
32
  ```bash
32
33
  npx cypress-validate run
@@ -48,7 +49,7 @@ npx cypress-validate show-report
48
49
 
49
50
  ---
50
51
 
51
- ## �️ Troubleshooting
52
+ ## Troubleshooting
52
53
 
53
54
  ### Peer Dependency Conflicts (`ERESOLVE`)
54
55
  If you see an `ERESOLVE could not resolve` error during installation, it is usually because another plugin in your project (like `@4tw/cypress-drag-drop`) has a strict peer dependency on an older version of Cypress (e.g., `< 14`), while you are using a newer version.
@@ -61,7 +62,7 @@ npm install --save-dev cypress-validate --legacy-peer-deps
61
62
 
62
63
  ---
63
64
 
64
- ## �📋 Command Reference
65
+ ## 📋 Command Reference
65
66
 
66
67
  ### `run` — Run tests headlessly *(like `npx playwright test`)*
67
68
 
@@ -170,12 +171,12 @@ Displays: Node.js version, OS, Cypress version, available browsers.
170
171
 
171
172
  ---
172
173
 
173
- ### `generate` — Scaffold test files *(like `npx playwright codegen`)*
174
-
174
+ ### `generate` — Scaffold Files *(like `npx playwright codegen`)*
175
+ Scaffold tests, fixtures, page objects, or custom commands interactively.
175
176
  ```bash
176
- npx cypress-validate generate [options]
177
+ npx cypress-validate generate # Interactive mode
178
+ npx cypress-validate generate --name login --type spec
177
179
  ```
178
-
179
180
  | Flag | Description |
180
181
  |---|---|
181
182
  | `--name <name>` | File name (without extension) |
@@ -184,17 +185,6 @@ npx cypress-validate generate [options]
184
185
  | `--typescript` | Generate `.cy.ts` TypeScript files |
185
186
  | `--dir <dir>` | Custom output directory |
186
187
 
187
- ```bash
188
- # Interactive (prompts wizard)
189
- npx cypress-validate generate
190
-
191
- # Non-interactive
192
- npx cypress-validate generate --name login --type spec --url http://localhost:3000
193
- npx cypress-validate generate --name user --type fixture
194
- npx cypress-validate generate --name auth --type command
195
- npx cypress-validate generate --name dashboard --type page
196
- ```
197
-
198
188
  Generated file types:
199
189
 
200
190
  | Type | Output path |
@@ -206,49 +196,29 @@ Generated file types:
206
196
 
207
197
  ---
208
198
 
209
- ### `screenshot` — Take a URL screenshot *(like `npx playwright screenshot`)*
210
-
199
+ ### `screenshot` — Capture URL *(like `npx playwright screenshot`)*
200
+ Captures a headless screenshot of a URL.
211
201
  ```bash
212
- npx cypress-validate screenshot --url <url> [options]
213
- ```
214
-
215
- | Flag | Description |
216
- |---|---|
217
- | `--url <url>` | **(required)** URL to screenshot |
218
- | `--output <path>` | Output file (default: `cypress/screenshots/screenshot.png`) |
219
- | `--browser <name>` | Browser to use |
220
- | `--full-page` | Full-page screenshot |
221
- | `--viewport <WxH>` | Viewport dimensions (default: `1280x720`) |
222
-
223
- ```bash
224
- npx cypress-validate screenshot --url https://example.com
225
- npx cypress-validate screenshot --url https://example.com --full-page --output ./my-shot.png
226
- npx cypress-validate screenshot --url https://example.com --viewport 390x844
202
+ npx cypress-validate screenshot # Defaults to project baseUrl
203
+ npx cypress-validate screenshot --url https://google.com
204
+ npx cypress-validate screenshot --full-page --viewport 1920x1080
227
205
  ```
228
206
 
229
207
  ---
230
208
 
231
209
  ### `show-report` — Open HTML report *(like `npx playwright show-report`)*
232
-
210
+ Opens the latest Mochawesome HTML report.
233
211
  ```bash
234
- npx cypress-validate show-report [options]
212
+ npx cypress-validate show-report
213
+ npx cypress-validate show-report --serve # Serve on local HTTP server
235
214
  ```
236
215
 
237
- | Flag | Description |
238
- |---|---|
239
- | `--path <dir>` | Report directory (default: `cypress/reports`) |
240
- | `--serve` | Start a local HTTP server instead of opening |
241
- | `--port <n>` | Port for `--serve` mode (default: `9323`) |
216
+ ---
242
217
 
218
+ ### `show-trace` — Open Trace Viewer *(like `npx playwright show-trace`)*
219
+ Opens the interactive Cypress Test Runner for the last failed spec, allowing for time-travel debugging.
243
220
  ```bash
244
- # Open most recent report in browser
245
- npx cypress-validate show-report
246
-
247
- # Serve on a local port
248
- npx cypress-validate show-report --serve --port 8080
249
-
250
- # Point to a custom report path
251
- npx cypress-validate show-report --path cypress/reports/mochawesome.html
221
+ npx cypress-validate show-trace
252
222
  ```
253
223
 
254
224
  ---
@@ -294,9 +264,10 @@ CYPRESS_RECORD_KEY=abc123 npx cypress-validate record --parallel --group "CI Run
294
264
  | `npx playwright test --ui` | `npx cypress-validate open` |
295
265
  | `npx playwright test --debug` | `npx cypress-validate open --spec <file>` |
296
266
  | `npx playwright install` | `npx cypress-validate install` |
297
- | `npx playwright show-report` | `npx cypress-validate show-report` |
298
- | `npx playwright screenshot [url]` | `npx cypress-validate screenshot --url [url]` |
299
- | `npx playwright codegen` | `npx cypress-validate generate` |
267
+ | `npx playwright show-report` | `npx cypress-validate show-report` | Open HTML test report |
268
+ | `npx playwright show-trace` | `npx cypress-validate show-trace` | **New**: Open interactive trace for failed spec |
269
+ | `npx playwright screenshot [url]` | `npx cypress-validate screenshot` | Take a screenshot (optional URL) |
270
+ | `npx playwright codegen` | `npx cypress-validate generate` | Scaffold tests/fixtures/pages |
300
271
  | `npx playwright info` | `npx cypress-validate info` |
301
272
  | `npx playwright --version` | `npx cypress-validate --version` |
302
273
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const { Command } = require('commander');
@@ -9,145 +9,153 @@ const program = new Command();
9
9
 
10
10
  // ─── Banner ───────────────────────────────────────────────────────────────────
11
11
  function printBanner() {
12
- console.log(chalk.cyan.bold('\n ██████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗'));
13
- console.log(chalk.cyan.bold(' ██╔════╝╚██╗ ██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝'));
14
- console.log(chalk.cyan.bold(' ██║ ╚████╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗'));
15
- console.log(chalk.cyan.bold(' ██║ ╚██╔╝ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║'));
16
- console.log(chalk.cyan.bold(' ╚██████╗ ██║ ██║ ██║ ██║███████╗███████║███████║'));
17
- console.log(chalk.cyan.bold(' ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝'));
18
- console.log(chalk.gray(`\n validate — Playwright-style CLI for Cypress v${pkg.version}\n`));
12
+ console.log(chalk.cyan.bold('\n ██████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗'));
13
+ console.log(chalk.cyan.bold(' ██╔════╝╚██╗ ██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝'));
14
+ console.log(chalk.cyan.bold(' ██║ ╚████╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗'));
15
+ console.log(chalk.cyan.bold(' ██║ ╚██╔╝ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║'));
16
+ console.log(chalk.cyan.bold(' ╚██████╗ ██║ ██║ ██║ ██║███████╗███████║███████║'));
17
+ console.log(chalk.cyan.bold(' ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝'));
18
+ console.log(chalk.gray(`\n validate — Playwright-style CLI for Cypress v${pkg.version}\n`));
19
19
  }
20
20
 
21
21
  // ─── Program setup ────────────────────────────────────────────────────────────
22
22
  program
23
- .name('cypress-validate')
24
- .description(chalk.white('A Playwright-style CLI for Cypress projects'))
25
- .version(pkg.version, '-v, --version', 'Show version number')
26
- .addHelpText('before', () => {
27
- printBanner();
28
- return '';
29
- });
23
+ .name('cypress-validate')
24
+ .description(chalk.white('A Playwright-style CLI for Cypress projects'))
25
+ .version(pkg.version, '-v, --version', 'Show version number')
26
+ .addHelpText('before', () => {
27
+ printBanner();
28
+ return '';
29
+ });
30
30
 
31
31
  // ─── run ──────────────────────────────────────────────────────────────────────
32
32
  program
33
- .command('run')
34
- .description('Run Cypress tests in headless mode (like npx playwright test)')
35
- .option('-s, --spec <pattern>', 'Spec file or glob pattern to run')
36
- .option('-b, --browser <name>', 'Browser to run tests in (chrome, firefox, edge, electron)', 'electron')
37
- .option('--headed', 'Run in headed (visible) browser mode')
38
- .option('--headless', 'Run in headless mode (default)')
39
- .option('-g, --grep <pattern>', 'Only run tests matching the grep pattern')
40
- .option('-w, --workers <number>', 'Number of parallel test workers (requires Cypress Cloud)', '1')
41
- .option('--timeout <ms>', 'Default timeout in milliseconds', '30000')
42
- .option('--retry <count>', 'Number of times to retry a failing test', '0')
43
- .option('--reporter <name>', 'Reporter to use (mochawesome, spec, json, junit)', 'spec')
44
- .option('--record', 'Record test results to Cypress Cloud')
45
- .option('--key <key>', 'Cypress Cloud record key (or set CYPRESS_RECORD_KEY env)')
46
- .option('--last-failed', 'Re-run only the specs that failed in the last run')
47
- .option('--forbid-only', 'Fail if test.only() is used anywhere in the suite')
48
- .option('--config <json>', 'Override cypress.config.js values (JSON string)')
49
- .option('--env <key=value>', 'Set environment variables (e.g. --env baseUrl=http://localhost)')
50
- .option('--tag <tags>', 'Associate tags with this run (Cypress Cloud)')
51
- .option('-p, --port <number>', 'Override default Cypress port')
52
- .action((opts) => {
53
- require('../lib/commands/run')(opts);
54
- });
33
+ .command('run')
34
+ .description('Run Cypress tests in headless mode (like npx playwright test)')
35
+ .option('-s, --spec <pattern>', 'Spec file or glob pattern to run')
36
+ .option('-b, --browser <name>', 'Browser to run tests in (chrome, firefox, edge, electron)', 'electron')
37
+ .option('--headed', 'Run in headed (visible) browser mode')
38
+ .option('--headless', 'Run in headless mode (default)')
39
+ .option('-g, --grep <pattern>', 'Only run tests matching the grep pattern')
40
+ .option('-w, --workers <number>', 'Number of parallel test workers (requires Cypress Cloud)', '1')
41
+ .option('--timeout <ms>', 'Default timeout in milliseconds', '30000')
42
+ .option('--retry <count>', 'Number of times to retry a failing test', '0')
43
+ .option('--reporter <name>', 'Reporter to use (mochawesome, spec, json, junit)', 'spec')
44
+ .option('--record', 'Record test results to Cypress Cloud')
45
+ .option('--key <key>', 'Cypress Cloud record key (or set CYPRESS_RECORD_KEY env)')
46
+ .option('--last-failed', 'Re-run only the specs that failed in the last run')
47
+ .option('--forbid-only', 'Fail if test.only() is used anywhere in the suite')
48
+ .option('--config <json>', 'Override cypress.config.js values (JSON string)')
49
+ .option('--env <key=value>', 'Set environment variables (e.g. --env baseUrl=http://localhost)')
50
+ .option('--tag <tags>', 'Associate tags with this run (Cypress Cloud)')
51
+ .option('-p, --port <number>', 'Override default Cypress port')
52
+ .action((opts) => {
53
+ require('../lib/commands/run')(opts);
54
+ });
55
55
 
56
56
  // ─── open ─────────────────────────────────────────────────────────────────────
57
57
  program
58
- .command('open')
59
- .description('Open Cypress interactive Test Runner (like npx playwright test --ui)')
60
- .option('-b, --browser <name>', 'Browser to open (chrome, firefox, edge, electron)')
61
- .option('-s, --spec <pattern>', 'Spec file to open directly')
62
- .option('--config <json>', 'Override cypress.config.js values (JSON string)')
63
- .option('--env <key=value>', 'Set environment variables')
64
- .option('-p, --port <number>', 'Override default Cypress port')
65
- .action((opts) => {
66
- require('../lib/commands/open')(opts);
67
- });
58
+ .command('open')
59
+ .description('Open Cypress interactive Test Runner (like npx playwright test --ui)')
60
+ .option('-b, --browser <name>', 'Browser to open (chrome, firefox, edge, electron)')
61
+ .option('-s, --spec <pattern>', 'Spec file to open directly')
62
+ .option('--config <json>', 'Override cypress.config.js values (JSON string)')
63
+ .option('--env <key=value>', 'Set environment variables')
64
+ .option('-p, --port <number>', 'Override default Cypress port')
65
+ .action((opts) => {
66
+ require('../lib/commands/open')(opts);
67
+ });
68
68
 
69
69
  // ─── install ──────────────────────────────────────────────────────────────────
70
70
  program
71
- .command('install')
72
- .description('Install Cypress in the current project (like npx playwright install)')
73
- .option('--force', 'Force re-install even if Cypress is already installed')
74
- .option('--global', 'Install Cypress globally')
75
- .option('--version <version>', 'Install a specific version of Cypress')
76
- .action((opts) => {
77
- require('../lib/commands/install')(opts);
78
- });
71
+ .command('install')
72
+ .description('Install Cypress in the current project (like npx playwright install)')
73
+ .option('--force', 'Force re-install even if Cypress is already installed')
74
+ .option('--global', 'Install Cypress globally')
75
+ .option('--version <version>', 'Install a specific version of Cypress')
76
+ .action((opts) => {
77
+ require('../lib/commands/install')(opts);
78
+ });
79
79
 
80
80
  // ─── verify ───────────────────────────────────────────────────────────────────
81
81
  program
82
- .command('verify')
83
- .description('Verify that Cypress is installed correctly')
84
- .option('--force', 'Force re-verification')
85
- .action((opts) => {
86
- require('../lib/commands/verify')(opts);
87
- });
82
+ .command('verify')
83
+ .description('Verify that Cypress is installed correctly')
84
+ .option('--force', 'Force re-verification')
85
+ .action((opts) => {
86
+ require('../lib/commands/verify')(opts);
87
+ });
88
88
 
89
89
  // ─── info ─────────────────────────────────────────────────────────────────────
90
90
  program
91
- .command('info')
92
- .description('Print Cypress, system, and browser information')
93
- .action(() => {
94
- require('../lib/commands/info')();
95
- });
91
+ .command('info')
92
+ .description('Print Cypress, system, and browser information')
93
+ .action(() => {
94
+ require('../lib/commands/info')();
95
+ });
96
96
 
97
97
  // ─── generate ─────────────────────────────────────────────────────────────────
98
98
  program
99
- .command('generate')
100
- .description('Scaffold test files interactively (like npx playwright codegen)')
101
- .option('-n, --name <name>', 'Name for the generated file (without extension)')
102
- .option('-t, --type <type>', 'Type to generate: spec | fixture | command | page', 'spec')
103
- .option('-d, --dir <directory>', 'Target directory (defaults to Cypress convention)')
104
- .option('--url <url>', 'Base URL to include in the generated spec')
105
- .option('--typescript', 'Generate TypeScript (.cy.ts) files')
106
- .action((opts) => {
107
- require('../lib/commands/generate')(opts);
108
- });
99
+ .command('generate')
100
+ .description('Scaffold test files interactively (like npx playwright codegen)')
101
+ .option('-n, --name <name>', 'Name for the generated file (without extension)')
102
+ .option('-t, --type <type>', 'Type to generate: spec | fixture | command | page', 'spec')
103
+ .option('-d, --dir <directory>', 'Target directory (defaults to Cypress convention)')
104
+ .option('--url <url>', 'Base URL to focus on (scaffold visit)')
105
+ .option('--typescript', 'Generate TypeScript (.cy.ts) files')
106
+ .action((opts) => {
107
+ require('../lib/commands/generate')(opts);
108
+ });
109
109
 
110
110
  // ─── screenshot ───────────────────────────────────────────────────────────────
111
111
  program
112
- .command('screenshot')
113
- .description('Take a screenshot of a URL (like npx playwright screenshot)')
114
- .requiredOption('--url <url>', 'URL to screenshot')
115
- .option('-o, --output <path>', 'Output file path', 'cypress/screenshots/screenshot.png')
116
- .option('-b, --browser <name>', 'Browser to use', 'electron')
117
- .option('--full-page', 'Capture full-page screenshot')
118
- .option('--viewport <WxH>', 'Viewport size (e.g. 1280x720)', '1280x720')
119
- .action((opts) => {
120
- require('../lib/commands/screenshot')(opts);
121
- });
112
+ .command('screenshot')
113
+ .description('Take a screenshot of a URL (like npx playwright screenshot)')
114
+ .option('--url <url>', 'URL to screenshot (defaults to baseUrl from config)')
115
+ .option('-o, --output <path>', 'Output file path', 'cypress/screenshots/screenshot.png')
116
+ .option('-b, --browser <name>', 'Browser to use', 'electron')
117
+ .option('--full-page', 'Capture full-page screenshot')
118
+ .option('--viewport <WxH>', 'Viewport size (e.g. 1280x720)', '1280x720')
119
+ .action((opts) => {
120
+ require('../lib/commands/screenshot')(opts);
121
+ });
122
+
123
+ // ─── show-trace ───────────────────────────────────────────────────────────────
124
+ program
125
+ .command('show-trace')
126
+ .description('Open interactive trace for the last failed spec (Playwright style)')
127
+ .action(() => {
128
+ require('../lib/commands/show-trace')();
129
+ });
122
130
 
123
131
  // ─── show-report ──────────────────────────────────────────────────────────────
124
132
  program
125
- .command('show-report')
126
- .description('Open Cypress HTML test report in the browser (like npx playwright show-report)')
127
- .option('--path <path>', 'Path to the HTML report file or directory', 'cypress/reports')
128
- .option('--serve', 'Serve the report on a local HTTP server')
129
- .option('--port <number>', 'Port for the local server (with --serve)', '9323')
130
- .action((opts) => {
131
- require('../lib/commands/show-report')(opts);
132
- });
133
+ .command('show-report')
134
+ .description('Open Cypress HTML test report in the browser (like npx playwright show-report)')
135
+ .option('--path <path>', 'Path to the HTML report file or directory', 'cypress/reports')
136
+ .option('--serve', 'Serve the report on a local HTTP server')
137
+ .option('--port <number>', 'Port for the local server (with --serve)', '9323')
138
+ .action((opts) => {
139
+ require('../lib/commands/show-report')(opts);
140
+ });
133
141
 
134
142
  // ─── record ───────────────────────────────────────────────────────────────────
135
143
  program
136
- .command('record')
137
- .description('Run tests and record results to Cypress Cloud')
138
- .option('-s, --spec <pattern>', 'Spec file or glob pattern')
139
- .option('-b, --browser <name>', 'Browser to run tests in', 'electron')
140
- .option('--key <key>', 'Cypress Cloud record key')
141
- .option('--tag <tags>', 'Tags for this run')
142
- .option('--group <name>', 'Group name for this run')
143
- .option('--parallel', 'Run tests in parallel on Cypress Cloud')
144
- .option('--ci-build-id <id>', 'CI build ID for grouping parallel runs')
145
- .action((opts) => {
146
- require('../lib/commands/record')(opts);
147
- });
144
+ .command('record')
145
+ .description('Run tests and record results to Cypress Cloud')
146
+ .option('-s, --spec <pattern>', 'Spec file or glob pattern')
147
+ .option('-b, --browser <name>', 'Browser to run tests in', 'electron')
148
+ .option('--key <key>', 'Cypress Cloud record key')
149
+ .option('--tag <tags>', 'Tags for this run')
150
+ .option('--group <name>', 'Group name for this run')
151
+ .option('--parallel', 'Run tests in parallel on Cypress Cloud')
152
+ .option('--ci-build-id <id>', 'CI build ID for grouping parallel runs')
153
+ .action((opts) => {
154
+ require('../lib/commands/record')(opts);
155
+ });
148
156
 
149
157
  // ─── Global error handler ─────────────────────────────────────────────────────
150
158
  program.parseAsync(process.argv).catch((err) => {
151
- console.error(chalk.red('\n ✖ Error: ') + err.message);
152
- process.exit(1);
159
+ console.error(chalk.red('\n ✖ Error: ') + err.message);
160
+ process.exit(1);
153
161
  });
@@ -8,26 +8,26 @@ const { findProjectRoot } = require('../utils/config-finder');
8
8
 
9
9
  // ─── Templates ────────────────────────────────────────────────────────────────
10
10
 
11
- function specTemplate({ name, url, typescript }) {
12
- const ext = typescript ? 'cy.ts' : 'cy.js';
13
- const baseUrl = url || 'http://localhost:3000';
14
- return `/// <reference types="cypress" />
11
+ function specTemplate({ name, typescript }) {
12
+ const ext = typescript ? 'cy.ts' : 'cy.js';
13
+ return `/// <reference types="cypress" />
15
14
 
16
15
  describe('${name}', () => {
17
16
  beforeEach(() => {
18
- cy.visit('${baseUrl}');
17
+ // Visits baseUrl from cypress.config.js
18
+ cy.visit('/');
19
19
  });
20
20
 
21
- it('should load the page', () => {
22
- cy.title().should('not.be.empty');
21
+ it('should load the page successfully', () => {
22
+ cy.location('pathname').should('not.be.empty');
23
23
  });
24
24
 
25
- it('should display the main heading', () => {
26
- cy.get('h1').should('be.visible');
25
+ it('should have a visible body', () => {
26
+ cy.get('body').should('be.visible');
27
27
  });
28
28
 
29
- it('should be responsive', () => {
30
- cy.viewport(375, 667); // iPhone SE
29
+ it('should be mobile responsive', () => {
30
+ cy.viewport('iphone-6');
31
31
  cy.get('body').should('be.visible');
32
32
  });
33
33
  });
@@ -35,16 +35,16 @@ describe('${name}', () => {
35
35
  }
36
36
 
37
37
  function fixtureTemplate({ name }) {
38
- return JSON.stringify({
39
- id: 1,
40
- name: name,
41
- email: `${name.toLowerCase()}@example.com`,
42
- createdAt: new Date().toISOString(),
43
- }, null, 2) + '\n';
38
+ return JSON.stringify({
39
+ id: 1,
40
+ name: name,
41
+ email: `${name.toLowerCase()}@example.com`,
42
+ createdAt: new Date().toISOString(),
43
+ }, null, 2) + '\n';
44
44
  }
45
45
 
46
46
  function commandTemplate({ name }) {
47
- return `// cypress/support/commands/${name}.js
47
+ return `// cypress/support/commands/${name}.js
48
48
  // Custom Cypress commands for "${name}"
49
49
 
50
50
  /**
@@ -70,8 +70,8 @@ Cypress.Commands.add('${name}Api', (method, url, body) => {
70
70
  }
71
71
 
72
72
  function pageTemplate({ name }) {
73
- const ClassName = name.charAt(0).toUpperCase() + name.slice(1) + 'Page';
74
- return `// cypress/pages/${name}.page.js
73
+ const ClassName = name.charAt(0).toUpperCase() + name.slice(1) + 'Page';
74
+ return `// cypress/pages/${name}.page.js
75
75
  // Page Object Model for "${name}" page
76
76
 
77
77
  class ${ClassName} {
@@ -105,96 +105,89 @@ module.exports = new ${ClassName}();
105
105
  // ─── Main ─────────────────────────────────────────────────────────────────────
106
106
 
107
107
  module.exports = async function generateCommand(opts) {
108
- logger.title('Cypress Validate — Generate');
109
- logger.divider();
110
-
111
- const projectRoot = findProjectRoot();
112
- let { name, type, dir, url, typescript } = opts;
113
-
114
- // If not passed via flags, use interactive prompts
115
- if (!name || !type) {
116
- try {
117
- const { prompt } = require('enquirer');
118
- const answers = await prompt([
119
- !name && {
120
- type: 'input',
121
- name: 'name',
122
- message: 'File name (without extension):',
123
- initial: 'example',
124
- validate: (v) => v.trim() ? true : 'Name is required',
125
- },
126
- !type && {
127
- type: 'select',
128
- name: 'type',
129
- message: 'What would you like to generate?',
130
- choices: [
131
- { name: 'spec', message: '📄 Spec file (cypress/e2e/<name>.cy.js)' },
132
- { name: 'fixture', message: '📦 Fixture file (cypress/fixtures/<name>.json)' },
133
- { name: 'command', message: '🔧 Custom command (cypress/support/commands/<name>.js)' },
134
- { name: 'page', message: '📐 Page Object (cypress/pages/<name>.page.js)' },
135
- ],
136
- },
137
- !url && type === 'spec' && {
138
- type: 'input',
139
- name: 'url',
140
- message: 'Base URL for spec (optional):',
141
- initial: 'http://localhost:3000',
142
- },
143
- ].filter(Boolean));
144
- name = answers.name || name;
145
- type = answers.type || type;
146
- url = answers.url || url;
147
- } catch (_) {
148
- // enquirer not available or TTY not supported — use defaults
149
- name = name || 'example';
150
- type = type || 'spec';
151
- }
108
+ logger.title('Cypress Validate — Generate');
109
+ logger.divider();
110
+
111
+ const projectRoot = findProjectRoot();
112
+ let { name, type, dir, url, typescript } = opts;
113
+
114
+ // If not passed via flags, use interactive prompts
115
+ if (!name || !type) {
116
+ try {
117
+ const { prompt } = require('enquirer');
118
+ const answers = await prompt([
119
+ !name && {
120
+ type: 'input',
121
+ name: 'name',
122
+ message: 'File name (without extension):',
123
+ initial: 'example',
124
+ validate: (v) => v.trim() ? true : 'Name is required',
125
+ },
126
+ !type && {
127
+ type: 'select',
128
+ name: 'type',
129
+ message: 'What would you like to generate?',
130
+ choices: [
131
+ { name: 'spec', message: '📄 Spec file (cypress/e2e/<name>.cy.js)' },
132
+ { name: 'fixture', message: '📦 Fixture file (cypress/fixtures/<name>.json)' },
133
+ { name: 'command', message: '🔧 Custom command (cypress/support/commands/<name>.js)' },
134
+ { name: 'page', message: '📐 Page Object (cypress/pages/<name>.page.js)' },
135
+ ],
136
+ },
137
+ ].filter(Boolean));
138
+ name = answers.name || name;
139
+ type = answers.type || type;
140
+ } catch (_) {
141
+ // enquirer not available or TTY not supported — use defaults
142
+ name = name || 'example';
143
+ type = type || 'spec';
152
144
  }
145
+ }
153
146
 
154
- // Determine output path
155
- let content, outPath;
156
- switch (type) {
157
- case 'spec': {
158
- const ext = typescript ? 'cy.ts' : 'cy.js';
159
- const targetDir = dir || path.join(projectRoot, 'cypress', 'e2e');
160
- outPath = path.join(targetDir, `${name}.${ext}`);
161
- content = specTemplate({ name, url, typescript });
162
- break;
163
- }
164
- case 'fixture': {
165
- const targetDir = dir || path.join(projectRoot, 'cypress', 'fixtures');
166
- outPath = path.join(targetDir, `${name}.json`);
167
- content = fixtureTemplate({ name });
168
- break;
169
- }
170
- case 'command': {
171
- const targetDir = dir || path.join(projectRoot, 'cypress', 'support', 'commands');
172
- outPath = path.join(targetDir, `${name}.js`);
173
- content = commandTemplate({ name });
174
- break;
175
- }
176
- case 'page': {
177
- const targetDir = dir || path.join(projectRoot, 'cypress', 'pages');
178
- outPath = path.join(targetDir, `${name}.page.js`);
179
- content = pageTemplate({ name });
180
- break;
181
- }
182
- default:
183
- logger.error(`Unknown type: ${type}. Use: spec | fixture | command | page`);
184
- process.exit(1);
147
+ // Determine output path
148
+ let content, outPath;
149
+ switch (type) {
150
+ case 'spec': {
151
+ const ext = typescript ? 'cy.ts' : 'cy.js';
152
+ const targetDir = dir || path.join(projectRoot, 'cypress', 'e2e');
153
+ outPath = path.join(targetDir, `${name}.${ext}`);
154
+ content = specTemplate({ name, typescript });
155
+ break;
156
+ }
157
+ case 'fixture': {
158
+ const targetDir = dir || path.join(projectRoot, 'cypress', 'fixtures');
159
+ outPath = path.join(targetDir, `${name}.json`);
160
+ content = fixtureTemplate({ name });
161
+ break;
185
162
  }
163
+ case 'command': {
164
+ const targetDir = dir || path.join(projectRoot, 'cypress', 'support', 'commands');
165
+ outPath = path.join(targetDir, `${name}.js`);
166
+ content = commandTemplate({ name });
167
+ break;
168
+ }
169
+ case 'page': {
170
+ const targetDir = dir || path.join(projectRoot, 'cypress', 'pages');
171
+ outPath = path.join(targetDir, `${name}.page.js`);
172
+ content = pageTemplate({ name });
173
+ break;
174
+ }
175
+ default:
176
+ logger.error(`Unknown type: ${type}. Use: spec | fixture | command | page`);
177
+ process.exit(1);
178
+ }
186
179
 
187
- const spinner = createSpinner(`Generating ${type} → ${path.relative(projectRoot, outPath)}`);
188
- spinner.start();
180
+ const spinner = createSpinner(`Generating ${type} → ${path.relative(projectRoot, outPath)}`);
181
+ spinner.start();
189
182
 
190
- await fs.ensureDir(path.dirname(outPath));
191
- await fs.writeFile(outPath, content, 'utf-8');
183
+ await fs.ensureDir(path.dirname(outPath));
184
+ await fs.writeFile(outPath, content, 'utf-8');
192
185
 
193
- spinner.succeed(chalk.green(`Generated: ${chalk.cyan(path.relative(process.cwd(), outPath))}`));
194
- logger.blank();
195
- logger.info(`Type: ${chalk.bold(type)}`);
196
- logger.info(`Path: ${chalk.cyan(outPath)}`);
197
- logger.blank();
198
- logger.step('Next: ' + chalk.cyan(`npx cypress-validate run --spec "${path.relative(process.cwd(), outPath)}"`));
199
- logger.blank();
186
+ spinner.succeed(chalk.green(`Generated: ${chalk.cyan(path.relative(process.cwd(), outPath))}`));
187
+ logger.blank();
188
+ logger.info(`Type: ${chalk.bold(type)}`);
189
+ logger.info(`Path: ${chalk.cyan(outPath)}`);
190
+ logger.blank();
191
+ logger.step('Next: ' + chalk.cyan(`npx cypress-validate run --spec "${path.relative(process.cwd(), outPath)}"`));
192
+ logger.blank();
200
193
  };
@@ -126,6 +126,7 @@ module.exports = async function runCommand(opts) {
126
126
  if (err.exitCode !== undefined) {
127
127
  logger.error(`Tests completed with exit code ${err.exitCode}`);
128
128
  logger.info('Run ' + chalk.cyan('cypress-validate show-report') + ' to view the HTML report.');
129
+ logger.info('Run ' + chalk.cyan('cypress-validate show-trace') + ' to debug the last failure.');
129
130
  } else {
130
131
  logger.error('Failed to run Cypress: ' + err.message);
131
132
  logger.warn('Make sure Cypress is installed: ' + chalk.cyan('npx cypress-validate install'));
@@ -6,12 +6,25 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const chalk = require('chalk');
8
8
  const { logger, createSpinner } = require('../utils/logger');
9
- const { findProjectRoot } = require('../utils/config-finder');
9
+ const { findProjectRoot, getBaseUrl } = require('../utils/config-finder');
10
10
 
11
11
  module.exports = async function screenshotCommand(opts) {
12
12
  logger.title('Cypress Validate — Screenshot');
13
13
  logger.divider();
14
- logger.step(`URL: ${chalk.cyan(opts.url)}`);
14
+
15
+ // Fallback to baseUrl if URL is not provided
16
+ let targetUrl = opts.url;
17
+ if (!targetUrl) {
18
+ targetUrl = getBaseUrl();
19
+ }
20
+
21
+ if (!targetUrl) {
22
+ logger.error('No URL provided and no baseUrl found in cypress.config.js');
23
+ logger.info('Please provide a URL: ' + chalk.cyan('npx cypress-validate screenshot --url <url>'));
24
+ process.exit(1);
25
+ }
26
+
27
+ logger.step(`URL: ${chalk.cyan(targetUrl)}`);
15
28
  logger.step(`Output: ${chalk.cyan(opts.output)}`);
16
29
  logger.step(`Browser: ${chalk.cyan(opts.browser)}`);
17
30
  if (opts.fullPage) logger.step('Full-page: enabled');
@@ -42,9 +55,9 @@ module.exports = async function screenshotCommand(opts) {
42
55
  const specContent = `
43
56
  /// <reference types="cypress" />
44
57
  describe('cypress-validate screenshot', () => {
45
- it('captures a screenshot of ${opts.url}', () => {
58
+ it('captures a screenshot of ${targetUrl}', () => {
46
59
  cy.viewport(${viewportWidth}, ${viewportHeight});
47
- cy.visit(${JSON.stringify(opts.url)});
60
+ cy.visit(${JSON.stringify(targetUrl)});
48
61
  cy.screenshot('screenshot', {
49
62
  capture: ${opts.fullPage ? "'fullPage'" : "'viewport'"},
50
63
  });
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const execa = require('execa');
4
+ const fs = require('fs-extra');
5
+ const chalk = require('chalk');
6
+ const { logger } = require('../utils/logger');
7
+ const { findProjectRoot, lastRunResultsPath } = require('../utils/config-finder');
8
+
9
+ module.exports = async function showTraceCommand() {
10
+ logger.title('Cypress Validate — Show Trace');
11
+ logger.divider();
12
+
13
+ const projectRoot = findProjectRoot();
14
+ const resultsFile = lastRunResultsPath(projectRoot);
15
+
16
+ if (!await fs.pathExists(resultsFile)) {
17
+ logger.error('No previous run results found.');
18
+ logger.info('Please run your tests first: ' + chalk.cyan('npx cypress-validate run'));
19
+ process.exit(1);
20
+ }
21
+
22
+ const data = await fs.readJson(resultsFile);
23
+ if (!data.failedSpecs || data.failedSpecs.length === 0) {
24
+ logger.success('The last run was successful! No traces to show.');
25
+ logger.info('To debug a spec interactively, use: ' + chalk.cyan('npx cypress-validate open --spec <path>'));
26
+ process.exit(0);
27
+ }
28
+
29
+ const lastFailed = data.failedSpecs[0];
30
+ logger.info(`Opening trace (Test Runner) for last failed spec:`);
31
+ logger.step(chalk.cyan(lastFailed));
32
+ logger.blank();
33
+ logger.tip('In Cypress, the "Trace Viewer" is the interactive Test Runner.');
34
+ logger.tip('It allows you to travel back in time through every command.');
35
+ logger.blank();
36
+
37
+ try {
38
+ await execa('npx', ['cypress', 'open', '--e2e', '--spec', lastFailed], {
39
+ cwd: projectRoot,
40
+ stdio: 'inherit',
41
+ });
42
+ } catch (err) {
43
+ logger.error('Failed to open trace: ' + err.message);
44
+ }
45
+ };
@@ -43,4 +43,21 @@ function lastRunResultsPath(projectRoot = findProjectRoot()) {
43
43
  return path.join(projectRoot, '.cypress-validate', 'last-run.json');
44
44
  }
45
45
 
46
- module.exports = { findCypressConfig, findProjectRoot, lastRunResultsPath };
46
+ /**
47
+ * Attempts to extract baseUrl from the Cypress config file.
48
+ */
49
+ function getBaseUrl() {
50
+ const configPath = findCypressConfig();
51
+ if (!configPath) return null;
52
+
53
+ try {
54
+ const content = fs.readFileSync(configPath, 'utf8');
55
+ // Simple regex to find baseUrl: "baseUrl": "..." or baseUrl: '...'
56
+ const match = content.match(/baseUrl\s*:\s*['"]([^'"]+)['"]/);
57
+ return match ? match[1] : null;
58
+ } catch (err) {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ module.exports = { findCypressConfig, findProjectRoot, lastRunResultsPath, getBaseUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-validate",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A Playwright-style CLI for Cypress — run, open, generate, screenshot, report and more via npx cypress-validate",
5
5
  "main": "lib/index.js",
6
6
  "bin": {