@skyramp/mcp 0.2.8 → 0.2.10-rc.1

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 (88) hide show
  1. package/build/index.js +1 -8
  2. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
  3. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
  4. package/build/prompts/personas.d.ts +2 -2
  5. package/build/prompts/personas.js +2 -2
  6. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
  7. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +6 -1
  8. package/build/prompts/sut-setup/shared.d.ts +20 -0
  9. package/build/prompts/sut-setup/shared.js +67 -7
  10. package/build/prompts/test-maintenance/actionsInstructions.js +5 -1
  11. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +15 -2
  12. package/build/prompts/test-maintenance/drift-analysis-prompt.js +63 -3
  13. package/build/prompts/test-maintenance/driftAnalysisSections.js +7 -25
  14. package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +20 -0
  15. package/build/prompts/test-maintenance/driftAnalysisShared.js +80 -0
  16. package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +32 -0
  17. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +212 -0
  18. package/build/prompts/test-recommendation/recommendationShared.js +1 -1
  19. package/build/prompts/test-recommendation/scopeAssessment.js +12 -2
  20. package/build/prompts/testbot/testbot-prompts.js +10 -9
  21. package/build/resources/testbotResource.js +1 -1
  22. package/build/services/TestDiscoveryService.d.ts +12 -10
  23. package/build/services/TestDiscoveryService.js +137 -83
  24. package/build/services/TestDiscoveryService.test.js +370 -15
  25. package/build/services/TestExecutionService.d.ts +1 -1
  26. package/build/services/TestExecutionService.js +7 -7
  27. package/build/services/TestExecutionService.test.js +4 -1
  28. package/build/tools/executeSkyrampTestTool.js +40 -71
  29. package/build/tools/submitReportTool.d.ts +25 -25
  30. package/build/tools/submitReportTool.js +123 -34
  31. package/build/tools/submitReportTool.test.js +413 -94
  32. package/build/tools/test-management/actionsTool.js +177 -77
  33. package/build/tools/test-management/actionsTool.test.d.ts +1 -0
  34. package/build/tools/test-management/actionsTool.test.js +297 -0
  35. package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
  36. package/build/tools/test-management/analyzeChangesTool.js +74 -169
  37. package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
  38. package/build/tools/test-management/analyzeTestHealthTool.js +63 -22
  39. package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +1 -0
  40. package/build/tools/test-management/analyzeTestHealthTool.test.js +268 -0
  41. package/build/tools/workspace/initializeWorkspaceTool.js +37 -24
  42. package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
  43. package/build/types/FrontendIntegration.d.ts +25 -0
  44. package/build/types/FrontendIntegration.js +19 -0
  45. package/build/types/RepositoryAnalysis.d.ts +8 -8
  46. package/build/types/TestAnalysis.d.ts +18 -49
  47. package/build/types/TestAnalysis.js +0 -28
  48. package/build/types/TestTypes.d.ts +3 -1
  49. package/build/types/TestTypes.js +3 -1
  50. package/build/types/TestbotReport.d.ts +1 -1
  51. package/build/utils/AnalysisStateManager.d.ts +16 -22
  52. package/build/utils/docker.test.js +1 -1
  53. package/build/utils/featureFlags.d.ts +1 -1
  54. package/build/utils/featureFlags.js +1 -1
  55. package/build/utils/frontendIntegration.d.ts +9 -0
  56. package/build/utils/frontendIntegration.js +237 -0
  57. package/build/utils/frontendIntegration.test.d.ts +1 -0
  58. package/build/utils/frontendIntegration.test.js +229 -0
  59. package/build/utils/pr-comment-parser.d.ts +3 -3
  60. package/build/utils/pr-comment-parser.js +6 -6
  61. package/build/utils/pr-comment-parser.test.js +3 -3
  62. package/build/utils/repoScanner.d.ts +7 -0
  63. package/build/utils/repoScanner.js +14 -6
  64. package/build/utils/repoScanner.test.js +9 -9
  65. package/build/utils/routeParsers.d.ts +32 -0
  66. package/build/utils/routeParsers.js +205 -2
  67. package/build/utils/routeParsers.test.js +279 -29
  68. package/build/utils/sourceRouteExtractor.js +174 -5
  69. package/build/utils/sourceRouteExtractor.test.js +173 -0
  70. package/build/utils/utils.d.ts +11 -0
  71. package/build/utils/utils.js +19 -0
  72. package/build/utils/utils.test.js +23 -1
  73. package/build/utils/versions.d.ts +3 -3
  74. package/build/utils/versions.js +1 -1
  75. package/build/workspace/workspace.d.ts +21 -21
  76. package/build/workspace/workspace.js +7 -4
  77. package/build/workspace/workspace.test.js +65 -6
  78. package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
  79. package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
  80. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
  81. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
  82. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
  83. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
  84. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
  85. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  86. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  87. package/node_modules/playwright/package.json +1 -1
  88. package/package.json +3 -3
@@ -2,6 +2,7 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { TestSource } from "../types/TestAnalysis.js";
5
+ import { TestType } from "../types/TestTypes.js";
5
6
  import fg from "fast-glob";
6
7
  export class TestDiscoveryService {
7
8
  EXCLUDED_DIRS = [
@@ -23,7 +24,8 @@ export class TestDiscoveryService {
23
24
  SUPPORTED_EXTENSIONS = [
24
25
  ".py", ".js", ".ts", ".tsx", ".jsx", ".java",
25
26
  ".go", ".rb", ".kt", ".kts", ".cs", ".rs",
26
- ".php", ".scala", ".swift", ".m",
27
+ ".php", ".scala", ".swift", ".m", ".dart",
28
+ ".snap", // Jest/Vitest snapshot files — may need updating when UI changes break them
27
29
  ];
28
30
  // Concurrency control for parallel operations
29
31
  MAX_CONCURRENT_OPERATIONS = 10;
@@ -47,11 +49,20 @@ export class TestDiscoveryService {
47
49
  // Skyramp-generated test suffixes — must be matched even outside test dirs
48
50
  /.*_(?:smoke|contract|fuzz|integration|load|e2e|ui)\.(py|ts|js|java|rb|go|cs|kt|kts|scala|swift|php|rs)$/i,
49
51
  ];
50
- // Directory patterns that signal test directories — works with both / and \ separators
52
+ // Directory patterns that signal test-owned files — works with both / and \ separators.
53
+ // Includes standard test dirs, snapshot dirs, and common helper/fixture dirs used by
54
+ // Playwright, Cypress, and other frameworks for page objects and support code.
51
55
  TEST_DIR_PATTERNS = [
52
56
  /[\\/]tests?[\\/]/,
53
57
  /[\\/]__tests__[\\/]/,
58
+ /[\\/]__snapshots__[\\/]/, // Jest/Vitest snapshot directories
54
59
  /[\\/]spec[\\/]/,
60
+ /[\\/]e2e[\\/]/,
61
+ /[\\/]cypress[\\/]/, // Cypress support, fixtures, plugins
62
+ /[\\/]playwright[\\/]/, // Playwright fixtures and helpers
63
+ /[\\/]page-?objects?[\\/]/, // page-objects/, page-object/, pageObjects/
64
+ /[\\/]fixtures[\\/](?!db[\\/]|seed[\\/]|migrations[\\/])/, // test fixture files (not db/seed/migration fixtures)
65
+ /[\\/]cypress[\\/]support[\\/]/, // Cypress support directory (scoped to avoid matching app support/ dirs)
55
66
  ];
56
67
  /**
57
68
  * Discover all tests under testDir — both Skyramp-generated and external (user-written).
@@ -93,48 +104,57 @@ export class TestDiscoveryService {
93
104
  // Full-repo mode (changedResources is undefined):
94
105
  // No diff context — all external files treated as potentially relevant.
95
106
  // Cap at MAX_EXTERNAL_FULL_REPO to avoid reading hundreds of files.
96
- const { changedResources } = options;
107
+ const { changedResources, hasFrontendChanges } = options;
97
108
  let relevantExternal;
98
- let otherExternal;
109
+ // Includes .e2e.ts — Playwright/Cypress tests that navigate pages. These get
110
+ // testType="e2e" from path detection but are never named *.test.ts or *.spec.ts
111
+ // so without explicit promotion they'd be excluded from stateData.existingTests,
112
+ // making the server-side symbol grep unable to find them.
113
+ const UI_TEST_EXT = /\.(test|spec|e2e)\.(tsx|jsx|ts|js)$/;
114
+ const SEC_EXT = /\.(snap|ambr|txt|yml|yaml)$/;
115
+ // Step 1 — API relevance partitioning (driven by changedResources).
116
+ // Two-pass: filename token overlap first, then content-based endpoint path scan
117
+ // for tests that are misnamed relative to the endpoint they exercise.
99
118
  if (changedResources?.length) {
100
- // PR mode with detected endpoints — partition by relevance
101
- ({ relevant: relevantExternal, other: otherExternal } =
102
- this.partitionByRelevance(classified.external, changedResources));
119
+ ({ relevant: relevantExternal } =
120
+ this.partitionByRelevance(classified.external, changedResources, classified.contentCache));
103
121
  }
104
122
  else if (changedResources !== undefined) {
105
- // PR mode with no path-resolvable endpoints — no path-based scoring available.
106
123
  relevantExternal = [];
107
- otherExternal = [];
108
124
  }
109
125
  else {
110
- // Full-repo mode (no diff context): cap full-extraction set, remaining become name-only
111
126
  relevantExternal = classified.external.slice(0, this.MAX_EXTERNAL_FULL_REPO);
112
- otherExternal = classified.external.slice(this.MAX_EXTERNAL_FULL_REPO);
113
127
  }
114
- logger.info(`External tests: ${relevantExternal.length} relevant (full extraction), ${otherExternal.length} low-relevance (name-only)`);
115
- // Free cached content for files we will not extract — their content is not needed.
116
- for (const f of otherExternal) {
117
- classified.contentCache.delete(f);
128
+ // Step 2 UI test promotion (driven by hasFrontendChanges, independent of Step 1).
129
+ if (hasFrontendChanges) {
130
+ const alreadyRelevant = new Set(relevantExternal);
131
+ const promoted = classified.external.filter(f => UI_TEST_EXT.test(f) && !alreadyRelevant.has(f));
132
+ const promotedSet = new Set(promoted);
133
+ // Promote snapshot/fixture siblings of scored AND newly promoted UI tests.
134
+ // Include promoted files so siblings are found on UI-only PRs where
135
+ // relevantExternal started empty before Step 2.
136
+ const relevantTestBasenames = new Set([...relevantExternal, ...promoted].filter(f => UI_TEST_EXT.test(f)).map(f => path.basename(f)));
137
+ const promotedSecondary = classified.external.filter(f => !alreadyRelevant.has(f) &&
138
+ !promotedSet.has(f) &&
139
+ relevantTestBasenames.has(path.basename(f).replace(SEC_EXT, '')));
140
+ const allPromoted = [...promoted, ...promotedSecondary];
141
+ if (allPromoted.length > 0) {
142
+ logger.info(`Promoting ${promoted.length} UI test + ${promotedSecondary.length} secondary file(s) for content reading`);
143
+ relevantExternal = [...relevantExternal, ...allPromoted];
144
+ }
145
+ }
146
+ // Free content cache for external files not selected for full extraction.
147
+ const relevantSet = new Set(relevantExternal);
148
+ for (const f of classified.external) {
149
+ if (!relevantSet.has(f))
150
+ classified.contentCache.delete(f);
118
151
  }
119
- // Full metadata + endpoint extraction only for relevant external files
152
+ logger.info(`External tests: ${relevantExternal.length} relevant (full extraction)`);
120
153
  const relevantExternalTests = await this.processFilesInBatches(relevantExternal, true, classified.contentCache);
121
- // Minimal entries for low-relevance files — no content reads, no endpoint extraction
122
- const otherExternalTests = otherExternal.map(filePath => ({
123
- testFile: filePath,
124
- testType: this.detectExternalTestType(filePath, ""),
125
- language: this.detectLanguage(filePath),
126
- framework: "",
127
- apiEndpoint: "",
128
- source: TestSource.External,
129
- }));
130
- const externalTests = [...relevantExternalTests, ...otherExternalTests];
154
+ const externalTests = relevantExternalTests;
131
155
  logger.info(`Discovered ${skyrampTests.length} Skyramp tests, ${externalTests.length} external tests`);
132
156
  return {
133
157
  tests: [...skyrampTests, ...externalTests],
134
- // Expose the relevant file paths so callers can build read instructions for the LLM.
135
- // These are the files the agent should read in Step 0 for definitive coverage
136
- // verification — includes files where regex extraction may have failed (e.g. helper
137
- // abstractions) since those most need semantic inspection.
138
158
  relevantExternalTestPaths: relevantExternal,
139
159
  };
140
160
  }
@@ -168,22 +188,46 @@ export class TestDiscoveryService {
168
188
  return parts.length > 1 && parts.every(p => p.length >= 3 && expanded.has(p));
169
189
  }).length;
170
190
  }
191
+ // Max additional tests promoted via content-based scoring on top of filename matches.
192
+ // Content matches are lower-confidence than filename matches — cap to avoid token bloat.
193
+ MAX_CONTENT_PROMOTED = 5;
171
194
  /**
172
195
  * Partition external test files into relevant (score > 0) and low-relevance (score = 0).
173
- * Signal: path/name token overlap with changedResources.
196
+ * Two-pass: filename token overlap first (primary); content-based endpoint path scan
197
+ * as a capped fallback for tests misnamed relative to the endpoint they exercise
198
+ * (e.g. test_checkout_flow.py testing /api/orders scores 0 by name but matches in content).
199
+ * Content matches are capped at MAX_CONTENT_PROMOTED to bound token impact.
174
200
  */
175
- partitionByRelevance(files, changedResources) {
176
- const relevant = [];
177
- const other = [];
201
+ partitionByRelevance(files, changedResources, contentCache) {
202
+ const nameMatched = [];
203
+ const contentCandidates = [];
204
+ // Precompile content patterns once — avoids 20 resources × 150 files = 3K compilations.
205
+ const contentPatterns = contentCache
206
+ ? changedResources.map(r => {
207
+ // Escape all regex metacharacters, then restore [-/] as a character class
208
+ // for hyphens and slashes that appear in resource names like "order-items".
209
+ const escaped = r.toLowerCase()
210
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // escape regex metacharacters
211
+ .replace(/-|\//g, "[-/]"); // treat hyphens/slashes interchangeably
212
+ return new RegExp(`["'\`][^"'\`]*/${escaped}`, "i");
213
+ })
214
+ : [];
178
215
  for (const f of files) {
179
216
  if (this.scoreRelevance(f, changedResources) > 0) {
180
- relevant.push(f);
217
+ nameMatched.push(f);
181
218
  }
182
- else {
183
- other.push(f);
219
+ else if (contentPatterns.length > 0) {
220
+ const content = contentCache.get(f) ?? "";
221
+ if (content && contentPatterns.some(p => p.test(content))) {
222
+ contentCandidates.push(f);
223
+ }
184
224
  }
185
225
  }
186
- return { relevant, other };
226
+ const contentMatched = contentCandidates.slice(0, this.MAX_CONTENT_PROMOTED);
227
+ if (contentMatched.length > 0) {
228
+ logger.info(`Content-based relevance: promoting ${contentMatched.length} test(s) (capped at ${this.MAX_CONTENT_PROMOTED})`);
229
+ }
230
+ return { relevant: [...nameMatched, ...contentMatched] };
187
231
  }
188
232
  /**
189
233
  * Process test files in parallel batches with concurrency control
@@ -286,7 +330,6 @@ export class TestDiscoveryService {
286
330
  }
287
331
  const language = this.detectLanguage(testFile);
288
332
  const testType = this.detectTestType(content);
289
- const apiSchema = this.extractApiSchema(content);
290
333
  const framework = this.extractFramework(content);
291
334
  const apiEndpoint = this.extractCoveredEndpoints(content);
292
335
  return {
@@ -294,8 +337,8 @@ export class TestDiscoveryService {
294
337
  testType,
295
338
  language,
296
339
  framework,
297
- apiSchema,
298
340
  apiEndpoint,
341
+ source: TestSource.Skyramp,
299
342
  };
300
343
  }
301
344
  /**
@@ -455,46 +498,72 @@ export class TestDiscoveryService {
455
498
  detectExternalTestType(filePath, content) {
456
499
  const lowerPath = filePath.toLowerCase();
457
500
  if (/integration|_integration/.test(lowerPath))
458
- return "integration";
501
+ return TestType.INTEGRATION;
459
502
  if (/e2e|end[_-]to[_-]end/.test(lowerPath))
460
- return "e2e";
503
+ return TestType.E2E;
504
+ // Playwright/Cypress directory paths → e2e even without "e2e" in the filename.
505
+ // Ensures correct testType for tests where content wasn't read (framework stays "").
506
+ if (/[\\/]playwright[\\/]|[\\/]cypress[\\/]/.test(lowerPath))
507
+ return TestType.E2E;
461
508
  if (/contract/.test(lowerPath))
462
- return "contract";
509
+ return TestType.CONTRACT;
463
510
  if (/smoke/.test(lowerPath))
464
- return "smoke";
511
+ return TestType.SMOKE;
465
512
  if (/load|performance/.test(lowerPath))
466
- return "load";
513
+ return TestType.LOAD;
467
514
  if (/[\\/]unit[\\/]|_unit/.test(lowerPath))
468
- return "unit";
469
- // Heuristic: files making HTTP calls are likely integration/API tests
515
+ return TestType.UNIT;
470
516
  if (/(?:requests|fetch|axios|supertest|RestAssured)/i.test(content))
471
- return "integration";
472
- return "unknown";
517
+ return TestType.INTEGRATION;
518
+ return TestType.UNKNOWN;
473
519
  }
474
- /**
475
- * Detect test framework from external test file content by checking imports.
476
- */
477
520
  detectExternalFramework(content) {
478
- // Python
521
+ if (/from selenium\b|import selenium\b|from appium\b|import appium\b/.test(content))
522
+ return "selenium";
523
+ // pytest-playwright: check before the generic pytest return.
524
+ if (/from playwright\.(?:sync|async)_api\b|import playwright\./.test(content)) {
525
+ const usesPage = /\bpage\.(?:goto|click|locator|get_by|fill|type|press|wait_for)\b/.test(content);
526
+ const usesRequestOnly = !usesPage && /\brequest\.(?:get|post|put|patch|delete)\b/.test(content);
527
+ return usesRequestOnly ? "playwright" : "playwright-ui";
528
+ }
479
529
  if (/import pytest|from pytest/.test(content))
480
530
  return "pytest";
481
531
  if (/import unittest|from unittest/.test(content))
482
532
  return "unittest";
483
- // JavaScript/TypeScript
484
533
  if (/from ['"]vitest['"]|import.*vitest|require\(['"]vitest['"]\)|\bvi\./.test(content))
485
534
  return "vitest";
486
- if (/from ['"]@jest\/globals['"]|from ['"]jest['"]|import.*jest|require\(['"]jest['"]\)|jest\./.test(content) ||
487
- (/\b(?:test|it)\s*\(/.test(content) && /\bexpect\s*\(/.test(content))) {
488
- return "jest";
489
- }
535
+ // Component/UI test frameworks — checked BEFORE the broad jest heuristic below,
536
+ // because RTL and similar frameworks use it()/expect() which would otherwise
537
+ // match the jest fallback and hide the more specific framework signal.
538
+ if (/from ['"]@testing-library\/react['"]|from ['"]@testing-library\/react-native['"]/.test(content))
539
+ return "rtl";
540
+ if (/from ['"]@testing-library\/vue['"]|from ['"]@vue\/test-utils['"]/.test(content))
541
+ return "vue-test-utils";
542
+ if (/from ['"]@testing-library\/angular['"]/.test(content))
543
+ return "angular-testbed";
544
+ if (/from ['"]@testing-library\/svelte['"]/.test(content))
545
+ return "svelte-testing-library";
546
+ if (/from ['"]enzyme['"]|require\(['"]enzyme['"]\)/.test(content))
547
+ return "enzyme";
490
548
  if (/from ['"]mocha['"]|require\(['"]mocha['"]\)/.test(content))
491
549
  return "mocha";
492
550
  if (/supertest/.test(content))
493
551
  return "supertest";
494
- if (/from ['"]@playwright/.test(content))
495
- return "playwright";
552
+ if (/from ['"]@playwright/.test(content)) {
553
+ // Distinguish browser UI usage (page fixture) from REST API usage (request fixture only).
554
+ // Using Playwright for API testing is an edge case — the default classification is UI.
555
+ const usesPage = /\bpage\.(goto|click|locator|getBy|fill|type|press|waitFor)\b/.test(content);
556
+ const usesRequestOnly = !usesPage && /\brequest\.(get|post|put|patch|delete)\b/.test(content);
557
+ return usesRequestOnly ? "playwright" : "playwright-ui";
558
+ }
496
559
  if (/from ['"]cypress/.test(content))
497
- return "cypress";
560
+ return "cypress-ui";
561
+ if (/package:flutter_test\/flutter_test\.dart/.test(content))
562
+ return "flutter-widget";
563
+ if (/from ['"]@jest\/globals['"]|from ['"]jest['"]|import.*jest|require\(['"]jest['"]\)|jest\./.test(content) ||
564
+ (/\b(?:test|it)\s*\(/.test(content) && /\bexpect\s*\(/.test(content))) {
565
+ return "jest";
566
+ }
498
567
  // Java / Kotlin — check TestNG before @Test since TestNG also uses @Test
499
568
  if (/import org\.testng/.test(content))
500
569
  return "testng";
@@ -502,6 +571,10 @@ export class TestDiscoveryService {
502
571
  return "junit";
503
572
  if (/RestAssured/.test(content))
504
573
  return "rest-assured";
574
+ if (/import androidx\.test\b|import com\.android\.support\.test\b|EspressoIdlingResource|onView\(/.test(content))
575
+ return "espresso";
576
+ if (/import io\.appium\./.test(content))
577
+ return "appium";
505
578
  // Go
506
579
  if (/import.*"testing"|func\s+Test\w+\(t\s+\*testing\.T\)/.test(content))
507
580
  return "go-testing";
@@ -555,6 +628,7 @@ export class TestDiscoveryService {
555
628
  ".scala": "scala",
556
629
  ".swift": "swift",
557
630
  ".m": "objective-c",
631
+ ".dart": "dart",
558
632
  };
559
633
  return languageMap[ext] || "unknown";
560
634
  }
@@ -563,39 +637,19 @@ export class TestDiscoveryService {
563
637
  * Checks Skyramp command line, filename, and content patterns
564
638
  */
565
639
  detectTestType(content) {
566
- const testTypes = [
567
- "smoke",
568
- "integration",
569
- "load",
570
- "contract",
571
- "fuzz",
572
- "e2e",
573
- "ui",
574
- ];
575
- // First, try to extract from Skyramp command line
576
- // Pattern: skyramp generate smoke rest ...
577
640
  const commandMatch = content.match(/skyramp generate (\w+)/i);
578
641
  if (commandMatch && commandMatch[1]) {
579
642
  const type = commandMatch[1].toLowerCase();
580
- if (testTypes.includes(type)) {
643
+ if (Object.values(TestType).includes(type)) {
581
644
  return type;
582
645
  }
583
646
  }
584
- return "";
647
+ return TestType.UNKNOWN;
585
648
  }
586
649
  /**
587
650
  * Extract API schema path from test content
588
651
  * Looks for Skyramp command line first, then fallback to other patterns
589
652
  */
590
- extractApiSchema(content) {
591
- // First, try to extract from Skyramp command line
592
- // Pattern: --api-schema http://localhost:8000/openapi.json
593
- const apiSchemaMatch = content.match(/--api-schema\s+([^\s\\]+)/);
594
- if (apiSchemaMatch && apiSchemaMatch[1]) {
595
- return apiSchemaMatch[1];
596
- }
597
- return "";
598
- }
599
653
  /**
600
654
  * Extract Framework from test content
601
655
  * Looks for Skyramp command line first, then fallback to other patterns