@skyramp/mcp 0.2.1 → 0.2.3
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/playwright/registerPlaywrightTools.js +10 -0
- package/build/prompts/test-recommendation/scopeAssessment.js +106 -5
- package/build/prompts/test-recommendation/scopeAssessment.test.js +128 -1
- package/build/prompts/testbot/testbot-prompts.js +2 -2
- package/build/prompts/testbot/testbot-prompts.test.js +21 -0
- package/build/tools/test-management/analyzeChangesTool.js +8 -2
- package/build/tools/test-management/uiAnalyzeChangesTool.js +8 -2
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +47 -0
- package/build/utils/dartRouteExtractor.js +319 -0
- package/build/utils/dartRouteExtractor.test.js +307 -0
- package/build/utils/uiPageEnumerator.js +67 -0
- package/build/utils/uiPageEnumerator.test.js +222 -0
- package/node_modules/playwright/lib/mcp/skyramp/common/mouseActions.js +123 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +359 -0
- package/node_modules/playwright/lib/mcp/skyramp/mouseActionTool.js +131 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +146 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +313 -5
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +32 -14
- package/package.json +1 -1
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import * as fs from "fs";
|
|
31
31
|
import * as path from "path";
|
|
32
|
+
import { extractDartRoutes } from "./dartRouteExtractor.js";
|
|
32
33
|
import { extractSourceRoutes } from "./sourceRouteExtractor.js";
|
|
34
|
+
import { hasFlutterSdkDep } from "../prompts/test-recommendation/scopeAssessment.js";
|
|
33
35
|
import { readWorkspaceConfigRaw } from "./workspaceAuth.js";
|
|
34
36
|
// ── Strategy 1: framework route grep ────────────────────────────────────────
|
|
35
37
|
/**
|
|
@@ -228,6 +230,68 @@ export function findCandidatePagesBySourceRoute(frontendFiles, baseUrl, reposito
|
|
|
228
230
|
}
|
|
229
231
|
return Array.from(byUrl.values());
|
|
230
232
|
}
|
|
233
|
+
// ── Strategy 2.5: Dart GoRouter routes ─────────────────────────────────────
|
|
234
|
+
/**
|
|
235
|
+
* Strategy 2.5: walk the repo's Dart files for GoRoute(path: '...') calls.
|
|
236
|
+
* Gated on `hasFlutterSdkDep(repoPath)` so non-Flutter Dart projects (e.g.
|
|
237
|
+
* shelf_router servers) don't accidentally emit URLs.
|
|
238
|
+
*
|
|
239
|
+
* Diff-matching: each GoRoute references its screen widget via
|
|
240
|
+
* `builder:`/`pageBuilder:` (e.g. `AuthorsScreen`). The extractor resolves
|
|
241
|
+
* those identifiers through the route file's imports to absolute screen
|
|
242
|
+
* file paths. We surface only routes whose screen file appears in the
|
|
243
|
+
* changed-file list. Falls back to all-routes when no route's screen
|
|
244
|
+
* file matches — preserves coverage for "router untouched, screen edited"
|
|
245
|
+
* cases when the import-resolution heuristic doesn't find a match.
|
|
246
|
+
*
|
|
247
|
+
* Filters applied:
|
|
248
|
+
* 1. Drop redirect-only routes (`redirect:` with no `builder:`/`pageBuilder:`).
|
|
249
|
+
* These have no UI to render — navigating there is wasted work.
|
|
250
|
+
* 2. Drop nested relative children (paths that don't start with `/`).
|
|
251
|
+
* Without parent-path composition the URL is meaningless.
|
|
252
|
+
*
|
|
253
|
+
* Returns empty when the repo isn't Flutter or no GoRoute calls are
|
|
254
|
+
* found — caller falls through to root-fallback.
|
|
255
|
+
*/
|
|
256
|
+
export function findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendFiles) {
|
|
257
|
+
if (!hasFlutterSdkDep(repositoryPath))
|
|
258
|
+
return [];
|
|
259
|
+
const dartRoutes = extractDartRoutes(repositoryPath);
|
|
260
|
+
if (dartRoutes.length === 0)
|
|
261
|
+
return [];
|
|
262
|
+
// Strip redirect-only and relative routes — they're never valid candidates.
|
|
263
|
+
const renderable = dartRoutes.filter((r) => !r.isRedirectOnly && r.path.startsWith("/"));
|
|
264
|
+
if (renderable.length === 0)
|
|
265
|
+
return [];
|
|
266
|
+
// Diff-match each route against frontendFiles via its screenFiles.
|
|
267
|
+
const changedAbs = new Set(frontendFiles.map((f) => path.resolve(repositoryPath, f)));
|
|
268
|
+
const matched = renderable.filter((r) => r.screenFiles.some((sf) => changedAbs.has(sf)));
|
|
269
|
+
// Fall back to all renderable routes when nothing diff-matches. The
|
|
270
|
+
// import-resolution heuristic isn't perfect (custom widget naming, multiple
|
|
271
|
+
// screens per file, factory functions); a zero-match should not collapse
|
|
272
|
+
// the candidate list to empty when the repo clearly has UI changes.
|
|
273
|
+
const surfaced = matched.length > 0 ? matched : renderable;
|
|
274
|
+
const normalizedBase = baseUrl.replace(/\/$/, "");
|
|
275
|
+
const byUrl = new Map();
|
|
276
|
+
for (const route of surfaced) {
|
|
277
|
+
const fullUrl = normalizedBase + route.path;
|
|
278
|
+
const sourceFile = path.relative(repositoryPath, route.declaredIn);
|
|
279
|
+
const existing = byUrl.get(fullUrl);
|
|
280
|
+
if (existing) {
|
|
281
|
+
if (!existing.sourcedFrom.includes(sourceFile)) {
|
|
282
|
+
existing.sourcedFrom.push(sourceFile);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
byUrl.set(fullUrl, {
|
|
287
|
+
url: fullUrl,
|
|
288
|
+
sourcedFrom: [sourceFile],
|
|
289
|
+
strategy: "dart-go-router",
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return Array.from(byUrl.values());
|
|
294
|
+
}
|
|
231
295
|
// ── Strategy 3: root fallback ───────────────────────────────────────────────
|
|
232
296
|
/**
|
|
233
297
|
* Strategy 3: when strategies 1+2 yield nothing, fall back to the workspace's
|
|
@@ -314,6 +378,9 @@ export async function enumerateCandidateUiPages(repositoryPath, frontendFiles) {
|
|
|
314
378
|
const fromSource = findCandidatePagesBySourceRoute(frontendFiles, baseUrl, repositoryPath);
|
|
315
379
|
if (fromSource.length > 0)
|
|
316
380
|
return fromSource;
|
|
381
|
+
const fromDart = findCandidatePagesByDartRoute(repositoryPath, baseUrl, frontendFiles);
|
|
382
|
+
if (fromDart.length > 0)
|
|
383
|
+
return fromDart;
|
|
317
384
|
const fallback = await findRootFallbackPage(repositoryPath);
|
|
318
385
|
return fallback ? [fallback] : [];
|
|
319
386
|
}
|
|
@@ -240,6 +240,48 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
240
240
|
});
|
|
241
241
|
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8000");
|
|
242
242
|
});
|
|
243
|
+
// Flutter support — the load-bearing assumption from the Confluence plan.
|
|
244
|
+
// init_workspace writes a single service with framework: playwright for a
|
|
245
|
+
// Flutter web app. Heuristic 1 must match by serviceName suffix
|
|
246
|
+
// (e.g. "birdle-frontend") because Heuristic 2 only knows JS/TS frontend
|
|
247
|
+
// frameworks. Without this, the agent never reaches the browser regardless
|
|
248
|
+
// of the .dart classifier fix.
|
|
249
|
+
it("picks the Flutter playwright-framework service by name suffix", async () => {
|
|
250
|
+
readSpy.mockResolvedValue({
|
|
251
|
+
services: [
|
|
252
|
+
{
|
|
253
|
+
serviceName: "birdle-frontend",
|
|
254
|
+
language: "typescript",
|
|
255
|
+
framework: "playwright",
|
|
256
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
|
|
261
|
+
});
|
|
262
|
+
// Edge case: Flutter service has no "frontend"/"ui"/"app"/"web" suffix
|
|
263
|
+
// (so H1 misses) and `framework: playwright` isn't in H2's TS/JS framework
|
|
264
|
+
// allow-list. The chosen URL must come from H3 (first service with baseUrl).
|
|
265
|
+
// We add a second service to prove H3 picked the first, not H1 by accident.
|
|
266
|
+
it("falls back to first service when name+framework don't match H1 or H2", async () => {
|
|
267
|
+
readSpy.mockResolvedValue({
|
|
268
|
+
services: [
|
|
269
|
+
{
|
|
270
|
+
serviceName: "birdle",
|
|
271
|
+
language: "typescript",
|
|
272
|
+
framework: "playwright",
|
|
273
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
serviceName: "other-service",
|
|
277
|
+
language: "go",
|
|
278
|
+
framework: "gin",
|
|
279
|
+
api: { baseUrl: "http://localhost:9000" },
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
});
|
|
283
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
|
|
284
|
+
});
|
|
243
285
|
});
|
|
244
286
|
// ---------------------------------------------------------------------------
|
|
245
287
|
// findRootFallbackPage
|
|
@@ -375,6 +417,186 @@ export default function App() { return <Routes><Route path="/cart" element={<Car
|
|
|
375
417
|
});
|
|
376
418
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
377
419
|
});
|
|
420
|
+
// Flutter end-to-end: pubspec.yaml declares the Flutter SDK, .dart files
|
|
421
|
+
// appear in the diff (passed in by callers after isFrontendFile filtering),
|
|
422
|
+
// workspace.yml has a baseUrl. Strategies 1, 2, and 2.5 don't match
|
|
423
|
+
// (no framework config; TS extractor doesn't see .dart; no GoRoute calls).
|
|
424
|
+
// Result: root-fallback with the workspace baseUrl.
|
|
425
|
+
it("returns root-fallback for a Flutter repo with no GoRoute declarations", async () => {
|
|
426
|
+
readSpy.mockResolvedValue({
|
|
427
|
+
services: [
|
|
428
|
+
{
|
|
429
|
+
serviceName: "birdle-frontend",
|
|
430
|
+
framework: "playwright",
|
|
431
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
});
|
|
435
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-flutter-enum-"));
|
|
436
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: birdle\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
437
|
+
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
438
|
+
fs.writeFileSync(path.join(repo, "lib", "main.dart"), "void main() => runApp(MyApp());");
|
|
439
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
440
|
+
expect(pages).toHaveLength(1);
|
|
441
|
+
expect(pages[0]).toMatchObject({
|
|
442
|
+
url: "http://localhost:8080",
|
|
443
|
+
strategy: "root-fallback",
|
|
444
|
+
});
|
|
445
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
446
|
+
});
|
|
447
|
+
// Strategy 2.5: Flutter repo with GoRouter declarations and no diff-match
|
|
448
|
+
// (frontendFiles names a screen file that's not imported by the router)
|
|
449
|
+
// surfaces every absolute route as a candidate URL via the all-routes
|
|
450
|
+
// fallback. Diff-match narrowing is exercised by the next test.
|
|
451
|
+
it("returns dart-go-router URLs for a Flutter repo with GoRoute declarations", async () => {
|
|
452
|
+
readSpy.mockResolvedValue({
|
|
453
|
+
services: [
|
|
454
|
+
{
|
|
455
|
+
serviceName: "books-frontend",
|
|
456
|
+
framework: "playwright",
|
|
457
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
});
|
|
461
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-"));
|
|
462
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n go_router: ^14.0.0\n");
|
|
463
|
+
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
464
|
+
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
465
|
+
final router = GoRouter(routes: [
|
|
466
|
+
GoRoute(path: '/signin', builder: (c, s) => SignIn()),
|
|
467
|
+
GoRoute(path: '/authors', builder: (c, s) => Authors()),
|
|
468
|
+
GoRoute(path: '/settings', builder: (c, s) => Settings()),
|
|
469
|
+
]);
|
|
470
|
+
`);
|
|
471
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/screens/authors.dart"]);
|
|
472
|
+
const urls = pages.map((p) => p.url).sort();
|
|
473
|
+
expect(urls).toEqual([
|
|
474
|
+
"http://localhost:8080/authors",
|
|
475
|
+
"http://localhost:8080/settings",
|
|
476
|
+
"http://localhost:8080/signin",
|
|
477
|
+
]);
|
|
478
|
+
expect(pages.every((p) => p.strategy === "dart-go-router")).toBe(true);
|
|
479
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
480
|
+
});
|
|
481
|
+
// Diff-matching: Strategy 2.5 should narrow candidate URLs to only those
|
|
482
|
+
// whose screen file is in frontendFiles. The fixture has 3 routes; only
|
|
483
|
+
// /authors's screen file (lib/src/screens/authors.dart) is in the diff.
|
|
484
|
+
it("narrows GoRouter candidates to routes whose screen file is in the diff", async () => {
|
|
485
|
+
readSpy.mockResolvedValue({
|
|
486
|
+
services: [
|
|
487
|
+
{
|
|
488
|
+
serviceName: "books-frontend",
|
|
489
|
+
framework: "playwright",
|
|
490
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
});
|
|
494
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-diff-"));
|
|
495
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: books\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
496
|
+
fs.mkdirSync(path.join(repo, "lib", "src", "screens"), { recursive: true });
|
|
497
|
+
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
498
|
+
import 'src/screens/authors.dart';
|
|
499
|
+
import 'src/screens/settings.dart';
|
|
500
|
+
import 'src/screens/sign_in.dart';
|
|
501
|
+
|
|
502
|
+
final router = GoRouter(routes: [
|
|
503
|
+
GoRoute(path: '/sign_in', builder: (c, s) => SignInScreen()),
|
|
504
|
+
GoRoute(path: '/authors', builder: (c, s) => AuthorsScreen()),
|
|
505
|
+
GoRoute(path: '/settings', builder: (c, s) => SettingsScreen()),
|
|
506
|
+
]);
|
|
507
|
+
`);
|
|
508
|
+
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "authors.dart"), "// authors");
|
|
509
|
+
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "settings.dart"), "// settings");
|
|
510
|
+
fs.writeFileSync(path.join(repo, "lib", "src", "screens", "sign_in.dart"), "// signin");
|
|
511
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/src/screens/authors.dart"]);
|
|
512
|
+
const urls = pages.map((p) => p.url).sort();
|
|
513
|
+
// Only /authors should surface — its screen file is the only one in the diff.
|
|
514
|
+
expect(urls).toEqual(["http://localhost:8080/authors"]);
|
|
515
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
516
|
+
});
|
|
517
|
+
// Redirect-only routes (`redirect:` with no builder) must not surface — they
|
|
518
|
+
// have no UI to navigate to. Same fixture shape but the only diff-matching
|
|
519
|
+
// route is redirect-only, so we should fall back to the non-redirect-only
|
|
520
|
+
// routes.
|
|
521
|
+
it("filters out redirect-only GoRoutes", async () => {
|
|
522
|
+
readSpy.mockResolvedValue({
|
|
523
|
+
services: [
|
|
524
|
+
{
|
|
525
|
+
serviceName: "frontend",
|
|
526
|
+
framework: "playwright",
|
|
527
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
});
|
|
531
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-redirect-"));
|
|
532
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
533
|
+
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
534
|
+
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
535
|
+
final router = GoRouter(routes: [
|
|
536
|
+
GoRoute(path: '/', redirect: (_, _) => '/home'),
|
|
537
|
+
GoRoute(path: '/home', builder: (c, s) => Home()),
|
|
538
|
+
]);
|
|
539
|
+
`);
|
|
540
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
541
|
+
const urls = pages.map((p) => p.url).sort();
|
|
542
|
+
// / is redirect-only — must not appear. Only /home is renderable.
|
|
543
|
+
expect(urls).toEqual(["http://localhost:8080/home"]);
|
|
544
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
545
|
+
});
|
|
546
|
+
// Fallback path: when no route's screen file matches the diff (e.g. router
|
|
547
|
+
// file edited but our import-resolution heuristic missed the screen), surface
|
|
548
|
+
// ALL renderable routes rather than collapsing to empty.
|
|
549
|
+
it("falls back to all renderable routes when no screen file matches the diff", async () => {
|
|
550
|
+
readSpy.mockResolvedValue({
|
|
551
|
+
services: [
|
|
552
|
+
{
|
|
553
|
+
serviceName: "frontend",
|
|
554
|
+
framework: "playwright",
|
|
555
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
});
|
|
559
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-go-router-fallback-"));
|
|
560
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: app\ndependencies:\n flutter:\n sdk: flutter\n");
|
|
561
|
+
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
562
|
+
fs.writeFileSync(path.join(repo, "lib", "main.dart"), `
|
|
563
|
+
final router = GoRouter(routes: [
|
|
564
|
+
GoRoute(path: '/foo', builder: (c, s) => FooScreen()),
|
|
565
|
+
GoRoute(path: '/bar', builder: (c, s) => BarScreen()),
|
|
566
|
+
]);
|
|
567
|
+
`);
|
|
568
|
+
// Diff touches only main.dart (the router itself, not any screen). The
|
|
569
|
+
// import-resolution heuristic finds zero matches against frontendFiles, so
|
|
570
|
+
// we surface all renderable routes.
|
|
571
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/main.dart"]);
|
|
572
|
+
const urls = pages.map((p) => p.url).sort();
|
|
573
|
+
expect(urls).toEqual([
|
|
574
|
+
"http://localhost:8080/bar",
|
|
575
|
+
"http://localhost:8080/foo",
|
|
576
|
+
]);
|
|
577
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
578
|
+
});
|
|
579
|
+
// Negative gate: pure-Dart server with a `GoRoute(`-shaped string in source
|
|
580
|
+
// (e.g. shelf_router uses similar names in tests) must NOT trigger Strategy
|
|
581
|
+
// 2.5 — pubspec.yaml lacks `sdk: flutter`, hasFlutterSdkDep returns false.
|
|
582
|
+
it("does not invoke Dart extractor when pubspec.yaml lacks sdk: flutter", async () => {
|
|
583
|
+
readSpy.mockResolvedValue({
|
|
584
|
+
services: [
|
|
585
|
+
{
|
|
586
|
+
serviceName: "server-frontend",
|
|
587
|
+
framework: "playwright",
|
|
588
|
+
api: { baseUrl: "http://localhost:8080" },
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
});
|
|
592
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-dart-server-"));
|
|
593
|
+
fs.writeFileSync(path.join(repo, "pubspec.yaml"), "name: server\ndependencies:\n shelf: ^1.0.0\n");
|
|
594
|
+
fs.mkdirSync(path.join(repo, "lib"), { recursive: true });
|
|
595
|
+
fs.writeFileSync(path.join(repo, "lib", "routes.dart"), `GoRoute(path: '/api/v1', handler: (req) => Response.ok(''))`);
|
|
596
|
+
const pages = await enumerateCandidateUiPages(repo, ["lib/routes.dart"]);
|
|
597
|
+
expect(pages.map((p) => p.strategy)).toEqual(["root-fallback"]);
|
|
598
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
599
|
+
});
|
|
378
600
|
});
|
|
379
601
|
// ---------------------------------------------------------------------------
|
|
380
602
|
// detectsFilesystemRouting
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var mouseActions_exports = {};
|
|
20
|
+
__export(mouseActions_exports, {
|
|
21
|
+
DEFAULT_DRAG_STEPS: () => DEFAULT_DRAG_STEPS,
|
|
22
|
+
decodeModifierKeys: () => decodeModifierKeys,
|
|
23
|
+
decomposeDrag: () => decomposeDrag,
|
|
24
|
+
mouseActionToJsonl: () => mouseActionToJsonl,
|
|
25
|
+
mouseJsonlToCode: () => mouseJsonlToCode
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(mouseActions_exports);
|
|
28
|
+
const DEFAULT_DRAG_STEPS = 10;
|
|
29
|
+
function decodeModifierKeys(mask, platform = typeof process !== "undefined" ? process.platform : "linux") {
|
|
30
|
+
if (!mask)
|
|
31
|
+
return [];
|
|
32
|
+
const keys = [];
|
|
33
|
+
if (mask & 1) keys.push("Alt");
|
|
34
|
+
if ((mask & 6) === 6) {
|
|
35
|
+
keys.push(platform === "darwin" ? "Meta" : "Control");
|
|
36
|
+
} else {
|
|
37
|
+
if (mask & 2) keys.push("Control");
|
|
38
|
+
if (mask & 4) keys.push("Meta");
|
|
39
|
+
}
|
|
40
|
+
if (mask & 8) keys.push("Shift");
|
|
41
|
+
return keys;
|
|
42
|
+
}
|
|
43
|
+
function decomposeDrag(start, end, steps = DEFAULT_DRAG_STEPS) {
|
|
44
|
+
return [
|
|
45
|
+
{ name: "mouse.move", position: start },
|
|
46
|
+
{ name: "mouse.down" },
|
|
47
|
+
{ name: "mouse.move", position: end, steps },
|
|
48
|
+
{ name: "mouse.up" }
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
function mouseActionToJsonl(params) {
|
|
52
|
+
const button = params.button ?? "left";
|
|
53
|
+
const modifiers = params.modifiers ?? 0;
|
|
54
|
+
switch (params.action) {
|
|
55
|
+
case "move": {
|
|
56
|
+
if (params.x === void 0 || params.y === void 0)
|
|
57
|
+
return { error: 'mouse action "move" requires x and y.' };
|
|
58
|
+
const move = params.steps !== void 0 ? { name: "mouse.move", position: { x: params.x, y: params.y }, steps: params.steps } : { name: "mouse.move", position: { x: params.x, y: params.y } };
|
|
59
|
+
return { actions: [move] };
|
|
60
|
+
}
|
|
61
|
+
case "down":
|
|
62
|
+
return { actions: [button === "left" ? { name: "mouse.down" } : { name: "mouse.down", button }] };
|
|
63
|
+
case "up":
|
|
64
|
+
return { actions: [button === "left" ? { name: "mouse.up" } : { name: "mouse.up", button }] };
|
|
65
|
+
case "wheel": {
|
|
66
|
+
if (params.deltaX === void 0 && params.deltaY === void 0)
|
|
67
|
+
return { error: 'mouse action "wheel" requires deltaX and/or deltaY.' };
|
|
68
|
+
if (params.x === void 0 || params.y === void 0)
|
|
69
|
+
return { error: 'mouse action "wheel" requires x and y (the scroll target) so the pointer is moved there before scrolling.' };
|
|
70
|
+
const position = { x: params.x, y: params.y };
|
|
71
|
+
return { actions: [
|
|
72
|
+
{ name: "mouse.move", position },
|
|
73
|
+
{ name: "mouse.wheel", position, deltaX: params.deltaX ?? 0, deltaY: params.deltaY ?? 0, modifiers }
|
|
74
|
+
] };
|
|
75
|
+
}
|
|
76
|
+
case "click": {
|
|
77
|
+
if (params.x === void 0 || params.y === void 0)
|
|
78
|
+
return { error: 'mouse action "click" requires x and y.' };
|
|
79
|
+
return { actions: [{ name: "click", selector: "body", position: { x: params.x, y: params.y }, button, modifiers, clickCount: params.clickCount ?? 1 }] };
|
|
80
|
+
}
|
|
81
|
+
case "drag": {
|
|
82
|
+
if (params.x === void 0 || params.y === void 0 || params.endX === void 0 || params.endY === void 0)
|
|
83
|
+
return { error: 'mouse action "drag" requires x, y, endX and endY.' };
|
|
84
|
+
return { actions: decomposeDrag({ x: params.x, y: params.y }, { x: params.endX, y: params.endY }, params.steps) };
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return { error: `Unknown mouse action: ${params.action}` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function mouseJsonlToCode(a) {
|
|
91
|
+
switch (a.name) {
|
|
92
|
+
case "mouse.move":
|
|
93
|
+
return [a.steps !== void 0 ? `await page.mouse.move(${a.position.x}, ${a.position.y}, { steps: ${a.steps} });` : `await page.mouse.move(${a.position.x}, ${a.position.y});`];
|
|
94
|
+
case "mouse.down":
|
|
95
|
+
return [a.button && a.button !== "left" ? `await page.mouse.down({ button: '${a.button}' });` : `await page.mouse.down();`];
|
|
96
|
+
case "mouse.up":
|
|
97
|
+
return [a.button && a.button !== "left" ? `await page.mouse.up({ button: '${a.button}' });` : `await page.mouse.up();`];
|
|
98
|
+
case "mouse.wheel":
|
|
99
|
+
return withModifierLines(a.modifiers, `await page.mouse.wheel(${a.deltaX}, ${a.deltaY});`);
|
|
100
|
+
case "click": {
|
|
101
|
+
const opts = a.button !== "left" || a.clickCount !== 1 ? `, { button: '${a.button}', clickCount: ${a.clickCount} }` : "";
|
|
102
|
+
return withModifierLines(a.modifiers, `await page.mouse.click(${a.position.x}, ${a.position.y}${opts});`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function withModifierLines(mask, line) {
|
|
107
|
+
const keys = decodeModifierKeys(mask);
|
|
108
|
+
if (!keys.length)
|
|
109
|
+
return [line];
|
|
110
|
+
return [
|
|
111
|
+
...keys.map((k) => `await page.keyboard.down('${k}');`),
|
|
112
|
+
line,
|
|
113
|
+
...[...keys].reverse().map((k) => `await page.keyboard.up('${k}');`)
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
117
|
+
0 && (module.exports = {
|
|
118
|
+
DEFAULT_DRAG_STEPS,
|
|
119
|
+
decodeModifierKeys,
|
|
120
|
+
decomposeDrag,
|
|
121
|
+
mouseActionToJsonl,
|
|
122
|
+
mouseJsonlToCode
|
|
123
|
+
});
|
|
@@ -20,17 +20,27 @@ var skyramp_exports = {};
|
|
|
20
20
|
__export(skyramp_exports, {
|
|
21
21
|
ARGS_ONLY_TOOLS: () => import_types.ARGS_ONLY_TOOLS,
|
|
22
22
|
TraceRecordingBackend: () => import_traceRecordingBackend.TraceRecordingBackend,
|
|
23
|
+
actionsFromPath: () => import_skyRampImport.actionsFromPath,
|
|
23
24
|
buildJsonlContent: () => import_skyRampExport.buildJsonlContent,
|
|
25
|
+
loadTraceMcpTool: () => import_loadTraceTool.loadTraceMcpTool,
|
|
26
|
+
loadTraceSchema: () => import_loadTraceTool.loadTraceSchema,
|
|
27
|
+
parseJsonl: () => import_skyRampImport.parseJsonl,
|
|
24
28
|
writeSkyrampZip: () => import_skyRampExport.writeSkyrampZip
|
|
25
29
|
});
|
|
26
30
|
module.exports = __toCommonJS(skyramp_exports);
|
|
27
31
|
var import_traceRecordingBackend = require("./traceRecordingBackend");
|
|
28
32
|
var import_types = require("./types");
|
|
29
33
|
var import_skyRampExport = require("../test/skyRampExport");
|
|
34
|
+
var import_loadTraceTool = require("./loadTraceTool");
|
|
35
|
+
var import_skyRampImport = require("./skyRampImport");
|
|
30
36
|
// Annotate the CommonJS export names for ESM import in node:
|
|
31
37
|
0 && (module.exports = {
|
|
32
38
|
ARGS_ONLY_TOOLS,
|
|
33
39
|
TraceRecordingBackend,
|
|
40
|
+
actionsFromPath,
|
|
34
41
|
buildJsonlContent,
|
|
42
|
+
loadTraceMcpTool,
|
|
43
|
+
loadTraceSchema,
|
|
44
|
+
parseJsonl,
|
|
35
45
|
writeSkyrampZip
|
|
36
46
|
});
|