chrometools-mcp 3.3.6 → 3.3.9
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/CHANGELOG.md +87 -0
- package/README.md +129 -24
- package/SPEC-pom-integration.md +227 -0
- package/SPEC-swagger-api-tools.md +3101 -0
- package/browser/page-manager.js +83 -0
- package/index.js +623 -201
- package/package.json +2 -1
- package/pom/apom-tree-converter.js +287 -51
- package/recorder/page-object-generator.js +45 -1
- package/server/tool-definitions.js +57 -6
- package/server/tool-schemas.js +31 -0
- package/test-swagger-phase1.mjs +959 -0
- package/utils/api-generators/api-models-python.js +448 -0
- package/utils/api-generators/api-models-typescript.js +375 -0
- package/utils/code-generators/code-generator-base.js +111 -6
- package/utils/code-generators/playwright-python.js +74 -0
- package/utils/code-generators/playwright-typescript.js +69 -0
- package/utils/code-generators/pom-integrator.js +373 -0
- package/utils/code-generators/selenium-java.js +72 -0
- package/utils/code-generators/selenium-python.js +75 -0
- package/utils/hints-generator.js +114 -19
- package/utils/openapi/helpers.js +25 -0
- package/utils/openapi/parser.js +448 -0
- package/utils/openapi/ref-resolver.js +149 -0
- package/utils/openapi/type-mapper.js +174 -0
- package/nul +0 -0
package/index.js
CHANGED
|
@@ -72,6 +72,12 @@ import {PlaywrightPythonGenerator} from './utils/code-generators/playwright-pyth
|
|
|
72
72
|
import {SeleniumPythonGenerator} from './utils/code-generators/selenium-python.js';
|
|
73
73
|
import {SeleniumJavaGenerator} from './utils/code-generators/selenium-java.js';
|
|
74
74
|
import {FileAppender} from './utils/code-generators/file-appender.js';
|
|
75
|
+
import {parsePomFile} from './utils/code-generators/pom-integrator.js';
|
|
76
|
+
|
|
77
|
+
// Import OpenAPI / Swagger tools
|
|
78
|
+
import {OpenAPIParser} from './utils/openapi/parser.js';
|
|
79
|
+
import {ApiModelsTypeScriptGenerator} from './utils/api-generators/api-models-typescript.js';
|
|
80
|
+
import {ApiModelsPythonGenerator} from './utils/api-generators/api-models-python.js';
|
|
75
81
|
// Import Figma tools
|
|
76
82
|
import {
|
|
77
83
|
collectAllText,
|
|
@@ -343,6 +349,9 @@ async function executeToolInternal(name, args) {
|
|
|
343
349
|
|
|
344
350
|
let hintsText = '\n\n** AI HINTS **';
|
|
345
351
|
hintsText += `\nPage type: ${hints.pageType}`;
|
|
352
|
+
if (hints.heading) {
|
|
353
|
+
hintsText += `\nPage heading: "${hints.heading}"`;
|
|
354
|
+
}
|
|
346
355
|
if (hints.availableActions.length > 0) {
|
|
347
356
|
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
348
357
|
}
|
|
@@ -521,10 +530,32 @@ async function executeToolInternal(name, args) {
|
|
|
521
530
|
|
|
522
531
|
// 3. Format output with hints and diagnostics
|
|
523
532
|
let hintsText = '\n\n** AI HINTS **';
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
533
|
+
|
|
534
|
+
// Modal: show title, body text, and actions
|
|
535
|
+
if (hints.modalOpened && hints.newElements.some(e => e.type === 'modal')) {
|
|
536
|
+
const modal = hints.newElements.find(e => e.type === 'modal');
|
|
537
|
+
let modalText = 'Modal opened';
|
|
538
|
+
if (modal.title) modalText += `: "${modal.title}"`;
|
|
539
|
+
if (modal.text) modalText += `\n ${modal.text}`;
|
|
540
|
+
if (modal.actions?.length) modalText += `\n Actions: [${modal.actions.join('] [')}]`;
|
|
541
|
+
hintsText += '\n' + modalText;
|
|
542
|
+
} else if (hints.modalOpened) {
|
|
543
|
+
hintsText += '\nModal opened - interact with it or close';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Overlay: show items
|
|
547
|
+
const overlay = hints.newElements.find(e => e.type === 'dropdown' || e.type === 'menu');
|
|
548
|
+
if (overlay?.items?.length) {
|
|
549
|
+
const label = overlay.type === 'menu' ? 'Menu' : 'Dropdown';
|
|
550
|
+
hintsText += `\n${label} with ${overlay.totalCount} options: ${overlay.items.join(', ')}`;
|
|
527
551
|
}
|
|
552
|
+
|
|
553
|
+
// Other new elements (alerts, etc.)
|
|
554
|
+
const otherElements = hints.newElements.filter(e => e.type !== 'modal' && e.type !== 'dropdown' && e.type !== 'menu');
|
|
555
|
+
if (otherElements.length > 0) {
|
|
556
|
+
hintsText += `\nNew elements appeared: ${otherElements.map(e => e.text ? `${e.type}: ${e.text}` : e.type).join(', ')}`;
|
|
557
|
+
}
|
|
558
|
+
|
|
528
559
|
if (hints.suggestedNext.length > 0) {
|
|
529
560
|
hintsText += `\nSuggested next: ${hints.suggestedNext.join('; ')}`;
|
|
530
561
|
}
|
|
@@ -1245,6 +1276,7 @@ async function executeToolInternal(name, args) {
|
|
|
1245
1276
|
|
|
1246
1277
|
const distance = validatedArgs.distance || 100;
|
|
1247
1278
|
const duration = validatedArgs.duration || 500;
|
|
1279
|
+
const mode = validatedArgs.mode || 'native';
|
|
1248
1280
|
|
|
1249
1281
|
// Calculate drag deltas based on direction
|
|
1250
1282
|
let deltaX = 0;
|
|
@@ -1281,62 +1313,166 @@ async function executeToolInternal(name, args) {
|
|
|
1281
1313
|
break;
|
|
1282
1314
|
}
|
|
1283
1315
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1316
|
+
if (mode === 'synthetic') {
|
|
1317
|
+
// Synthetic mode: dispatch DOM events for better JS library compatibility
|
|
1318
|
+
const result = await page.evaluate((selector, deltaX, deltaY, duration) => {
|
|
1319
|
+
const element = document.querySelector(selector);
|
|
1320
|
+
if (!element) {
|
|
1321
|
+
return { success: false, error: `Element not found: ${selector}` };
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const rect = element.getBoundingClientRect();
|
|
1325
|
+
const startX = rect.left + rect.width / 2;
|
|
1326
|
+
const startY = rect.top + rect.height / 2;
|
|
1327
|
+
const endX = startX + deltaX;
|
|
1328
|
+
const endY = startY + deltaY;
|
|
1329
|
+
|
|
1330
|
+
// Helper to create mouse/pointer event
|
|
1331
|
+
const createEvent = (type, clientX, clientY, buttons = 0) => {
|
|
1332
|
+
// Try PointerEvent first (modern browsers)
|
|
1333
|
+
if (typeof PointerEvent !== 'undefined') {
|
|
1334
|
+
return new PointerEvent(type, {
|
|
1335
|
+
bubbles: true,
|
|
1336
|
+
cancelable: true,
|
|
1337
|
+
view: window,
|
|
1338
|
+
clientX,
|
|
1339
|
+
clientY,
|
|
1340
|
+
screenX: clientX,
|
|
1341
|
+
screenY: clientY,
|
|
1342
|
+
buttons,
|
|
1343
|
+
button: 0,
|
|
1344
|
+
pointerId: 1,
|
|
1345
|
+
pointerType: 'mouse',
|
|
1346
|
+
isPrimary: true
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
// Fallback to MouseEvent
|
|
1350
|
+
return new MouseEvent(type, {
|
|
1351
|
+
bubbles: true,
|
|
1352
|
+
cancelable: true,
|
|
1353
|
+
view: window,
|
|
1354
|
+
clientX,
|
|
1355
|
+
clientY,
|
|
1356
|
+
screenX: clientX,
|
|
1357
|
+
screenY: clientY,
|
|
1358
|
+
buttons,
|
|
1359
|
+
button: 0
|
|
1360
|
+
});
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
// Dispatch mousedown/pointerdown
|
|
1364
|
+
element.dispatchEvent(createEvent('pointerdown', startX, startY, 1));
|
|
1365
|
+
element.dispatchEvent(createEvent('mousedown', startX, startY, 1));
|
|
1366
|
+
|
|
1367
|
+
// Dispatch intermediate mousemove/pointermove events
|
|
1368
|
+
const steps = Math.max(10, Math.floor(duration / 20));
|
|
1369
|
+
const stepDelay = duration / steps;
|
|
1370
|
+
|
|
1371
|
+
return new Promise((resolve) => {
|
|
1372
|
+
let currentStep = 0;
|
|
1373
|
+
|
|
1374
|
+
const moveInterval = setInterval(() => {
|
|
1375
|
+
currentStep++;
|
|
1376
|
+
const progress = currentStep / steps;
|
|
1377
|
+
const currentX = startX + (deltaX * progress);
|
|
1378
|
+
const currentY = startY + (deltaY * progress);
|
|
1379
|
+
|
|
1380
|
+
element.dispatchEvent(createEvent('pointermove', currentX, currentY, 1));
|
|
1381
|
+
element.dispatchEvent(createEvent('mousemove', currentX, currentY, 1));
|
|
1382
|
+
|
|
1383
|
+
if (currentStep >= steps) {
|
|
1384
|
+
clearInterval(moveInterval);
|
|
1385
|
+
|
|
1386
|
+
// Dispatch mouseup/pointerup
|
|
1387
|
+
element.dispatchEvent(createEvent('pointerup', endX, endY, 0));
|
|
1388
|
+
element.dispatchEvent(createEvent('mouseup', endX, endY, 0));
|
|
1389
|
+
element.dispatchEvent(createEvent('click', endX, endY, 0));
|
|
1390
|
+
|
|
1391
|
+
resolve({
|
|
1392
|
+
success: true,
|
|
1393
|
+
startX: Math.round(startX),
|
|
1394
|
+
startY: Math.round(startY),
|
|
1395
|
+
endX: Math.round(endX),
|
|
1396
|
+
endY: Math.round(endY),
|
|
1397
|
+
mode: 'synthetic'
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
}, stepDelay);
|
|
1401
|
+
});
|
|
1402
|
+
}, validatedArgs.selector, deltaX, deltaY, duration);
|
|
1403
|
+
|
|
1404
|
+
if (!result.success) {
|
|
1405
|
+
throw new Error(result.error);
|
|
1289
1406
|
}
|
|
1290
1407
|
|
|
1291
|
-
const rect = element.getBoundingClientRect();
|
|
1292
1408
|
return {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1409
|
+
content: [{
|
|
1410
|
+
type: "text",
|
|
1411
|
+
text: `Dragged ${validatedArgs.selector} ${validatedArgs.direction} by ${distance}px (${result.mode} mode):\n` +
|
|
1412
|
+
` Start position: (${result.startX}, ${result.startY})\n` +
|
|
1413
|
+
` End position: (${result.endX}, ${result.endY})\n` +
|
|
1414
|
+
` Delta: (${deltaX}px, ${deltaY}px)\n` +
|
|
1415
|
+
` Duration: ${duration}ms\n` +
|
|
1416
|
+
` Events: pointerdown → ${Math.floor(duration / 20)} × pointermove → pointerup`
|
|
1417
|
+
}],
|
|
1298
1418
|
};
|
|
1299
|
-
}
|
|
1419
|
+
} else {
|
|
1420
|
+
// Native mode: use Puppeteer mouse API (default, faster)
|
|
1421
|
+
const elementInfo = await page.evaluate((selector) => {
|
|
1422
|
+
const element = document.querySelector(selector);
|
|
1423
|
+
if (!element) {
|
|
1424
|
+
return { success: false, error: `Element not found: ${selector}` };
|
|
1425
|
+
}
|
|
1300
1426
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1427
|
+
const rect = element.getBoundingClientRect();
|
|
1428
|
+
return {
|
|
1429
|
+
success: true,
|
|
1430
|
+
centerX: rect.left + rect.width / 2,
|
|
1431
|
+
centerY: rect.top + rect.height / 2,
|
|
1432
|
+
width: rect.width,
|
|
1433
|
+
height: rect.height
|
|
1434
|
+
};
|
|
1435
|
+
}, validatedArgs.selector);
|
|
1304
1436
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const endX = startX + deltaX;
|
|
1309
|
-
const endY = startY + deltaY;
|
|
1437
|
+
if (!elementInfo.success) {
|
|
1438
|
+
throw new Error(elementInfo.error);
|
|
1439
|
+
}
|
|
1310
1440
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1441
|
+
const startX = elementInfo.centerX;
|
|
1442
|
+
const startY = elementInfo.centerY;
|
|
1443
|
+
const endX = startX + deltaX;
|
|
1444
|
+
const endY = startY + deltaY;
|
|
1313
1445
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1446
|
+
// Move to start position
|
|
1447
|
+
await page.mouse.move(startX, startY);
|
|
1316
1448
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1449
|
+
// Press mouse button (start drag)
|
|
1450
|
+
await page.mouse.down();
|
|
1319
1451
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
await page.mouse.move(endX, endY, { steps });
|
|
1452
|
+
// Wait a bit to ensure drag is registered
|
|
1453
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1323
1454
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1455
|
+
// Move mouse to end position (drag)
|
|
1456
|
+
const steps = Math.max(10, Math.floor(duration / 20)); // Smooth movement
|
|
1457
|
+
await page.mouse.move(endX, endY, { steps });
|
|
1326
1458
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1459
|
+
// Wait for duration
|
|
1460
|
+
await new Promise(resolve => setTimeout(resolve, Math.max(0, duration - steps * 20)));
|
|
1329
1461
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1462
|
+
// Release mouse button (end drag)
|
|
1463
|
+
await page.mouse.up();
|
|
1464
|
+
|
|
1465
|
+
return {
|
|
1466
|
+
content: [{
|
|
1467
|
+
type: "text",
|
|
1468
|
+
text: `Dragged ${validatedArgs.selector} ${validatedArgs.direction} by ${distance}px (native mode):\n` +
|
|
1469
|
+
` Start position: (${Math.round(startX)}, ${Math.round(startY)})\n` +
|
|
1470
|
+
` End position: (${Math.round(endX)}, ${Math.round(endY)})\n` +
|
|
1471
|
+
` Delta: (${deltaX}px, ${deltaY}px)\n` +
|
|
1472
|
+
` Duration: ${duration}ms`
|
|
1473
|
+
}],
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1340
1476
|
}
|
|
1341
1477
|
|
|
1342
1478
|
if (name === "scrollHorizontal") {
|
|
@@ -1515,6 +1651,9 @@ async function executeToolInternal(name, args) {
|
|
|
1515
1651
|
|
|
1516
1652
|
let hintsText = '\n\n** AI HINTS **';
|
|
1517
1653
|
hintsText += `\nPage type: ${hints.pageType}`;
|
|
1654
|
+
if (hints.heading) {
|
|
1655
|
+
hintsText += `\nPage heading: "${hints.heading}"`;
|
|
1656
|
+
}
|
|
1518
1657
|
if (hints.availableActions.length > 0) {
|
|
1519
1658
|
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
1520
1659
|
}
|
|
@@ -2105,9 +2244,12 @@ Start coding now.`;
|
|
|
2105
2244
|
const maxResults = validatedArgs.maxResults || 5;
|
|
2106
2245
|
|
|
2107
2246
|
// Execute smart search in page context
|
|
2108
|
-
const results = await page.evaluate((description, maxResults, utilsCode) => {
|
|
2247
|
+
const results = await page.evaluate((description, maxResults, utilsCode, selectorResolverCode) => {
|
|
2109
2248
|
// Inject utilities into page context
|
|
2110
2249
|
eval(utilsCode);
|
|
2250
|
+
if (typeof registerElement === 'undefined') {
|
|
2251
|
+
eval(selectorResolverCode);
|
|
2252
|
+
}
|
|
2111
2253
|
|
|
2112
2254
|
// Determine element type from description
|
|
2113
2255
|
const elementType = determineElementType(description);
|
|
@@ -2163,18 +2305,29 @@ Start coding now.`;
|
|
|
2163
2305
|
});
|
|
2164
2306
|
|
|
2165
2307
|
// Filter and sort
|
|
2166
|
-
|
|
2308
|
+
const filtered = analyzed
|
|
2167
2309
|
.filter(r => r.score > 5) // Minimum threshold
|
|
2168
2310
|
.sort((a, b) => b.score - a.score)
|
|
2169
2311
|
.slice(0, maxResults);
|
|
2170
2312
|
|
|
2171
|
-
|
|
2313
|
+
// Register found elements in APOM registry and assign IDs
|
|
2314
|
+
filtered.forEach((result, idx) => {
|
|
2315
|
+
const apomId = `smart_${result.type}_${idx}`;
|
|
2316
|
+
result.id = apomId;
|
|
2317
|
+
if (typeof registerElement === 'function') {
|
|
2318
|
+
registerElement(apomId, result.selector, { source: 'smartFindElement' });
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
return filtered;
|
|
2323
|
+
|
|
2324
|
+
}, validatedArgs.description, maxResults, elementFinderUtils, selectorResolver);
|
|
2172
2325
|
|
|
2173
2326
|
const hints = {
|
|
2174
2327
|
totalCandidates: results.length,
|
|
2175
2328
|
bestMatch: results[0] || null,
|
|
2176
2329
|
suggestion: results.length > 0
|
|
2177
|
-
? `Use selector: ${results[0].selector}`
|
|
2330
|
+
? `Use id: "${results[0].id}" or selector: ${results[0].selector}`
|
|
2178
2331
|
: 'No good matches found. Try a different description.',
|
|
2179
2332
|
};
|
|
2180
2333
|
|
|
@@ -2212,20 +2365,25 @@ Start coding now.`;
|
|
|
2212
2365
|
};
|
|
2213
2366
|
}
|
|
2214
2367
|
|
|
2368
|
+
// Store previous analysis for diff calculation
|
|
2369
|
+
if (!global.previousApomAnalysis) {
|
|
2370
|
+
global.previousApomAnalysis = new Map(); // pageUrl -> analysis data
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2215
2373
|
if (name === "analyzePage") {
|
|
2216
2374
|
const validatedArgs = schemas.AnalyzePageSchema.parse(args);
|
|
2217
2375
|
const page = await getLastOpenPage();
|
|
2218
2376
|
const pageUrl = page.url();
|
|
2219
2377
|
|
|
2220
2378
|
// APOM Tree format (default) - v2 with tree structure and positioning
|
|
2221
|
-
const apomResult = await page.evaluate((apomTreeConverterCode, selectorResolverCode, shouldRegister, includeAll) => {
|
|
2379
|
+
const apomResult = await page.evaluate((apomTreeConverterCode, selectorResolverCode, shouldRegister, includeAll, viewportOnly) => {
|
|
2222
2380
|
// Inject utilities
|
|
2223
2381
|
eval(apomTreeConverterCode);
|
|
2224
2382
|
eval(selectorResolverCode);
|
|
2225
2383
|
|
|
2226
2384
|
// Build APOM tree
|
|
2227
2385
|
// interactiveOnly = !includeAll (if includeAll is true, we want ALL elements)
|
|
2228
|
-
const apomData = buildAPOMTree(!includeAll);
|
|
2386
|
+
const apomData = buildAPOMTree(!includeAll, viewportOnly);
|
|
2229
2387
|
|
|
2230
2388
|
// Register elements in selector resolver if requested
|
|
2231
2389
|
if (shouldRegister) {
|
|
@@ -2258,7 +2416,43 @@ Start coding now.`;
|
|
|
2258
2416
|
}
|
|
2259
2417
|
|
|
2260
2418
|
return apomData;
|
|
2261
|
-
}, apomTreeConverter, selectorResolver, validatedArgs.registerElements !== false, validatedArgs.includeAll || false);
|
|
2419
|
+
}, apomTreeConverter, selectorResolver, validatedArgs.registerElements !== false, validatedArgs.includeAll || false, validatedArgs.viewportOnly || false);
|
|
2420
|
+
|
|
2421
|
+
// Handle diff mode
|
|
2422
|
+
if (validatedArgs.diff) {
|
|
2423
|
+
const previousAnalysis = global.previousApomAnalysis.get(pageUrl);
|
|
2424
|
+
|
|
2425
|
+
if (previousAnalysis) {
|
|
2426
|
+
// Calculate diff
|
|
2427
|
+
const diff = calculateApomDiff(previousAnalysis, apomResult);
|
|
2428
|
+
|
|
2429
|
+
// Store current analysis for next diff
|
|
2430
|
+
global.previousApomAnalysis.set(pageUrl, apomResult);
|
|
2431
|
+
|
|
2432
|
+
return {
|
|
2433
|
+
content: [{
|
|
2434
|
+
type: 'text',
|
|
2435
|
+
text: JSON.stringify({
|
|
2436
|
+
mode: 'diff',
|
|
2437
|
+
pageId: apomResult.pageId,
|
|
2438
|
+
url: apomResult.url,
|
|
2439
|
+
timestamp: apomResult.timestamp,
|
|
2440
|
+
previousTimestamp: previousAnalysis.timestamp,
|
|
2441
|
+
diff,
|
|
2442
|
+
metadata: apomResult.metadata,
|
|
2443
|
+
alerts: apomResult.alerts
|
|
2444
|
+
})
|
|
2445
|
+
}]
|
|
2446
|
+
};
|
|
2447
|
+
} else {
|
|
2448
|
+
// No previous analysis, return full result with note
|
|
2449
|
+
global.previousApomAnalysis.set(pageUrl, apomResult);
|
|
2450
|
+
apomResult._note = 'First analysis for this page, no diff available';
|
|
2451
|
+
}
|
|
2452
|
+
} else {
|
|
2453
|
+
// Store for future diff
|
|
2454
|
+
global.previousApomAnalysis.set(pageUrl, apomResult);
|
|
2455
|
+
}
|
|
2262
2456
|
|
|
2263
2457
|
return {
|
|
2264
2458
|
content: [{
|
|
@@ -2268,6 +2462,82 @@ Start coding now.`;
|
|
|
2268
2462
|
};
|
|
2269
2463
|
}
|
|
2270
2464
|
|
|
2465
|
+
/**
|
|
2466
|
+
* Calculate diff between two APOM analyses
|
|
2467
|
+
*/
|
|
2468
|
+
function calculateApomDiff(previous, current) {
|
|
2469
|
+
const previousElements = flattenApomTree(previous.tree);
|
|
2470
|
+
const currentElements = flattenApomTree(current.tree);
|
|
2471
|
+
|
|
2472
|
+
const previousIds = new Set(previousElements.map(e => e.id));
|
|
2473
|
+
const currentIds = new Set(currentElements.map(e => e.id));
|
|
2474
|
+
|
|
2475
|
+
const added = currentElements.filter(e => !previousIds.has(e.id));
|
|
2476
|
+
const removed = previousElements.filter(e => !currentIds.has(e.id));
|
|
2477
|
+
|
|
2478
|
+
// Find changed elements (same ID but different content)
|
|
2479
|
+
const changed = [];
|
|
2480
|
+
for (const curr of currentElements) {
|
|
2481
|
+
if (previousIds.has(curr.id)) {
|
|
2482
|
+
const prev = previousElements.find(e => e.id === curr.id);
|
|
2483
|
+
if (prev && JSON.stringify(prev.metadata) !== JSON.stringify(curr.metadata)) {
|
|
2484
|
+
changed.push({
|
|
2485
|
+
id: curr.id,
|
|
2486
|
+
type: curr.type,
|
|
2487
|
+
before: prev.metadata,
|
|
2488
|
+
after: curr.metadata
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
return {
|
|
2495
|
+
added: added.length > 0 ? added : undefined,
|
|
2496
|
+
removed: removed.length > 0 ? removed : undefined,
|
|
2497
|
+
changed: changed.length > 0 ? changed : undefined,
|
|
2498
|
+
summary: {
|
|
2499
|
+
addedCount: added.length,
|
|
2500
|
+
removedCount: removed.length,
|
|
2501
|
+
changedCount: changed.length
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
/**
|
|
2507
|
+
* Flatten APOM tree to array of elements
|
|
2508
|
+
*/
|
|
2509
|
+
function flattenApomTree(node, result = []) {
|
|
2510
|
+
if (!node) return result;
|
|
2511
|
+
|
|
2512
|
+
// Handle compact format: { "tag_id": [children] }
|
|
2513
|
+
if (typeof node === 'object' && !node.id && !node.tag) {
|
|
2514
|
+
const keys = Object.keys(node);
|
|
2515
|
+
for (const key of keys) {
|
|
2516
|
+
if (Array.isArray(node[key])) {
|
|
2517
|
+
node[key].forEach(child => flattenApomTree(child, result));
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return result;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// Interactive element with id
|
|
2524
|
+
if (node.id) {
|
|
2525
|
+
result.push({
|
|
2526
|
+
id: node.id,
|
|
2527
|
+
tag: node.tag,
|
|
2528
|
+
type: node.type,
|
|
2529
|
+
metadata: node.metadata
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// Process children
|
|
2534
|
+
if (node.children) {
|
|
2535
|
+
node.children.forEach(child => flattenApomTree(child, result));
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
return result;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2271
2541
|
if (name === "getElementDetails") {
|
|
2272
2542
|
const validatedArgs = schemas.GetElementDetailsSchema.parse(args);
|
|
2273
2543
|
const page = await getLastOpenPage();
|
|
@@ -2419,8 +2689,11 @@ Start coding now.`;
|
|
|
2419
2689
|
const validatedArgs = schemas.FindElementsByTextSchema.parse(args);
|
|
2420
2690
|
const page = await getLastOpenPage();
|
|
2421
2691
|
|
|
2422
|
-
const elements = await page.evaluate((text, exact, caseSensitive, utilsCode) => {
|
|
2692
|
+
const elements = await page.evaluate((text, exact, caseSensitive, utilsCode, selectorResolverCode) => {
|
|
2423
2693
|
eval(utilsCode);
|
|
2694
|
+
if (typeof registerElement === 'undefined') {
|
|
2695
|
+
eval(selectorResolverCode);
|
|
2696
|
+
}
|
|
2424
2697
|
|
|
2425
2698
|
const results = [];
|
|
2426
2699
|
const searchText = caseSensitive ? text : text.toLowerCase();
|
|
@@ -2447,9 +2720,16 @@ Start coding now.`;
|
|
|
2447
2720
|
: compareText.includes(searchText);
|
|
2448
2721
|
|
|
2449
2722
|
if (matches) {
|
|
2723
|
+
const selector = getUniqueSelectorInPage(el);
|
|
2724
|
+
const type = el.tagName.toLowerCase();
|
|
2725
|
+
const apomId = `text_${type}_${results.length}`;
|
|
2726
|
+
if (typeof registerElement === 'function') {
|
|
2727
|
+
registerElement(apomId, selector, { source: 'findElementsByText' });
|
|
2728
|
+
}
|
|
2450
2729
|
results.push({
|
|
2451
|
-
|
|
2452
|
-
|
|
2730
|
+
id: apomId,
|
|
2731
|
+
selector,
|
|
2732
|
+
type,
|
|
2453
2733
|
text: elementText.substring(0, 100), // Only first 100 chars for preview
|
|
2454
2734
|
visible: el.offsetParent !== null, // Add visibility check
|
|
2455
2735
|
});
|
|
@@ -2457,7 +2737,7 @@ Start coding now.`;
|
|
|
2457
2737
|
});
|
|
2458
2738
|
|
|
2459
2739
|
return results;
|
|
2460
|
-
}, validatedArgs.text, validatedArgs.exact || false, validatedArgs.caseSensitive || false, elementFinderUtils);
|
|
2740
|
+
}, validatedArgs.text, validatedArgs.exact || false, validatedArgs.caseSensitive || false, elementFinderUtils, selectorResolver);
|
|
2461
2741
|
|
|
2462
2742
|
// Prioritize visible elements and limit results to prevent token overflow
|
|
2463
2743
|
const visibleElements = elements.filter(el => el.visible);
|
|
@@ -2972,13 +3252,102 @@ Start coding now.`;
|
|
|
2972
3252
|
};
|
|
2973
3253
|
}
|
|
2974
3254
|
|
|
3255
|
+
// Resolve pageObjectMode (backward compat: generatePageObject: true -> 'generate')
|
|
3256
|
+
const pageObjectMode = args.pageObjectMode || (args.generatePageObject ? 'generate' : 'none');
|
|
3257
|
+
|
|
2975
3258
|
// Select generator based on language
|
|
2976
|
-
let generator;
|
|
2977
3259
|
const options = {
|
|
2978
3260
|
cleanSelectors: args.cleanSelectors !== false, // default true
|
|
2979
3261
|
includeComments: args.includeComments !== false, // default true
|
|
2980
3262
|
};
|
|
2981
3263
|
|
|
3264
|
+
// Resolve POM elements for integrated modes
|
|
3265
|
+
let pomElements = null;
|
|
3266
|
+
let pomClassName = null;
|
|
3267
|
+
let pomImportPath = null;
|
|
3268
|
+
let pageObjectData = null;
|
|
3269
|
+
|
|
3270
|
+
if (pageObjectMode === 'generate-integrated' || pageObjectMode === 'generate') {
|
|
3271
|
+
try {
|
|
3272
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
3273
|
+
if (entryUrl) {
|
|
3274
|
+
let page;
|
|
3275
|
+
try {
|
|
3276
|
+
page = await getLastOpenPage();
|
|
3277
|
+
const currentUrl = page.url();
|
|
3278
|
+
if (currentUrl !== entryUrl) {
|
|
3279
|
+
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3280
|
+
}
|
|
3281
|
+
} catch (error) {
|
|
3282
|
+
page = await getOrCreatePage(entryUrl);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
const pageObjectOptions = {
|
|
3286
|
+
className: args.pageObjectClassName || null,
|
|
3287
|
+
framework: args.language,
|
|
3288
|
+
includeComments: args.includeComments !== false,
|
|
3289
|
+
groupElements: true
|
|
3290
|
+
};
|
|
3291
|
+
|
|
3292
|
+
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3293
|
+
if (pageObjectResult.success) {
|
|
3294
|
+
const extension = args.language.includes('typescript') ? '.ts' :
|
|
3295
|
+
args.language.includes('java') ? '.java' : '.py';
|
|
3296
|
+
pageObjectData = {
|
|
3297
|
+
code: pageObjectResult.code,
|
|
3298
|
+
className: pageObjectResult.className,
|
|
3299
|
+
suggestedFileName: `${pageObjectResult.className}${extension}`,
|
|
3300
|
+
elementCount: pageObjectResult.elementCount
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3303
|
+
if (pageObjectMode === 'generate-integrated') {
|
|
3304
|
+
pomElements = pageObjectResult.elements;
|
|
3305
|
+
pomClassName = pageObjectResult.className;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
// Page Object generation failed, continue without it
|
|
3311
|
+
}
|
|
3312
|
+
} else if (pageObjectMode === 'use-existing') {
|
|
3313
|
+
if (!args.pageObjectFile) {
|
|
3314
|
+
return {
|
|
3315
|
+
content: [{
|
|
3316
|
+
type: 'text',
|
|
3317
|
+
text: JSON.stringify({
|
|
3318
|
+
error: "pageObjectFile is required for 'use-existing' mode"
|
|
3319
|
+
}, null, 2)
|
|
3320
|
+
}],
|
|
3321
|
+
isError: true
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
try {
|
|
3326
|
+
const pomContent = FileAppender.readFile(args.pageObjectFile);
|
|
3327
|
+
const parsed = parsePomFile(pomContent, args.language);
|
|
3328
|
+
pomElements = parsed.elements;
|
|
3329
|
+
pomClassName = parsed.className;
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
return {
|
|
3332
|
+
content: [{
|
|
3333
|
+
type: 'text',
|
|
3334
|
+
text: JSON.stringify({
|
|
3335
|
+
error: `Failed to parse POM file: ${error.message}`
|
|
3336
|
+
}, null, 2)
|
|
3337
|
+
}],
|
|
3338
|
+
isError: true
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// Add POM options to generator
|
|
3344
|
+
if (pomElements && pomClassName) {
|
|
3345
|
+
options.pomElements = pomElements;
|
|
3346
|
+
options.pomClassName = pomClassName;
|
|
3347
|
+
options.pomImportPath = pomImportPath;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
let generator;
|
|
2982
3351
|
switch (args.language) {
|
|
2983
3352
|
case 'playwright-typescript':
|
|
2984
3353
|
generator = new PlaywrightTypeScriptGenerator(options);
|
|
@@ -3017,59 +3386,21 @@ Start coding now.`;
|
|
|
3017
3386
|
referenceTestName: args.referenceTestName
|
|
3018
3387
|
};
|
|
3019
3388
|
|
|
3020
|
-
// Generate Page Object if requested
|
|
3021
|
-
let pageObjectData = null;
|
|
3022
|
-
if (args.generatePageObject) {
|
|
3023
|
-
try {
|
|
3024
|
-
const entryUrl = scenario.metadata?.entryUrl;
|
|
3025
|
-
if (entryUrl) {
|
|
3026
|
-
let page;
|
|
3027
|
-
try {
|
|
3028
|
-
page = await getLastOpenPage();
|
|
3029
|
-
const currentUrl = page.url();
|
|
3030
|
-
if (currentUrl !== entryUrl) {
|
|
3031
|
-
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3032
|
-
}
|
|
3033
|
-
} catch (error) {
|
|
3034
|
-
page = await getOrCreatePage(entryUrl);
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
const pageObjectOptions = {
|
|
3038
|
-
className: args.pageObjectClassName || null,
|
|
3039
|
-
framework: args.language,
|
|
3040
|
-
includeComments: args.includeComments !== false,
|
|
3041
|
-
groupElements: true
|
|
3042
|
-
};
|
|
3043
|
-
|
|
3044
|
-
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3045
|
-
if (pageObjectResult.success) {
|
|
3046
|
-
// Suggest filename based on className
|
|
3047
|
-
const extension = args.language.includes('typescript') ? '.ts' :
|
|
3048
|
-
args.language.includes('java') ? '.java' : '.py';
|
|
3049
|
-
pageObjectData = {
|
|
3050
|
-
code: pageObjectResult.code,
|
|
3051
|
-
className: pageObjectResult.className,
|
|
3052
|
-
suggestedFileName: `${pageObjectResult.className}${extension}`,
|
|
3053
|
-
elementCount: pageObjectResult.elementCount
|
|
3054
|
-
};
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
} catch (error) {
|
|
3058
|
-
// Page Object generation failed, continue without it
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
|
|
3062
3389
|
// Return JSON with instructions for Claude Code to append the test
|
|
3063
3390
|
const result = {
|
|
3064
3391
|
action: 'append_test',
|
|
3065
3392
|
targetFile: args.targetFile,
|
|
3066
|
-
testCode: testOnly,
|
|
3393
|
+
testCode: testOnly,
|
|
3067
3394
|
testName: args.testName || scenario.metadata?.name,
|
|
3068
3395
|
insertPosition: appendOptions.insertPosition,
|
|
3069
3396
|
referenceTestName: appendOptions.referenceTestName,
|
|
3070
3397
|
instruction: `Read file '${args.targetFile}', append the testCode at position '${appendOptions.insertPosition}', then write the file back.`
|
|
3071
3398
|
};
|
|
3072
3399
|
|
|
3400
|
+
if (pomClassName) {
|
|
3401
|
+
result.pomIntegration = { className: pomClassName, mode: pageObjectMode };
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3073
3404
|
if (pageObjectData) {
|
|
3074
3405
|
result.pageObject = pageObjectData;
|
|
3075
3406
|
result.instruction += ` Also create a Page Object file '${pageObjectData.suggestedFileName}' with the provided pageObject.code.`;
|
|
@@ -3111,86 +3442,48 @@ Start coding now.`;
|
|
|
3111
3442
|
};
|
|
3112
3443
|
}
|
|
3113
3444
|
|
|
3114
|
-
//
|
|
3115
|
-
|
|
3445
|
+
// Resolve pageObjectMode (backward compat: generatePageObject: true -> 'generate')
|
|
3446
|
+
const pageObjectMode = args.pageObjectMode || (args.generatePageObject ? 'generate' : 'none');
|
|
3447
|
+
|
|
3116
3448
|
const options = {
|
|
3117
3449
|
cleanSelectors: args.cleanSelectors !== false, // default true
|
|
3118
3450
|
includeComments: args.includeComments !== false, // default true
|
|
3119
3451
|
};
|
|
3120
3452
|
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
generator = new SeleniumPythonGenerator(options);
|
|
3130
|
-
break;
|
|
3131
|
-
case 'selenium-java':
|
|
3132
|
-
generator = new SeleniumJavaGenerator(options);
|
|
3133
|
-
break;
|
|
3134
|
-
default:
|
|
3453
|
+
// Resolve POM elements for integrated modes
|
|
3454
|
+
let pomElements = null;
|
|
3455
|
+
let pomClassName = null;
|
|
3456
|
+
let pageObjectData = null;
|
|
3457
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
3458
|
+
|
|
3459
|
+
if (pageObjectMode === 'generate-integrated' || pageObjectMode === 'generate') {
|
|
3460
|
+
if (!entryUrl) {
|
|
3135
3461
|
return {
|
|
3136
3462
|
content: [{
|
|
3137
3463
|
type: 'text',
|
|
3138
3464
|
text: JSON.stringify({
|
|
3139
|
-
error:
|
|
3465
|
+
error: 'Cannot generate Page Object: scenario has no entryUrl in metadata'
|
|
3140
3466
|
}, null, 2)
|
|
3141
3467
|
}],
|
|
3142
3468
|
isError: true
|
|
3143
3469
|
};
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
// Generate test code with full imports
|
|
3147
|
-
const testCode = generator.generate(scenario, options);
|
|
3148
|
-
|
|
3149
|
-
// Generate suggested filename
|
|
3150
|
-
const testName = scenario.metadata?.name || 'test';
|
|
3151
|
-
const extension = args.language.includes('typescript') ? '.spec.ts' :
|
|
3152
|
-
args.language.includes('java') ? 'Test.java' :
|
|
3153
|
-
args.language.includes('python') ? '_test.py' : '.test.js';
|
|
3154
|
-
const suggestedFileName = args.language.includes('java')
|
|
3155
|
-
? testName.charAt(0).toUpperCase() + testName.slice(1) + 'Test.java'
|
|
3156
|
-
: testName.replace(/\s+/g, '_').toLowerCase() + extension;
|
|
3470
|
+
}
|
|
3157
3471
|
|
|
3158
|
-
// If generatePageObject is requested, also generate Page Object class
|
|
3159
|
-
if (args.generatePageObject) {
|
|
3160
3472
|
try {
|
|
3161
|
-
// Get page - need to open at scenario's entry URL
|
|
3162
3473
|
let page;
|
|
3163
|
-
const entryUrl = scenario.metadata?.entryUrl;
|
|
3164
|
-
|
|
3165
|
-
if (!entryUrl) {
|
|
3166
|
-
return {
|
|
3167
|
-
content: [{
|
|
3168
|
-
type: 'text',
|
|
3169
|
-
text: JSON.stringify({
|
|
3170
|
-
error: 'Cannot generate Page Object: scenario has no entryUrl in metadata'
|
|
3171
|
-
}, null, 2)
|
|
3172
|
-
}],
|
|
3173
|
-
isError: true
|
|
3174
|
-
};
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
|
-
// Try to get existing page or open new one
|
|
3178
3474
|
try {
|
|
3179
3475
|
page = await getLastOpenPage();
|
|
3180
|
-
// Navigate to entry URL if current page is different
|
|
3181
3476
|
const currentUrl = page.url();
|
|
3182
3477
|
if (currentUrl !== entryUrl) {
|
|
3183
3478
|
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
3184
3479
|
}
|
|
3185
3480
|
} catch (error) {
|
|
3186
|
-
// No page open, create new one
|
|
3187
3481
|
page = await getOrCreatePage(entryUrl);
|
|
3188
3482
|
}
|
|
3189
3483
|
|
|
3190
|
-
// Generate Page Object
|
|
3191
3484
|
const pageObjectOptions = {
|
|
3192
3485
|
className: args.pageObjectClassName || null,
|
|
3193
|
-
framework: args.language,
|
|
3486
|
+
framework: args.language,
|
|
3194
3487
|
includeComments: args.includeComments !== false,
|
|
3195
3488
|
groupElements: true
|
|
3196
3489
|
};
|
|
@@ -3198,71 +3491,119 @@ Start coding now.`;
|
|
|
3198
3491
|
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
3199
3492
|
|
|
3200
3493
|
if (pageObjectResult.success) {
|
|
3201
|
-
// Suggest Page Object filename
|
|
3202
3494
|
const poExtension = args.language.includes('typescript') ? '.ts' :
|
|
3203
3495
|
args.language.includes('java') ? '.java' : '.py';
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
type: 'text',
|
|
3210
|
-
text: JSON.stringify({
|
|
3211
|
-
action: 'create_new_file',
|
|
3212
|
-
suggestedFileName: suggestedFileName,
|
|
3213
|
-
testCode: testCode,
|
|
3214
|
-
pageObject: {
|
|
3215
|
-
code: pageObjectResult.code,
|
|
3216
|
-
className: pageObjectResult.className,
|
|
3217
|
-
suggestedFileName: pageObjectFileName,
|
|
3218
|
-
elementCount: pageObjectResult.elementCount
|
|
3219
|
-
},
|
|
3220
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode. Also create a Page Object file '${pageObjectFileName}' with the pageObject.code.`
|
|
3221
|
-
}, null, 2)
|
|
3222
|
-
}]
|
|
3223
|
-
};
|
|
3224
|
-
} else {
|
|
3225
|
-
// Page Object generation failed, return test code only with warning
|
|
3226
|
-
return {
|
|
3227
|
-
content: [{
|
|
3228
|
-
type: 'text',
|
|
3229
|
-
text: JSON.stringify({
|
|
3230
|
-
action: 'create_new_file',
|
|
3231
|
-
suggestedFileName: suggestedFileName,
|
|
3232
|
-
testCode: testCode,
|
|
3233
|
-
warning: 'Page Object generation failed: ' + (pageObjectResult.error || 'Unknown error'),
|
|
3234
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3235
|
-
}, null, 2)
|
|
3236
|
-
}]
|
|
3496
|
+
pageObjectData = {
|
|
3497
|
+
code: pageObjectResult.code,
|
|
3498
|
+
className: pageObjectResult.className,
|
|
3499
|
+
suggestedFileName: `${pageObjectResult.className}${poExtension}`,
|
|
3500
|
+
elementCount: pageObjectResult.elementCount
|
|
3237
3501
|
};
|
|
3502
|
+
|
|
3503
|
+
if (pageObjectMode === 'generate-integrated') {
|
|
3504
|
+
pomElements = pageObjectResult.elements;
|
|
3505
|
+
pomClassName = pageObjectResult.className;
|
|
3506
|
+
}
|
|
3238
3507
|
}
|
|
3239
3508
|
} catch (error) {
|
|
3240
|
-
// Page Object generation failed,
|
|
3509
|
+
// Page Object generation failed, continue without it
|
|
3510
|
+
}
|
|
3511
|
+
} else if (pageObjectMode === 'use-existing') {
|
|
3512
|
+
if (!args.pageObjectFile) {
|
|
3241
3513
|
return {
|
|
3242
3514
|
content: [{
|
|
3243
3515
|
type: 'text',
|
|
3244
3516
|
text: JSON.stringify({
|
|
3245
|
-
|
|
3246
|
-
suggestedFileName: suggestedFileName,
|
|
3247
|
-
testCode: testCode,
|
|
3248
|
-
warning: 'Page Object generation error: ' + error.message,
|
|
3249
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3517
|
+
error: "pageObjectFile is required for 'use-existing' mode"
|
|
3250
3518
|
}, null, 2)
|
|
3251
|
-
}]
|
|
3519
|
+
}],
|
|
3520
|
+
isError: true
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
try {
|
|
3525
|
+
const pomContent = FileAppender.readFile(args.pageObjectFile);
|
|
3526
|
+
const parsed = parsePomFile(pomContent, args.language);
|
|
3527
|
+
pomElements = parsed.elements;
|
|
3528
|
+
pomClassName = parsed.className;
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
return {
|
|
3531
|
+
content: [{
|
|
3532
|
+
type: 'text',
|
|
3533
|
+
text: JSON.stringify({
|
|
3534
|
+
error: `Failed to parse POM file: ${error.message}`
|
|
3535
|
+
}, null, 2)
|
|
3536
|
+
}],
|
|
3537
|
+
isError: true
|
|
3252
3538
|
};
|
|
3253
3539
|
}
|
|
3254
3540
|
}
|
|
3255
3541
|
|
|
3256
|
-
//
|
|
3542
|
+
// Add POM options to generator
|
|
3543
|
+
if (pomElements && pomClassName) {
|
|
3544
|
+
options.pomElements = pomElements;
|
|
3545
|
+
options.pomClassName = pomClassName;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
let generator;
|
|
3549
|
+
switch (args.language) {
|
|
3550
|
+
case 'playwright-typescript':
|
|
3551
|
+
generator = new PlaywrightTypeScriptGenerator(options);
|
|
3552
|
+
break;
|
|
3553
|
+
case 'playwright-python':
|
|
3554
|
+
generator = new PlaywrightPythonGenerator(options);
|
|
3555
|
+
break;
|
|
3556
|
+
case 'selenium-python':
|
|
3557
|
+
generator = new SeleniumPythonGenerator(options);
|
|
3558
|
+
break;
|
|
3559
|
+
case 'selenium-java':
|
|
3560
|
+
generator = new SeleniumJavaGenerator(options);
|
|
3561
|
+
break;
|
|
3562
|
+
default:
|
|
3563
|
+
return {
|
|
3564
|
+
content: [{
|
|
3565
|
+
type: 'text',
|
|
3566
|
+
text: JSON.stringify({
|
|
3567
|
+
error: `Unknown language: ${args.language}. Supported: playwright-typescript, playwright-python, selenium-python, selenium-java`
|
|
3568
|
+
}, null, 2)
|
|
3569
|
+
}],
|
|
3570
|
+
isError: true
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
// Generate test code with full imports
|
|
3575
|
+
const testCode = generator.generate(scenario, options);
|
|
3576
|
+
|
|
3577
|
+
// Generate suggested filename
|
|
3578
|
+
const testName = scenario.metadata?.name || 'test';
|
|
3579
|
+
const extension = args.language.includes('typescript') ? '.spec.ts' :
|
|
3580
|
+
args.language.includes('java') ? 'Test.java' :
|
|
3581
|
+
args.language.includes('python') ? '_test.py' : '.test.js';
|
|
3582
|
+
const suggestedFileName = args.language.includes('java')
|
|
3583
|
+
? testName.charAt(0).toUpperCase() + testName.slice(1) + 'Test.java'
|
|
3584
|
+
: testName.replace(/\s+/g, '_').toLowerCase() + extension;
|
|
3585
|
+
|
|
3586
|
+
// Build result
|
|
3587
|
+
const result = {
|
|
3588
|
+
action: 'create_new_file',
|
|
3589
|
+
suggestedFileName: suggestedFileName,
|
|
3590
|
+
testCode: testCode,
|
|
3591
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3592
|
+
};
|
|
3593
|
+
|
|
3594
|
+
if (pomClassName) {
|
|
3595
|
+
result.pomIntegration = { className: pomClassName, mode: pageObjectMode };
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
if (pageObjectData) {
|
|
3599
|
+
result.pageObject = pageObjectData;
|
|
3600
|
+
result.instruction = `Create a new test file '${suggestedFileName}' with the testCode. Also create a Page Object file '${pageObjectData.suggestedFileName}' with the pageObject.code.`;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3257
3603
|
return {
|
|
3258
3604
|
content: [{
|
|
3259
3605
|
type: 'text',
|
|
3260
|
-
text: JSON.stringify(
|
|
3261
|
-
action: 'create_new_file',
|
|
3262
|
-
suggestedFileName: suggestedFileName,
|
|
3263
|
-
testCode: testCode,
|
|
3264
|
-
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
3265
|
-
}, null, 2)
|
|
3606
|
+
text: JSON.stringify(result, null, 2)
|
|
3266
3607
|
}]
|
|
3267
3608
|
};
|
|
3268
3609
|
}
|
|
@@ -3424,6 +3765,87 @@ Start coding now.`;
|
|
|
3424
3765
|
};
|
|
3425
3766
|
}
|
|
3426
3767
|
|
|
3768
|
+
// ========== API / Swagger Tools ==========
|
|
3769
|
+
|
|
3770
|
+
if (name === "loadSwagger") {
|
|
3771
|
+
const validatedArgs = schemas.LoadSwaggerSchema.parse(args);
|
|
3772
|
+
const parser = await OpenAPIParser.load(validatedArgs.source, validatedArgs.format || 'auto');
|
|
3773
|
+
const summary = parser.getSummary();
|
|
3774
|
+
|
|
3775
|
+
return {
|
|
3776
|
+
content: [{
|
|
3777
|
+
type: 'text',
|
|
3778
|
+
text: JSON.stringify({
|
|
3779
|
+
success: true,
|
|
3780
|
+
...summary,
|
|
3781
|
+
instruction: 'Use generateApiModels to generate typed models from these schemas.'
|
|
3782
|
+
}, null, 2)
|
|
3783
|
+
}]
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
if (name === "generateApiModels") {
|
|
3788
|
+
const validatedArgs = schemas.GenerateApiModelsSchema.parse(args);
|
|
3789
|
+
const parser = await OpenAPIParser.load(validatedArgs.source, validatedArgs.format || 'auto');
|
|
3790
|
+
let schemasObj = parser.getSchemas();
|
|
3791
|
+
|
|
3792
|
+
// Filter schemas if specified
|
|
3793
|
+
if (validatedArgs.schemas && validatedArgs.schemas.length > 0) {
|
|
3794
|
+
const filtered = {};
|
|
3795
|
+
for (const schemaName of validatedArgs.schemas) {
|
|
3796
|
+
if (schemasObj[schemaName]) filtered[schemaName] = schemasObj[schemaName];
|
|
3797
|
+
}
|
|
3798
|
+
schemasObj = filtered;
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
const metadata = {
|
|
3802
|
+
title: parser.spec.info?.title || '',
|
|
3803
|
+
source: validatedArgs.source,
|
|
3804
|
+
version: parser.version
|
|
3805
|
+
};
|
|
3806
|
+
|
|
3807
|
+
let code, suggestedFileName;
|
|
3808
|
+
|
|
3809
|
+
if (validatedArgs.language === 'typescript') {
|
|
3810
|
+
const generator = new ApiModelsTypeScriptGenerator(schemasObj, {
|
|
3811
|
+
style: validatedArgs.style || 'interface',
|
|
3812
|
+
includeEnums: validatedArgs.includeEnums !== false,
|
|
3813
|
+
includeValidation: validatedArgs.includeValidation || false,
|
|
3814
|
+
});
|
|
3815
|
+
code = generator.generate(metadata);
|
|
3816
|
+
const titleSlug = (metadata.title || 'api').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
3817
|
+
suggestedFileName = `${titleSlug}.models.ts`;
|
|
3818
|
+
} else {
|
|
3819
|
+
const generator = new ApiModelsPythonGenerator(schemasObj, {
|
|
3820
|
+
style: validatedArgs.pythonStyle || 'dataclass',
|
|
3821
|
+
includeEnums: validatedArgs.includeEnums !== false,
|
|
3822
|
+
includeValidation: validatedArgs.includeValidation || false,
|
|
3823
|
+
});
|
|
3824
|
+
code = generator.generate(metadata);
|
|
3825
|
+
const titleSlug = (metadata.title || 'api').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
3826
|
+
suggestedFileName = `${titleSlug}_models.py`;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
const schemaCount = Object.keys(schemasObj).length;
|
|
3830
|
+
const enumCount = Object.values(schemasObj).filter(s => s.enum && s.type === 'string').length;
|
|
3831
|
+
|
|
3832
|
+
return {
|
|
3833
|
+
content: [{
|
|
3834
|
+
type: 'text',
|
|
3835
|
+
text: JSON.stringify({
|
|
3836
|
+
action: 'create_new_file',
|
|
3837
|
+
suggestedFileName,
|
|
3838
|
+
code,
|
|
3839
|
+
schemaCount,
|
|
3840
|
+
enumCount,
|
|
3841
|
+
language: validatedArgs.language,
|
|
3842
|
+
source: validatedArgs.source,
|
|
3843
|
+
instruction: `Create file '${suggestedFileName}' with the generated code.`
|
|
3844
|
+
}, null, 2)
|
|
3845
|
+
}]
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3427
3849
|
return {
|
|
3428
3850
|
content: [
|
|
3429
3851
|
{
|