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 +26 -56
- package/bin/cypress-validate.js +116 -108
- package/lib/commands/generate.js +99 -106
- package/lib/commands/run.js +1 -0
- package/lib/commands/screenshot.js +17 -4
- package/lib/commands/show-trace.js +45 -0
- package/lib/utils/config-finder.js +18 -1
- package/package.json +1 -1
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
|
-
|
|
22
|
+
Scaffold a new spec file:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npx cypress-validate generate --name login --type spec
|
|
25
|
+
npx cypress-validate generate --name login --type spec
|
|
26
26
|
```
|
|
27
|
-
*This creates `cypress/e2e/login.cy.js`
|
|
27
|
+
*This creates `cypress/e2e/login.cy.js` using your project's `baseUrl`.*
|
|
28
28
|
|
|
29
29
|
### 3. Run Your Tests
|
|
30
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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` —
|
|
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
|
|
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
|
|
212
|
+
npx cypress-validate show-report
|
|
213
|
+
npx cypress-validate show-report --serve # Serve on local HTTP server
|
|
236
214
|
```
|
|
237
215
|
|
|
238
|
-
|
|
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
|
-
|
|
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
|
|
300
|
-
| `npx playwright
|
|
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
|
|
package/bin/cypress-validate.js
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
159
|
+
console.error(chalk.red('\n ✖ Error: ') + err.message);
|
|
160
|
+
process.exit(1);
|
|
153
161
|
});
|
package/lib/commands/generate.js
CHANGED
|
@@ -8,26 +8,26 @@ const { findProjectRoot } = require('../utils/config-finder');
|
|
|
8
8
|
|
|
9
9
|
// ─── Templates ────────────────────────────────────────────────────────────────
|
|
10
10
|
|
|
11
|
-
function specTemplate({ name,
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
+
// Visits baseUrl from cypress.config.js
|
|
18
|
+
cy.visit('/');
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it('should load the page', () => {
|
|
22
|
-
cy.
|
|
21
|
+
it('should load the page successfully', () => {
|
|
22
|
+
cy.location('pathname').should('not.be.empty');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('should
|
|
26
|
-
cy.get('
|
|
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(
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
188
|
-
|
|
180
|
+
const spinner = createSpinner(`Generating ${type} → ${path.relative(projectRoot, outPath)}`);
|
|
181
|
+
spinner.start();
|
|
189
182
|
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
await fs.ensureDir(path.dirname(outPath));
|
|
184
|
+
await fs.writeFile(outPath, content, 'utf-8');
|
|
192
185
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
};
|
package/lib/commands/run.js
CHANGED
|
@@ -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
|
-
|
|
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 ${
|
|
58
|
+
it('captures a screenshot of ${targetUrl}', () => {
|
|
46
59
|
cy.viewport(${viewportWidth}, ${viewportHeight});
|
|
47
|
-
cy.visit(${JSON.stringify(
|
|
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
|
-
|
|
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