specforge-mcp 0.2.2 → 0.4.0

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.
Files changed (76) hide show
  1. package/dist/engine/skill-generator/sections-skills.d.ts +4 -0
  2. package/dist/engine/skill-generator/sections-skills.d.ts.map +1 -0
  3. package/dist/engine/skill-generator/sections-skills.js +51 -0
  4. package/dist/engine/skill-generator/sections-skills.js.map +1 -0
  5. package/dist/engine/skill-generator/sections.d.ts +1 -0
  6. package/dist/engine/skill-generator/sections.d.ts.map +1 -1
  7. package/dist/engine/skill-generator/sections.js +2 -1
  8. package/dist/engine/skill-generator/sections.js.map +1 -1
  9. package/dist/engine/skill-generator.d.ts +3 -1
  10. package/dist/engine/skill-generator.d.ts.map +1 -1
  11. package/dist/engine/skill-generator.js +11 -2
  12. package/dist/engine/skill-generator.js.map +1 -1
  13. package/dist/engine/test-plan-generator.d.ts +3 -0
  14. package/dist/engine/test-plan-generator.d.ts.map +1 -0
  15. package/dist/engine/test-plan-generator.js +166 -0
  16. package/dist/engine/test-plan-generator.js.map +1 -0
  17. package/dist/engine/test-spec-generator.d.ts +8 -0
  18. package/dist/engine/test-spec-generator.d.ts.map +1 -0
  19. package/dist/engine/test-spec-generator.js +348 -0
  20. package/dist/engine/test-spec-generator.js.map +1 -0
  21. package/dist/tools/generate-rules.d.ts.map +1 -1
  22. package/dist/tools/generate-rules.js +20 -0
  23. package/dist/tools/generate-rules.js.map +1 -1
  24. package/dist/tools/generate-tests/generators/database-test-generator.d.ts +11 -0
  25. package/dist/tools/generate-tests/generators/database-test-generator.d.ts.map +1 -0
  26. package/dist/tools/generate-tests/generators/database-test-generator.js +329 -0
  27. package/dist/tools/generate-tests/generators/database-test-generator.js.map +1 -0
  28. package/dist/tools/generate-tests/generators/graphql-test-generator.d.ts +17 -0
  29. package/dist/tools/generate-tests/generators/graphql-test-generator.d.ts.map +1 -0
  30. package/dist/tools/generate-tests/generators/graphql-test-generator.js +235 -0
  31. package/dist/tools/generate-tests/generators/graphql-test-generator.js.map +1 -0
  32. package/dist/tools/generate-tests/generators/grpc-test-generator.d.ts +17 -0
  33. package/dist/tools/generate-tests/generators/grpc-test-generator.d.ts.map +1 -0
  34. package/dist/tools/generate-tests/generators/grpc-test-generator.js +283 -0
  35. package/dist/tools/generate-tests/generators/grpc-test-generator.js.map +1 -0
  36. package/dist/tools/generate-tests/generators/microservices-test-generator.d.ts +10 -0
  37. package/dist/tools/generate-tests/generators/microservices-test-generator.d.ts.map +1 -0
  38. package/dist/tools/generate-tests/generators/microservices-test-generator.js +341 -0
  39. package/dist/tools/generate-tests/generators/microservices-test-generator.js.map +1 -0
  40. package/dist/tools/generate-tests/generators/security-test-generator.d.ts +11 -0
  41. package/dist/tools/generate-tests/generators/security-test-generator.d.ts.map +1 -0
  42. package/dist/tools/generate-tests/generators/security-test-generator.js +318 -0
  43. package/dist/tools/generate-tests/generators/security-test-generator.js.map +1 -0
  44. package/dist/tools/generate-tests/generators/visual-regression-generator.d.ts +19 -0
  45. package/dist/tools/generate-tests/generators/visual-regression-generator.d.ts.map +1 -0
  46. package/dist/tools/generate-tests/generators/visual-regression-generator.js +304 -0
  47. package/dist/tools/generate-tests/generators/visual-regression-generator.js.map +1 -0
  48. package/dist/tools/generate-tests/generators/websocket-test-generator.d.ts +17 -0
  49. package/dist/tools/generate-tests/generators/websocket-test-generator.d.ts.map +1 -0
  50. package/dist/tools/generate-tests/generators/websocket-test-generator.js +243 -0
  51. package/dist/tools/generate-tests/generators/websocket-test-generator.js.map +1 -0
  52. package/dist/tools/generate-tests/plan-mode-handler.d.ts +3 -0
  53. package/dist/tools/generate-tests/plan-mode-handler.d.ts.map +1 -0
  54. package/dist/tools/generate-tests/plan-mode-handler.js +54 -0
  55. package/dist/tools/generate-tests/plan-mode-handler.js.map +1 -0
  56. package/dist/tools/generate-tests/spec-dispatcher.d.ts.map +1 -1
  57. package/dist/tools/generate-tests/spec-dispatcher.js +46 -0
  58. package/dist/tools/generate-tests/spec-dispatcher.js.map +1 -1
  59. package/dist/tools/generate-tests/test-helpers.d.ts +8 -0
  60. package/dist/tools/generate-tests/test-helpers.d.ts.map +1 -0
  61. package/dist/tools/generate-tests/test-helpers.js +120 -0
  62. package/dist/tools/generate-tests/test-helpers.js.map +1 -0
  63. package/dist/tools/generate-tests.d.ts.map +1 -1
  64. package/dist/tools/generate-tests.js +6 -118
  65. package/dist/tools/generate-tests.js.map +1 -1
  66. package/dist/tools/init-project/handler.d.ts.map +1 -1
  67. package/dist/tools/init-project/handler.js +29 -0
  68. package/dist/tools/init-project/handler.js.map +1 -1
  69. package/dist/types/stack/recommend.d.ts +2 -0
  70. package/dist/types/stack/recommend.d.ts.map +1 -1
  71. package/dist/types/testing.d.ts +51 -0
  72. package/dist/types/testing.d.ts.map +1 -1
  73. package/package.json +3 -2
  74. package/src/i18n/messages/en.json +333 -0
  75. package/src/i18n/messages/es.json +333 -0
  76. package/src/i18n/messages/pt.json +333 -0
@@ -0,0 +1,318 @@
1
+ // tools/generate-tests/generators/security-test-generator.ts — SPEC-058b Section G
2
+ // Generates security test scaffolds:
3
+ // OWASP Top 10, auth flows, file upload, API rate limiting.
4
+ const AUTH_SIGNALS = [
5
+ 'jwt',
6
+ 'oauth',
7
+ 'passport',
8
+ 'keycloak',
9
+ 'auth0',
10
+ 'clerk',
11
+ 'supabase',
12
+ 'session',
13
+ 'cookie-session',
14
+ 'express-session',
15
+ 'next-auth',
16
+ 'lucia',
17
+ 'better-auth',
18
+ ];
19
+ const UPLOAD_SIGNALS = [
20
+ 'multer',
21
+ 'formidable',
22
+ 'busboy',
23
+ 'sharp',
24
+ 'jimp',
25
+ 'imagemagick',
26
+ 'file-upload',
27
+ 'multipart',
28
+ ];
29
+ export function detectAuth(knowledge) {
30
+ const stackLower = knowledge.stack.map((s) => s.toLowerCase());
31
+ for (const sig of AUTH_SIGNALS) {
32
+ if (stackLower.some((s) => s.includes(sig))) {
33
+ return sig;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ export function detectFileUpload(knowledge) {
39
+ const stackLower = knowledge.stack.map((s) => s.toLowerCase());
40
+ for (const sig of UPLOAD_SIGNALS) {
41
+ if (stackLower.some((s) => s.includes(sig))) {
42
+ return sig;
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ export function hasPublicApi(knowledge) {
48
+ if (knowledge.apiContracts.length > 0) {
49
+ return true;
50
+ }
51
+ return knowledge.apps.some((a) => a.type === 'backend');
52
+ }
53
+ export function isSecurityTestProject(knowledge) {
54
+ if (detectAuth(knowledge) !== null) {
55
+ return true;
56
+ }
57
+ if (hasPublicApi(knowledge)) {
58
+ return true;
59
+ }
60
+ return knowledge.apps.some((a) => a.type === 'backend' || a.type === 'frontend');
61
+ }
62
+ export function generateSecurityTestDefs(title, testDir, _testExt, knowledge) {
63
+ const defs = [];
64
+ if (hasPublicApi(knowledge)) {
65
+ defs.push({
66
+ name: `${title} — OWASP Top 10: injection, XSS, CSRF, auth bypass`,
67
+ type: 'integration',
68
+ file: `${testDir}/security/`,
69
+ description: 'Verify protection against OWASP Top 10 vulnerabilities on public endpoints',
70
+ priority: 'critical',
71
+ automatable: true,
72
+ });
73
+ }
74
+ if (detectAuth(knowledge) !== null) {
75
+ defs.push({
76
+ name: `${title} — auth: token expiration, refresh flow, privilege escalation`,
77
+ type: 'integration',
78
+ file: `${testDir}/security/`,
79
+ description: 'Verify auth token lifecycle, refresh flow, and privilege escalation prevention',
80
+ priority: 'critical',
81
+ automatable: true,
82
+ });
83
+ }
84
+ if (detectFileUpload(knowledge) !== null) {
85
+ defs.push({
86
+ name: `${title} — file upload: type validation, size limits, path traversal`,
87
+ type: 'integration',
88
+ file: `${testDir}/security/`,
89
+ description: 'Verify file upload security: type validation, size limits, path traversal prevention',
90
+ priority: 'high',
91
+ automatable: true,
92
+ });
93
+ }
94
+ if (hasPublicApi(knowledge)) {
95
+ defs.push({
96
+ name: `${title} — API security: rate limiting, CORS, API key rotation`,
97
+ type: 'integration',
98
+ file: `${testDir}/security/`,
99
+ description: 'Verify rate limiting effectiveness, CORS policy, and API key rotation support',
100
+ priority: 'high',
101
+ automatable: true,
102
+ });
103
+ }
104
+ return defs;
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Scaffold builders
108
+ // ---------------------------------------------------------------------------
109
+ function buildTsSecurityTest(title, auth, upload, publicApi) {
110
+ const owaspSection = publicApi
111
+ ? `
112
+ describe('OWASP Top 10', () => {
113
+ it('rejects SQL injection in query parameters', async () => {
114
+ // const res = await request(app).get("/api/users?id=' OR 1=1 --");
115
+ // expect(res.status).toBe(400);
116
+ expect(true).toBe(true);
117
+ });
118
+
119
+ it('escapes XSS payloads in response', async () => {
120
+ // const res = await request(app).post('/api/comments')
121
+ // .send({ body: '<script>alert("xss")</script>' });
122
+ // expect(res.body.body).not.toContain('<script>');
123
+ expect(true).toBe(true);
124
+ });
125
+
126
+ it('validates CSRF token on state-changing requests', async () => {
127
+ expect(true).toBe(true);
128
+ });
129
+
130
+ it('rejects auth bypass attempts', async () => {
131
+ expect(true).toBe(true);
132
+ });
133
+ });
134
+ `
135
+ : '';
136
+ const authSection = auth
137
+ ? `
138
+ describe('Authentication (${auth})', () => {
139
+ it('rejects expired token with 401', async () => {
140
+ // const expiredToken = signToken({ exp: Math.floor(Date.now()/1000) - 60 });
141
+ // const res = await request(app).get('/api/me').set('Authorization', \`Bearer \${expiredToken}\`);
142
+ // expect(res.status).toBe(401);
143
+ expect(true).toBe(true);
144
+ });
145
+
146
+ it('refresh token returns new access token', async () => {
147
+ expect(true).toBe(true);
148
+ });
149
+
150
+ it('prevents privilege escalation (user cannot access admin)', async () => {
151
+ expect(true).toBe(true);
152
+ });
153
+
154
+ it('prevents session fixation attack', async () => {
155
+ expect(true).toBe(true);
156
+ });
157
+ });
158
+ `
159
+ : '';
160
+ const uploadSection = upload
161
+ ? `
162
+ describe('File upload (${upload})', () => {
163
+ it('rejects disallowed file types', async () => {
164
+ // const res = await request(app).post('/api/upload')
165
+ // .attach('file', Buffer.from('#!/bin/sh'), { filename: 'evil.sh' });
166
+ // expect(res.status).toBe(400);
167
+ expect(true).toBe(true);
168
+ });
169
+
170
+ it('enforces maximum file size', async () => {
171
+ expect(true).toBe(true);
172
+ });
173
+
174
+ it('prevents path traversal in filename', async () => {
175
+ // filename: '../../etc/passwd' → should be sanitized
176
+ expect(true).toBe(true);
177
+ });
178
+
179
+ it('scans for malicious content', async () => {
180
+ expect(true).toBe(true);
181
+ });
182
+ });
183
+ `
184
+ : '';
185
+ const apiSection = publicApi
186
+ ? `
187
+ describe('API security', () => {
188
+ it('rate limiter blocks excessive requests', async () => {
189
+ // for (let i = 0; i < 101; i++) await request(app).get('/api/data');
190
+ // const res = await request(app).get('/api/data');
191
+ // expect(res.status).toBe(429);
192
+ expect(true).toBe(true);
193
+ });
194
+
195
+ it('CORS rejects requests from unauthorized origins', async () => {
196
+ expect(true).toBe(true);
197
+ });
198
+
199
+ it('supports API key rotation without downtime', async () => {
200
+ expect(true).toBe(true);
201
+ });
202
+ });
203
+ `
204
+ : '';
205
+ return `// Security tests for: ${title}
206
+ // Generated by SpecForge SDD MCP Server (SPEC-058b)
207
+ import { describe, it, expect } from 'vitest';
208
+
209
+ describe('${title} — Security Tests', () => {
210
+ ${owaspSection}${authSection}${uploadSection}${apiSection}});
211
+ `;
212
+ }
213
+ function buildPythonSecurityTest(title, auth, upload, publicApi) {
214
+ const cls = title.replace(/[^a-zA-Z0-9]/g, '');
215
+ const owaspSection = publicApi
216
+ ? `
217
+ def test_sql_injection_rejected(self) -> None:
218
+ """SQL injection in query parameters is rejected."""
219
+ # response = client.get("/api/users?id=' OR 1=1 --")
220
+ # assert response.status_code == 400
221
+ pass
222
+
223
+ def test_xss_payload_escaped(self) -> None:
224
+ """XSS payloads are escaped in response."""
225
+ pass
226
+
227
+ def test_csrf_token_validated(self) -> None:
228
+ """CSRF token is required on state-changing requests."""
229
+ pass
230
+
231
+ def test_auth_bypass_rejected(self) -> None:
232
+ """Auth bypass attempts are rejected."""
233
+ pass
234
+ `
235
+ : '';
236
+ const authSection = auth
237
+ ? `
238
+ def test_expired_token_returns_401(self) -> None:
239
+ """Expired token is rejected with 401."""
240
+ pass
241
+
242
+ def test_refresh_token_returns_new_access_token(self) -> None:
243
+ """Refresh token flow issues a new access token."""
244
+ pass
245
+
246
+ def test_privilege_escalation_prevented(self) -> None:
247
+ """User cannot access admin-only endpoints."""
248
+ pass
249
+
250
+ def test_session_fixation_prevented(self) -> None:
251
+ """Session fixation attack is prevented."""
252
+ pass
253
+ `
254
+ : '';
255
+ const uploadSection = upload
256
+ ? `
257
+ def test_disallowed_file_type_rejected(self) -> None:
258
+ """Disallowed file types are rejected on upload."""
259
+ pass
260
+
261
+ def test_max_file_size_enforced(self) -> None:
262
+ """Files exceeding size limit are rejected."""
263
+ pass
264
+
265
+ def test_path_traversal_in_filename_prevented(self) -> None:
266
+ """Path traversal in filename is sanitized."""
267
+ pass
268
+
269
+ def test_malicious_content_detected(self) -> None:
270
+ """Malicious file content is detected and rejected."""
271
+ pass
272
+ `
273
+ : '';
274
+ const apiSection = publicApi
275
+ ? `
276
+ def test_rate_limiter_blocks_excess(self) -> None:
277
+ """Rate limiter returns 429 after threshold."""
278
+ pass
279
+
280
+ def test_cors_rejects_unauthorized_origin(self) -> None:
281
+ """CORS policy rejects requests from unauthorized origins."""
282
+ pass
283
+
284
+ def test_api_key_rotation_supported(self) -> None:
285
+ """API key rotation works without downtime."""
286
+ pass
287
+ `
288
+ : '';
289
+ return `"""Security tests for: ${title}
290
+ Generated by SpecForge SDD MCP Server (SPEC-058b).
291
+ """
292
+ import pytest
293
+
294
+
295
+ class Test${cls}Security:
296
+ """Security test suite for ${title}."""
297
+ ${owaspSection}${authSection}${uploadSection}${apiSection}`;
298
+ }
299
+ export function generateSecurityTestFiles(spec, testDir, framework, language, testExt, autoGenerate, knowledge) {
300
+ const auth = detectAuth(knowledge);
301
+ const upload = detectFileUpload(knowledge);
302
+ const publicApi = hasPublicApi(knowledge);
303
+ const isPython = language === 'python';
304
+ const ext = isPython ? 'py' : testExt;
305
+ const fw = isPython ? 'pytest' : framework;
306
+ const content = isPython
307
+ ? buildPythonSecurityTest(spec.title, auth, upload, publicApi)
308
+ : buildTsSecurityTest(spec.title, auth, upload, publicApi);
309
+ return [
310
+ {
311
+ path: `${testDir}/security/${spec.slug}.security.test.${ext}`,
312
+ framework: fw,
313
+ content,
314
+ ready: autoGenerate,
315
+ },
316
+ ];
317
+ }
318
+ //# sourceMappingURL=security-test-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-test-generator.js","sourceRoot":"","sources":["../../../../src/tools/generate-tests/generators/security-test-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { ProjectKnowledge, TestDefinition, TestFile } from '../../../types/index.js';
2
+ /**
3
+ * Returns true when the project has at least one frontend/fullstack app with a UI framework.
4
+ */
5
+ export declare function isVisualRegressionProject(knowledge: ProjectKnowledge): boolean;
6
+ declare function hasDarkModeSupport(knowledge: ProjectKnowledge): boolean;
7
+ /**
8
+ * Generate visual regression test definitions.
9
+ */
10
+ export declare function generateVisualRegressionTestDefs(title: string, testDir: string, _testExt: string): TestDefinition[];
11
+ /**
12
+ * Generate visual regression test files for the given spec.
13
+ */
14
+ export declare function generateVisualRegressionTestFiles(spec: {
15
+ title: string;
16
+ slug: string;
17
+ }, testDir: string, framework: string, language: string, testExt: string, autoGenerate: boolean): TestFile[];
18
+ export { hasDarkModeSupport };
19
+ //# sourceMappingURL=visual-regression-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-regression-generator.d.ts","sourceRoot":"","sources":["../../../../src/tools/generate-tests/generators/visual-regression-generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAuC1F;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAiB9E;AAED,iBAAS,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAGhE;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,cAAc,EAAE,CA4ClB;AAsMD;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACrC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,OAAO,GACpB,QAAQ,EAAE,CAaZ;AAGD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,304 @@
1
+ // tools/generate-tests/generators/visual-regression-generator.ts — SPEC-058a Section C
2
+ // Generates visual regression and frontend screenshot test scaffolds.
3
+ const UI_FRAMEWORKS = [
4
+ 'react',
5
+ 'vue',
6
+ 'angular',
7
+ 'svelte',
8
+ 'next',
9
+ 'nextjs',
10
+ 'next.js',
11
+ 'nuxt',
12
+ 'sveltekit',
13
+ 'svelte-kit',
14
+ 'solid',
15
+ 'solidjs',
16
+ 'preact',
17
+ 'qwik',
18
+ 'remix',
19
+ 'gatsby',
20
+ 'astro',
21
+ ];
22
+ const DARK_MODE_SIGNALS = [
23
+ 'tailwind',
24
+ 'shadcn',
25
+ 'radix',
26
+ 'mui',
27
+ 'chakra',
28
+ 'mantine',
29
+ 'daisy',
30
+ 'daisyui',
31
+ 'styled-components',
32
+ 'emotion',
33
+ ];
34
+ const BP_MOBILE = { label: 'mobile', width: 375, height: 812 };
35
+ const BP_TABLET = { label: 'tablet', width: 768, height: 1024 };
36
+ const BP_DESKTOP = { label: 'desktop', width: 1280, height: 800 };
37
+ /**
38
+ * Returns true when the project has at least one frontend/fullstack app with a UI framework.
39
+ */
40
+ export function isVisualRegressionProject(knowledge) {
41
+ const frontendApps = knowledge.apps.filter((app) => app.type === 'frontend');
42
+ for (const app of frontendApps) {
43
+ const fw = (app.framework ?? '').toLowerCase();
44
+ if (UI_FRAMEWORKS.some((uif) => fw.includes(uif))) {
45
+ return true;
46
+ }
47
+ }
48
+ // Fall back to top-level framework when no frontend apps matched
49
+ if (frontendApps.length === 0) {
50
+ const topFw = (knowledge.framework ?? '').toLowerCase();
51
+ return UI_FRAMEWORKS.some((uif) => topFw.includes(uif));
52
+ }
53
+ return false;
54
+ }
55
+ function hasDarkModeSupport(knowledge) {
56
+ const stackLower = knowledge.stack.map((s) => s.toLowerCase());
57
+ return DARK_MODE_SIGNALS.some((sig) => stackLower.some((s) => s.includes(sig)));
58
+ }
59
+ /**
60
+ * Generate visual regression test definitions.
61
+ */
62
+ export function generateVisualRegressionTestDefs(title, testDir, _testExt) {
63
+ const defs = [
64
+ {
65
+ name: `${title} — Visual: screenshot baseline matches approved snapshot`,
66
+ type: 'e2e',
67
+ file: `${testDir}/visual/`,
68
+ description: 'Capture full-page screenshot and compare against approved baseline snapshot — fails on unexpected visual changes',
69
+ priority: 'critical',
70
+ automatable: true,
71
+ },
72
+ {
73
+ name: `${title} — Visual: responsive layout at 375px, 768px, 1280px`,
74
+ type: 'e2e',
75
+ file: `${testDir}/visual/`,
76
+ description: 'Verify layout integrity across mobile (375px), tablet (768px), and desktop (1280px) breakpoints',
77
+ priority: 'high',
78
+ automatable: true,
79
+ },
80
+ {
81
+ name: `${title} — Visual: component renders without layout shift`,
82
+ type: 'unit',
83
+ file: `${testDir}/visual/`,
84
+ description: 'Component snapshot test — verify no unintended style or markup changes in isolated render',
85
+ priority: 'medium',
86
+ automatable: true,
87
+ },
88
+ ];
89
+ // Dark mode def added unconditionally — caller controls knowledge, but we always include it
90
+ // since isVisualRegressionProject already filtered for UI projects that commonly support dark mode
91
+ defs.push({
92
+ name: `${title} — Visual: dark mode toggle does not break layout`,
93
+ type: 'e2e',
94
+ file: `${testDir}/visual/`,
95
+ description: 'Toggle dark/light mode and verify screenshot matches approved dark-mode snapshot without overflow or invisible text',
96
+ priority: 'medium',
97
+ automatable: true,
98
+ });
99
+ return defs;
100
+ }
101
+ // === Language-specific content generators ===
102
+ function generatePlaywrightTest(title, slug) {
103
+ return `// Visual regression tests for ${title}
104
+ // Requires: @playwright/test with --update-snapshots for initial baseline
105
+ import { test, expect } from '@playwright/test';
106
+
107
+ const BASE_URL = process.env.BASE_URL ?? 'http://localhost:3000';
108
+
109
+ test.describe('${title} — Visual Regression', () => {
110
+ test('full-page screenshot matches baseline', async ({ page }) => {
111
+ await page.goto(BASE_URL);
112
+ await page.waitForLoadState('networkidle');
113
+ await expect(page).toHaveScreenshot('${slug}-baseline.png', {
114
+ fullPage: true,
115
+ maxDiffPixelRatio: 0.02,
116
+ });
117
+ });
118
+
119
+ test('responsive — mobile 375px', async ({ page }) => {
120
+ await page.setViewportSize({ width: ${BP_MOBILE.width}, height: ${BP_MOBILE.height} });
121
+ await page.goto(BASE_URL);
122
+ await page.waitForLoadState('networkidle');
123
+ await expect(page).toHaveScreenshot('${slug}-mobile.png', {
124
+ maxDiffPixelRatio: 0.02,
125
+ });
126
+ });
127
+
128
+ test('responsive — tablet 768px', async ({ page }) => {
129
+ await page.setViewportSize({ width: ${BP_TABLET.width}, height: ${BP_TABLET.height} });
130
+ await page.goto(BASE_URL);
131
+ await page.waitForLoadState('networkidle');
132
+ await expect(page).toHaveScreenshot('${slug}-tablet.png', {
133
+ maxDiffPixelRatio: 0.02,
134
+ });
135
+ });
136
+
137
+ test('responsive — desktop 1280px', async ({ page }) => {
138
+ await page.setViewportSize({ width: ${BP_DESKTOP.width}, height: ${BP_DESKTOP.height} });
139
+ await page.goto(BASE_URL);
140
+ await page.waitForLoadState('networkidle');
141
+ await expect(page).toHaveScreenshot('${slug}-desktop.png', {
142
+ maxDiffPixelRatio: 0.02,
143
+ });
144
+ });
145
+
146
+ test('component renders without layout shift', async ({ page }) => {
147
+ await page.goto(\`\${BASE_URL}/components\`);
148
+ // TODO: navigate to your component preview/Storybook URL
149
+ await page.waitForLoadState('networkidle');
150
+ await expect(page).toHaveScreenshot('${slug}-component.png');
151
+ });
152
+
153
+ test('dark mode toggle does not break layout', async ({ page }) => {
154
+ await page.goto(BASE_URL);
155
+ await page.waitForLoadState('networkidle');
156
+ // TODO: adjust selector to match your dark mode toggle
157
+ await page.click('[data-testid="theme-toggle"], button[aria-label*="dark"], button[aria-label*="theme"]');
158
+ await page.waitForTimeout(300); // allow CSS transition
159
+ await expect(page).toHaveScreenshot('${slug}-dark-mode.png', {
160
+ maxDiffPixelRatio: 0.02,
161
+ });
162
+ });
163
+ });
164
+ `;
165
+ }
166
+ function generatePytestPlaywrightTest(title, slug) {
167
+ const pySlug = slug.replace(/-/g, '_');
168
+ return `"""Visual regression tests for ${title} — pytest-playwright."""
169
+ # Requires: pytest-playwright + pytest-playwright-snapshot
170
+ # Install: pip install pytest-playwright playwright-pytest-snapshot
171
+ # Run: pytest --update-snapshots (first time to create baseline)
172
+ import pytest
173
+ from playwright.sync_api import Page, expect
174
+
175
+
176
+ BASE_URL = "http://localhost:3000"
177
+
178
+
179
+ @pytest.fixture(scope="session")
180
+ def base_url() -> str:
181
+ import os
182
+ return os.environ.get("BASE_URL", BASE_URL)
183
+
184
+
185
+ def test_${pySlug}_full_page_screenshot(page: Page, base_url: str, assert_snapshot: object) -> None:
186
+ """Full-page screenshot matches approved baseline."""
187
+ page.goto(base_url)
188
+ page.wait_for_load_state("networkidle")
189
+ assert_snapshot(page.screenshot(full_page=True), name="${slug}-baseline.png")
190
+
191
+
192
+ def test_${pySlug}_responsive_mobile(page: Page, base_url: str, assert_snapshot: object) -> None:
193
+ """Layout intact at 375px (mobile)."""
194
+ page.set_viewport_size({"width": ${BP_MOBILE.width}, "height": ${BP_MOBILE.height}})
195
+ page.goto(base_url)
196
+ page.wait_for_load_state("networkidle")
197
+ assert_snapshot(page.screenshot(), name="${slug}-mobile.png")
198
+
199
+
200
+ def test_${pySlug}_responsive_tablet(page: Page, base_url: str, assert_snapshot: object) -> None:
201
+ """Layout intact at 768px (tablet)."""
202
+ page.set_viewport_size({"width": ${BP_TABLET.width}, "height": ${BP_TABLET.height}})
203
+ page.goto(base_url)
204
+ page.wait_for_load_state("networkidle")
205
+ assert_snapshot(page.screenshot(), name="${slug}-tablet.png")
206
+
207
+
208
+ def test_${pySlug}_responsive_desktop(page: Page, base_url: str, assert_snapshot: object) -> None:
209
+ """Layout intact at 1280px (desktop)."""
210
+ page.set_viewport_size({"width": ${BP_DESKTOP.width}, "height": ${BP_DESKTOP.height}})
211
+ page.goto(base_url)
212
+ page.wait_for_load_state("networkidle")
213
+ assert_snapshot(page.screenshot(), name="${slug}-desktop.png")
214
+
215
+
216
+ def test_${pySlug}_dark_mode(page: Page, base_url: str, assert_snapshot: object) -> None:
217
+ """Dark mode toggle does not break layout."""
218
+ page.goto(base_url)
219
+ page.wait_for_load_state("networkidle")
220
+ # TODO: adjust selector to match your dark mode toggle
221
+ page.click('[data-testid="theme-toggle"]')
222
+ page.wait_for_timeout(300)
223
+ assert_snapshot(page.screenshot(), name="${slug}-dark-mode.png")
224
+ `;
225
+ }
226
+ function generateCypressVisualTest(title, slug) {
227
+ return `// Visual regression tests for ${title} — Cypress + cypress-image-snapshot
228
+ // Install: npm install --save-dev cypress-image-snapshot
229
+ // Config: import 'cypress-image-snapshot/command' in cypress/support/commands.ts
230
+ describe('${title} — Visual Regression', () => {
231
+ const BASE_URL = Cypress.env('BASE_URL') ?? 'http://localhost:3000';
232
+
233
+ it('full-page screenshot matches baseline', () => {
234
+ cy.visit(BASE_URL);
235
+ cy.matchImageSnapshot('${slug}-baseline', { failureThreshold: 0.02 });
236
+ });
237
+
238
+ it('responsive — mobile 375px', () => {
239
+ cy.viewport(${BP_MOBILE.width}, ${BP_MOBILE.height});
240
+ cy.visit(BASE_URL);
241
+ cy.matchImageSnapshot('${slug}-mobile', { failureThreshold: 0.02 });
242
+ });
243
+
244
+ it('responsive — tablet 768px', () => {
245
+ cy.viewport(${BP_TABLET.width}, ${BP_TABLET.height});
246
+ cy.visit(BASE_URL);
247
+ cy.matchImageSnapshot('${slug}-tablet', { failureThreshold: 0.02 });
248
+ });
249
+
250
+ it('responsive — desktop 1280px', () => {
251
+ cy.viewport(${BP_DESKTOP.width}, ${BP_DESKTOP.height});
252
+ cy.visit(BASE_URL);
253
+ cy.matchImageSnapshot('${slug}-desktop', { failureThreshold: 0.02 });
254
+ });
255
+
256
+ it('dark mode toggle does not break layout', () => {
257
+ cy.visit(BASE_URL);
258
+ // TODO: adjust selector to match your dark mode toggle
259
+ cy.get('[data-testid="theme-toggle"]').click();
260
+ cy.wait(300); // allow CSS transition
261
+ cy.matchImageSnapshot('${slug}-dark-mode', { failureThreshold: 0.02 });
262
+ });
263
+ });
264
+ `;
265
+ }
266
+ function generateVisualTestContent(title, slug, framework, language) {
267
+ if (language === 'python') {
268
+ return generatePytestPlaywrightTest(title, slug);
269
+ }
270
+ if (framework === 'cypress') {
271
+ return generateCypressVisualTest(title, slug);
272
+ }
273
+ return generatePlaywrightTest(title, slug);
274
+ }
275
+ function getFileExtension(language) {
276
+ const exts = {
277
+ python: 'py',
278
+ go: 'go',
279
+ ruby: 'rb',
280
+ java: 'java',
281
+ javascript: 'test.ts',
282
+ typescript: 'test.ts',
283
+ };
284
+ return exts[language] ?? 'test.ts';
285
+ }
286
+ /**
287
+ * Generate visual regression test files for the given spec.
288
+ */
289
+ export function generateVisualRegressionTestFiles(spec, testDir, framework, language, testExt, autoGenerate) {
290
+ const ext = language === 'python' ? 'py' : getFileExtension(language);
291
+ const resolvedFramework = framework === 'cypress' ? 'cypress' : 'playwright';
292
+ const content = generateVisualTestContent(spec.title, spec.slug, framework, language);
293
+ return [
294
+ {
295
+ path: `${testDir}/visual/${spec.slug}.visual.${ext === 'test.ts' ? testExt : ext}`,
296
+ framework: resolvedFramework,
297
+ content,
298
+ ready: autoGenerate,
299
+ },
300
+ ];
301
+ }
302
+ // Re-export for dark mode detection utility (used in spec-dispatcher if needed)
303
+ export { hasDarkModeSupport };
304
+ //# sourceMappingURL=visual-regression-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-regression-generator.js","sourceRoot":"","sources":["../../../../src/tools/generate-tests/generators/visual-regression-generator.ts"],"names":[],"mappings":"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"}