@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.
- package/build/index.js +1 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
- package/build/prompts/personas.d.ts +2 -2
- package/build/prompts/personas.js +2 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +6 -1
- package/build/prompts/sut-setup/shared.d.ts +20 -0
- package/build/prompts/sut-setup/shared.js +67 -7
- package/build/prompts/test-maintenance/actionsInstructions.js +5 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +15 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +63 -3
- package/build/prompts/test-maintenance/driftAnalysisSections.js +7 -25
- package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +20 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +80 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +32 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +212 -0
- package/build/prompts/test-recommendation/recommendationShared.js +1 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +12 -2
- package/build/prompts/testbot/testbot-prompts.js +10 -9
- package/build/resources/testbotResource.js +1 -1
- package/build/services/TestDiscoveryService.d.ts +12 -10
- package/build/services/TestDiscoveryService.js +137 -83
- package/build/services/TestDiscoveryService.test.js +370 -15
- package/build/services/TestExecutionService.d.ts +1 -1
- package/build/services/TestExecutionService.js +7 -7
- package/build/services/TestExecutionService.test.js +4 -1
- package/build/tools/executeSkyrampTestTool.js +40 -71
- package/build/tools/submitReportTool.d.ts +25 -25
- package/build/tools/submitReportTool.js +123 -34
- package/build/tools/submitReportTool.test.js +413 -94
- package/build/tools/test-management/actionsTool.js +177 -77
- package/build/tools/test-management/actionsTool.test.d.ts +1 -0
- package/build/tools/test-management/actionsTool.test.js +297 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
- package/build/tools/test-management/analyzeChangesTool.js +74 -169
- package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
- package/build/tools/test-management/analyzeTestHealthTool.js +63 -22
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeTestHealthTool.test.js +268 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +37 -24
- package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
- package/build/types/FrontendIntegration.d.ts +25 -0
- package/build/types/FrontendIntegration.js +19 -0
- package/build/types/RepositoryAnalysis.d.ts +8 -8
- package/build/types/TestAnalysis.d.ts +18 -49
- package/build/types/TestAnalysis.js +0 -28
- package/build/types/TestTypes.d.ts +3 -1
- package/build/types/TestTypes.js +3 -1
- package/build/types/TestbotReport.d.ts +1 -1
- package/build/utils/AnalysisStateManager.d.ts +16 -22
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.d.ts +1 -1
- package/build/utils/featureFlags.js +1 -1
- package/build/utils/frontendIntegration.d.ts +9 -0
- package/build/utils/frontendIntegration.js +237 -0
- package/build/utils/frontendIntegration.test.d.ts +1 -0
- package/build/utils/frontendIntegration.test.js +229 -0
- package/build/utils/pr-comment-parser.d.ts +3 -3
- package/build/utils/pr-comment-parser.js +6 -6
- package/build/utils/pr-comment-parser.test.js +3 -3
- package/build/utils/repoScanner.d.ts +7 -0
- package/build/utils/repoScanner.js +14 -6
- package/build/utils/repoScanner.test.js +9 -9
- package/build/utils/routeParsers.d.ts +32 -0
- package/build/utils/routeParsers.js +205 -2
- package/build/utils/routeParsers.test.js +279 -29
- package/build/utils/sourceRouteExtractor.js +174 -5
- package/build/utils/sourceRouteExtractor.test.js +173 -0
- package/build/utils/utils.d.ts +11 -0
- package/build/utils/utils.js +19 -0
- package/build/utils/utils.test.js +23 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +21 -21
- package/build/workspace/workspace.js +7 -4
- package/build/workspace/workspace.test.js +65 -6
- package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
- package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- 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
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
classified.
|
|
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
|
-
|
|
152
|
+
logger.info(`External tests: ${relevantExternal.length} relevant (full extraction)`);
|
|
120
153
|
const relevantExternalTests = await this.processFilesInBatches(relevantExternal, true, classified.contentCache);
|
|
121
|
-
|
|
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
|
-
*
|
|
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
|
|
177
|
-
const
|
|
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
|
-
|
|
217
|
+
nameMatched.push(f);
|
|
181
218
|
}
|
|
182
|
-
else {
|
|
183
|
-
|
|
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
|
-
|
|
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
|
|
501
|
+
return TestType.INTEGRATION;
|
|
459
502
|
if (/e2e|end[_-]to[_-]end/.test(lowerPath))
|
|
460
|
-
return
|
|
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
|
|
509
|
+
return TestType.CONTRACT;
|
|
463
510
|
if (/smoke/.test(lowerPath))
|
|
464
|
-
return
|
|
511
|
+
return TestType.SMOKE;
|
|
465
512
|
if (/load|performance/.test(lowerPath))
|
|
466
|
-
return
|
|
513
|
+
return TestType.LOAD;
|
|
467
514
|
if (/[\\/]unit[\\/]|_unit/.test(lowerPath))
|
|
468
|
-
return
|
|
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
|
|
472
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|