cypress-validate 1.0.3 → 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
@@ -19,15 +19,15 @@ npm install --save-dev cypress-validate
19
19
  ```
20
20
 
21
21
  ### 2. Scaffold Your First Test
22
- Generate a new spec file using the `generate` command (like `codegen`):
22
+ Scaffold a new spec file:
23
23
 
24
24
  ```bash
25
- npx cypress-validate generate --name login --type spec --url http://localhost:3000
25
+ npx cypress-validate generate --name login --type spec
26
26
  ```
27
- *This creates `cypress/e2e/login.cy.js` with a boilerplate test.*
27
+ *This creates `cypress/e2e/login.cy.js` using your project's `baseUrl`.*
28
28
 
29
29
  ### 3. Run Your Tests
30
- Execute your tests in headless mode with a simple command:
30
+ Run all tests headlessly with a simple command:
31
31
 
32
32
  ```bash
33
33
  npx cypress-validate run
@@ -49,7 +49,7 @@ npx cypress-validate show-report
49
49
 
50
50
  ---
51
51
 
52
- ## �️ Troubleshooting
52
+ ## Troubleshooting
53
53
 
54
54
  ### Peer Dependency Conflicts (`ERESOLVE`)
55
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.
@@ -62,7 +62,7 @@ npm install --save-dev cypress-validate --legacy-peer-deps
62
62
 
63
63
  ---
64
64
 
65
- ## �📋 Command Reference
65
+ ## 📋 Command Reference
66
66
 
67
67
  ### `run` — Run tests headlessly *(like `npx playwright test`)*
68
68
 
@@ -171,12 +171,12 @@ Displays: Node.js version, OS, Cypress version, available browsers.
171
171
 
172
172
  ---
173
173
 
174
- ### `generate` — Scaffold test files *(like `npx playwright codegen`)*
175
-
174
+ ### `generate` — Scaffold Files *(like `npx playwright codegen`)*
175
+ Scaffold tests, fixtures, page objects, or custom commands interactively.
176
176
  ```bash
177
- npx cypress-validate generate [options]
177
+ npx cypress-validate generate # Interactive mode
178
+ npx cypress-validate generate --name login --type spec
178
179
  ```
179
-
180
180
  | Flag | Description |
181
181
  |---|---|
182
182
  | `--name <name>` | File name (without extension) |
@@ -185,17 +185,6 @@ npx cypress-validate generate [options]
185
185
  | `--typescript` | Generate `.cy.ts` TypeScript files |
186
186
  | `--dir <dir>` | Custom output directory |
187
187
 
188
- ```bash
189
- # Interactive (prompts wizard)
190
- npx cypress-validate generate
191
-
192
- # Non-interactive
193
- npx cypress-validate generate --name login --type spec --url http://localhost:3000
194
- npx cypress-validate generate --name user --type fixture
195
- npx cypress-validate generate --name auth --type command
196
- npx cypress-validate generate --name dashboard --type page
197
- ```
198
-
199
188
  Generated file types:
200
189
 
201
190
  | Type | Output path |
@@ -207,49 +196,29 @@ Generated file types:
207
196
 
208
197
  ---
209
198
 
210
- ### `screenshot` — Take a URL screenshot *(like `npx playwright screenshot`)*
211
-
199
+ ### `screenshot` — Capture URL *(like `npx playwright screenshot`)*
200
+ Captures a headless screenshot of a URL.
212
201
  ```bash
213
- npx cypress-validate screenshot --url <url> [options]
214
- ```
215
-
216
- | Flag | Description |
217
- |---|---|
218
- | `--url <url>` | **(required)** URL to screenshot |
219
- | `--output <path>` | Output file (default: `cypress/screenshots/screenshot.png`) |
220
- | `--browser <name>` | Browser to use |
221
- | `--full-page` | Full-page screenshot |
222
- | `--viewport <WxH>` | Viewport dimensions (default: `1280x720`) |
223
-
224
- ```bash
225
- npx cypress-validate screenshot --url https://example.com
226
- npx cypress-validate screenshot --url https://example.com --full-page --output ./my-shot.png
227
- 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
228
205
  ```
229
206
 
230
207
  ---
231
208
 
232
209
  ### `show-report` — Open HTML report *(like `npx playwright show-report`)*
233
-
210
+ Opens the latest Mochawesome HTML report.
234
211
  ```bash
235
- npx cypress-validate show-report [options]
212
+ npx cypress-validate show-report
213
+ npx cypress-validate show-report --serve # Serve on local HTTP server
236
214
  ```
237
215
 
238
- | Flag | Description |
239
- |---|---|
240
- | `--path <dir>` | Report directory (default: `cypress/reports`) |
241
- | `--serve` | Start a local HTTP server instead of opening |
242
- | `--port <n>` | Port for `--serve` mode (default: `9323`) |
216
+ ---
243
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.
244
220
  ```bash
245
- # Open most recent report in browser
246
- npx cypress-validate show-report
247
-
248
- # Serve on a local port
249
- npx cypress-validate show-report --serve --port 8080
250
-
251
- # Point to a custom report path
252
- npx cypress-validate show-report --path cypress/reports/mochawesome.html
221
+ npx cypress-validate show-trace
253
222
  ```
254
223
 
255
224
  ---
@@ -295,9 +264,10 @@ CYPRESS_RECORD_KEY=abc123 npx cypress-validate record --parallel --group "CI Run
295
264
  | `npx playwright test --ui` | `npx cypress-validate open` |
296
265
  | `npx playwright test --debug` | `npx cypress-validate open --spec <file>` |
297
266
  | `npx playwright install` | `npx cypress-validate install` |
298
- | `npx playwright show-report` | `npx cypress-validate show-report` |
299
- | `npx playwright screenshot [url]` | `npx cypress-validate screenshot --url [url]` |
300
- | `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 |
301
271
  | `npx playwright info` | `npx cypress-validate info` |
302
272
  | `npx playwright --version` | `npx cypress-validate --version` |
303
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.3",
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": {