framewatch-mcp-server 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +895 -15
- package/dist/constants.d.ts +274 -0
- package/dist/constants.js +279 -0
- package/dist/constants.js.map +1 -1
- package/dist/engine/browser.d.ts +20 -4
- package/dist/engine/browser.js +26 -9
- package/dist/engine/browser.js.map +1 -1
- package/dist/engine/clicks.d.ts +221 -0
- package/dist/engine/clicks.js +801 -0
- package/dist/engine/clicks.js.map +1 -0
- package/dist/engine/forms.d.ts +137 -0
- package/dist/engine/forms.js +474 -0
- package/dist/engine/forms.js.map +1 -0
- package/dist/engine/hmr.d.ts +41 -0
- package/dist/engine/hmr.js +91 -0
- package/dist/engine/hmr.js.map +1 -0
- package/dist/engine/inspect.d.ts +31 -0
- package/dist/engine/inspect.js +383 -0
- package/dist/engine/inspect.js.map +1 -0
- package/dist/engine/interaction.d.ts +12 -7
- package/dist/engine/interaction.js +110 -18
- package/dist/engine/interaction.js.map +1 -1
- package/dist/engine/links.d.ts +134 -0
- package/dist/engine/links.js +384 -0
- package/dist/engine/links.js.map +1 -0
- package/dist/engine/mocks.d.ts +53 -0
- package/dist/engine/mocks.js +148 -0
- package/dist/engine/mocks.js.map +1 -0
- package/dist/engine/rtl.d.ts +129 -0
- package/dist/engine/rtl.js +540 -0
- package/dist/engine/rtl.js.map +1 -0
- package/dist/engine/seo.d.ts +189 -0
- package/dist/engine/seo.js +398 -0
- package/dist/engine/seo.js.map +1 -0
- package/dist/engine/snapshot.d.ts +29 -0
- package/dist/engine/snapshot.js +10 -0
- package/dist/engine/snapshot.js.map +1 -0
- package/dist/engine/vue.d.ts +54 -0
- package/dist/engine/vue.js +419 -0
- package/dist/engine/vue.js.map +1 -0
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/accessibility.d.ts +4 -0
- package/dist/tools/accessibility.js +9 -2
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/api-mock.d.ts +405 -0
- package/dist/tools/api-mock.js +186 -0
- package/dist/tools/api-mock.js.map +1 -0
- package/dist/tools/capture.d.ts +90 -26
- package/dist/tools/capture.js +109 -58
- package/dist/tools/capture.js.map +1 -1
- package/dist/tools/compare.d.ts +4 -0
- package/dist/tools/compare.js +16 -5
- package/dist/tools/compare.js.map +1 -1
- package/dist/tools/dead-clicks.d.ts +128 -0
- package/dist/tools/dead-clicks.js +570 -0
- package/dist/tools/dead-clicks.js.map +1 -0
- package/dist/tools/form-test.d.ts +112 -0
- package/dist/tools/form-test.js +477 -0
- package/dist/tools/form-test.js.map +1 -0
- package/dist/tools/index.d.ts +17 -1
- package/dist/tools/index.js +45 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspect.d.ts +78 -0
- package/dist/tools/inspect.js +136 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/interact.d.ts +37 -18
- package/dist/tools/interact.js +113 -13
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/links.d.ts +129 -0
- package/dist/tools/links.js +640 -0
- package/dist/tools/links.js.map +1 -0
- package/dist/tools/responsive.d.ts +10 -6
- package/dist/tools/responsive.js +21 -4
- package/dist/tools/responsive.js.map +1 -1
- package/dist/tools/rtl.d.ts +241 -0
- package/dist/tools/rtl.js +410 -0
- package/dist/tools/rtl.js.map +1 -0
- package/dist/tools/save-auth.d.ts +263 -0
- package/dist/tools/save-auth.js +253 -0
- package/dist/tools/save-auth.js.map +1 -0
- package/dist/tools/screenshot.d.ts +4 -0
- package/dist/tools/screenshot.js +15 -4
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/seo.d.ts +113 -0
- package/dist/tools/seo.js +281 -0
- package/dist/tools/seo.js.map +1 -0
- package/dist/tools/snapshot.d.ts +122 -0
- package/dist/tools/snapshot.js +183 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/dist/tools/wait-for.d.ts +107 -0
- package/dist/tools/wait-for.js +167 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/utils/arabic-text.d.ts +14 -0
- package/dist/utils/arabic-text.js +193 -0
- package/dist/utils/arabic-text.js.map +1 -0
- package/dist/utils/budget.d.ts +41 -0
- package/dist/utils/budget.js +182 -0
- package/dist/utils/budget.js.map +1 -0
- package/dist/utils/format.d.ts +11 -1
- package/dist/utils/format.js +27 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/highlight.d.ts +69 -0
- package/dist/utils/highlight.js +181 -0
- package/dist/utils/highlight.js.map +1 -0
- package/dist/utils/link-rules.d.ts +100 -0
- package/dist/utils/link-rules.js +284 -0
- package/dist/utils/link-rules.js.map +1 -0
- package/dist/utils/mock-rules.d.ts +144 -0
- package/dist/utils/mock-rules.js +224 -0
- package/dist/utils/mock-rules.js.map +1 -0
- package/dist/utils/rtl-rules.d.ts +142 -0
- package/dist/utils/rtl-rules.js +296 -0
- package/dist/utils/rtl-rules.js.map +1 -0
- package/dist/utils/seo-rules.d.ts +129 -0
- package/dist/utils/seo-rules.js +726 -0
- package/dist/utils/seo-rules.js.map +1 -0
- package/dist/utils/snapshot-rules.d.ts +33 -0
- package/dist/utils/snapshot-rules.js +111 -0
- package/dist/utils/snapshot-rules.js.map +1 -0
- package/dist/utils/storage-state.d.ts +76 -0
- package/dist/utils/storage-state.js +195 -0
- package/dist/utils/storage-state.js.map +1 -0
- package/dist/utils/style-rules.d.ts +107 -0
- package/dist/utils/style-rules.js +223 -0
- package/dist/utils/style-rules.js.map +1 -0
- package/dist/utils/test-data.d.ts +75 -0
- package/dist/utils/test-data.js +294 -0
- package/dist/utils/test-data.js.map +1 -0
- package/dist/utils/vue-rules.d.ts +72 -0
- package/dist/utils/vue-rules.js +108 -0
- package/dist/utils/vue-rules.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
/**
|
|
3
|
+
* The page as a tree of named elements.
|
|
4
|
+
*
|
|
5
|
+
* Playwright's AI-mode aria snapshot gives every visible element a `[ref=eN]`
|
|
6
|
+
* that `aria-ref=eN` resolves back to, so an agent that has read the tree
|
|
7
|
+
* can act on `e8` instead of guessing a selector from a screenshot. The refs
|
|
8
|
+
* belong to the page: they are assigned when the snapshot is taken and stay
|
|
9
|
+
* valid until the DOM changes, which is why this runs on the long-lived
|
|
10
|
+
* interact session rather than on a throwaway page.
|
|
11
|
+
*/
|
|
12
|
+
export interface SnapshotOptions {
|
|
13
|
+
/** Only this container. Omit for the whole document. */
|
|
14
|
+
selector?: string;
|
|
15
|
+
/** `full` keeps the tree; `interactive` flattens it to what a person can act on. */
|
|
16
|
+
mode: "full" | "interactive";
|
|
17
|
+
/** Cut the text past this many characters, on a line boundary. */
|
|
18
|
+
max_chars: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PageSnapshot {
|
|
21
|
+
text: string;
|
|
22
|
+
/** Elements with a ref, before any cut. */
|
|
23
|
+
elements: number;
|
|
24
|
+
/** Of those, the ones a person can act on. */
|
|
25
|
+
interactive: number;
|
|
26
|
+
/** Lines dropped to fit `max_chars`. */
|
|
27
|
+
cut_lines: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function takeSnapshot(page: Page, options: SnapshotOptions): Promise<PageSnapshot>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { countSnapshot, interactiveOnly, truncateSnapshot } from "../utils/snapshot-rules.js";
|
|
2
|
+
export async function takeSnapshot(page, options) {
|
|
3
|
+
const root = options.selector ? page.locator(options.selector).first() : page.locator(":root");
|
|
4
|
+
const tree = await root.ariaSnapshot({ mode: "ai" });
|
|
5
|
+
const counts = countSnapshot(tree);
|
|
6
|
+
const text = options.mode === "interactive" ? interactiveOnly(tree) : tree;
|
|
7
|
+
const cut = truncateSnapshot(text, options.max_chars);
|
|
8
|
+
return { text: cut.text, elements: counts.elements, interactive: counts.interactive, cut_lines: cut.cut_lines };
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/engine/snapshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAgC9F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAU,EAAE,OAAwB;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;AAClH,CAAC","sourcesContent":["import type { Page } from \"playwright\";\nimport { countSnapshot, interactiveOnly, truncateSnapshot } from \"../utils/snapshot-rules.js\";\n\n/**\n * The page as a tree of named elements.\n *\n * Playwright's AI-mode aria snapshot gives every visible element a `[ref=eN]`\n * that `aria-ref=eN` resolves back to, so an agent that has read the tree\n * can act on `e8` instead of guessing a selector from a screenshot. The refs\n * belong to the page: they are assigned when the snapshot is taken and stay\n * valid until the DOM changes, which is why this runs on the long-lived\n * interact session rather than on a throwaway page.\n */\n\nexport interface SnapshotOptions {\n /** Only this container. Omit for the whole document. */\n selector?: string;\n /** `full` keeps the tree; `interactive` flattens it to what a person can act on. */\n mode: \"full\" | \"interactive\";\n /** Cut the text past this many characters, on a line boundary. */\n max_chars: number;\n}\n\nexport interface PageSnapshot {\n text: string;\n /** Elements with a ref, before any cut. */\n elements: number;\n /** Of those, the ones a person can act on. */\n interactive: number;\n /** Lines dropped to fit `max_chars`. */\n cut_lines: number;\n}\n\nexport async function takeSnapshot(page: Page, options: SnapshotOptions): Promise<PageSnapshot> {\n const root = options.selector ? page.locator(options.selector).first() : page.locator(\":root\");\n const tree = await root.ariaSnapshot({ mode: \"ai\" });\n const counts = countSnapshot(tree);\n const text = options.mode === \"interactive\" ? interactiveOnly(tree) : tree;\n const cut = truncateSnapshot(text, options.max_chars);\n return { text: cut.text, elements: counts.elements, interactive: counts.interactive, cut_lines: cut.cut_lines };\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Locator, Page } from "playwright";
|
|
2
|
+
import type { ComponentInfo, ComponentNode, ComponentUnavailable, VueInfo } from "../utils/vue-rules.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a Vue app on the page is willing to tell us.
|
|
5
|
+
*
|
|
6
|
+
* Vue 3 leaves two handles behind in the DOM: the app on its container
|
|
7
|
+
* (`__vue_app__`, and `data-v-app` on the same element), and on every element
|
|
8
|
+
* a dev build rendered, the component instance that rendered it
|
|
9
|
+
* (`__vueParentComponent`). From those come the version, the router and its
|
|
10
|
+
* current route, the component name, props and state behind any element, the
|
|
11
|
+
* whole component tree, and a way to navigate through the router instead of
|
|
12
|
+
* the address bar. Production builds keep only the app handle, and say so.
|
|
13
|
+
*
|
|
14
|
+
* Everything in-page below is serialised into Chromium: it may not close over
|
|
15
|
+
* a module value (CLAUDE.md, decision 50), so every cap is passed in.
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectVue(page: Page): Promise<VueInfo | null>;
|
|
18
|
+
export interface ReadyOptions {
|
|
19
|
+
/** How long to keep looking for a mounted app. */
|
|
20
|
+
detect_ms: number;
|
|
21
|
+
/** Once found, how long to give the router to be ready. */
|
|
22
|
+
ready_ms: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ReadyResult {
|
|
25
|
+
vue: VueInfo | null;
|
|
26
|
+
/** Wall time spent, whichever way it ended. */
|
|
27
|
+
waited_ms: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Wait for a Vue app to be mounted and its router (if any) to have resolved
|
|
31
|
+
* its first navigation, then two frames so the first render is painted.
|
|
32
|
+
* Returns as soon as that is true; a page with no Vue is given `detect_ms`
|
|
33
|
+
* and then handed back untouched — no slower than the sleep it replaces.
|
|
34
|
+
*/
|
|
35
|
+
export declare function waitForVueReady(page: Page, options: ReadyOptions): Promise<ReadyResult>;
|
|
36
|
+
/** The component that rendered the element `locator` points at, or why there is none. */
|
|
37
|
+
export declare function componentOf(locator: Locator): Promise<ComponentInfo | ComponentUnavailable | null>;
|
|
38
|
+
export interface ComponentTree {
|
|
39
|
+
root: ComponentNode;
|
|
40
|
+
total: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function componentTree(page: Page): Promise<ComponentTree | null>;
|
|
43
|
+
export type RouterOutcome = {
|
|
44
|
+
ok: true;
|
|
45
|
+
route: {
|
|
46
|
+
path: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
/** `router.push(path)`, awaited, on the app's own router. */
|
|
54
|
+
export declare function routerNavigate(page: Page, path: string): Promise<RouterOutcome>;
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { MAX_COMPONENT_ENTRIES, MAX_COMPONENT_TREE_NODES, MAX_COMPONENT_VALUE_LENGTH } from "../constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* What a Vue app on the page is willing to tell us.
|
|
4
|
+
*
|
|
5
|
+
* Vue 3 leaves two handles behind in the DOM: the app on its container
|
|
6
|
+
* (`__vue_app__`, and `data-v-app` on the same element), and on every element
|
|
7
|
+
* a dev build rendered, the component instance that rendered it
|
|
8
|
+
* (`__vueParentComponent`). From those come the version, the router and its
|
|
9
|
+
* current route, the component name, props and state behind any element, the
|
|
10
|
+
* whole component tree, and a way to navigate through the router instead of
|
|
11
|
+
* the address bar. Production builds keep only the app handle, and say so.
|
|
12
|
+
*
|
|
13
|
+
* Everything in-page below is serialised into Chromium: it may not close over
|
|
14
|
+
* a module value (CLAUDE.md, decision 50), so every cap is passed in.
|
|
15
|
+
*/
|
|
16
|
+
export async function detectVue(page) {
|
|
17
|
+
try {
|
|
18
|
+
return await page.evaluate(detectInPage);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Wait for a Vue app to be mounted and its router (if any) to have resolved
|
|
26
|
+
* its first navigation, then two frames so the first render is painted.
|
|
27
|
+
* Returns as soon as that is true; a page with no Vue is given `detect_ms`
|
|
28
|
+
* and then handed back untouched — no slower than the sleep it replaces.
|
|
29
|
+
*/
|
|
30
|
+
export async function waitForVueReady(page, options) {
|
|
31
|
+
const started = Date.now();
|
|
32
|
+
const deadline = started + Math.max(0, options.detect_ms);
|
|
33
|
+
let vue = null;
|
|
34
|
+
for (;;) {
|
|
35
|
+
vue = await detectVue(page);
|
|
36
|
+
if (vue !== null || Date.now() >= deadline)
|
|
37
|
+
break;
|
|
38
|
+
await page.waitForTimeout(Math.min(50, Math.max(1, deadline - Date.now())));
|
|
39
|
+
}
|
|
40
|
+
if (vue !== null) {
|
|
41
|
+
await page.evaluate(readyInPage, { timeout_ms: options.ready_ms }).catch(() => undefined);
|
|
42
|
+
vue = (await detectVue(page)) ?? vue;
|
|
43
|
+
}
|
|
44
|
+
return { vue, waited_ms: Date.now() - started };
|
|
45
|
+
}
|
|
46
|
+
/** The component that rendered the element `locator` points at, or why there is none. */
|
|
47
|
+
export async function componentOf(locator) {
|
|
48
|
+
try {
|
|
49
|
+
return await locator.evaluate(componentInPage, { max_value: MAX_COMPONENT_VALUE_LENGTH, max_entries: MAX_COMPONENT_ENTRIES });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function componentTree(page) {
|
|
56
|
+
try {
|
|
57
|
+
return await page.evaluate(treeInPage, {
|
|
58
|
+
max_nodes: MAX_COMPONENT_TREE_NODES,
|
|
59
|
+
builtins: ["RouterView", "RouterLink", "Transition", "TransitionGroup", "KeepAlive", "BaseTransition", "Teleport", "Suspense"],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** `router.push(path)`, awaited, on the app's own router. */
|
|
67
|
+
export async function routerNavigate(page, path) {
|
|
68
|
+
try {
|
|
69
|
+
return await page.evaluate(routerPushInPage, { path });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return { ok: false, error: (error instanceof Error ? error.message : String(error)).split("\n")[0] };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/* ── In-page ──────────────────────────────────────────────────────────────
|
|
76
|
+
* Each function below is serialised whole into Chromium, so each carries its
|
|
77
|
+
* own copy of `findApp`: a shared module-level helper would be a
|
|
78
|
+
* ReferenceError in the page (CLAUDE.md, decision 50).
|
|
79
|
+
*/
|
|
80
|
+
function detectInPage() {
|
|
81
|
+
const doc = globalThis.document;
|
|
82
|
+
if (!doc)
|
|
83
|
+
return null;
|
|
84
|
+
const findApp = () => {
|
|
85
|
+
const doc = globalThis.document;
|
|
86
|
+
if (!doc)
|
|
87
|
+
return null;
|
|
88
|
+
const marked = doc.querySelector("[data-v-app]");
|
|
89
|
+
if (marked && marked.__vue_app__)
|
|
90
|
+
return { app: marked.__vue_app__, root: marked };
|
|
91
|
+
const all = doc.querySelectorAll("*");
|
|
92
|
+
for (let i = 0; i < all.length; i++) {
|
|
93
|
+
if (all[i].__vue_app__)
|
|
94
|
+
return { app: all[i].__vue_app__, root: all[i] };
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
};
|
|
98
|
+
const found = findApp();
|
|
99
|
+
if (found) {
|
|
100
|
+
const { app, root } = found;
|
|
101
|
+
// A dev build keeps the root instance on the app; a production build does
|
|
102
|
+
// not, but both stamp `data-v-app` on the container at mount.
|
|
103
|
+
const instance = app._instance;
|
|
104
|
+
const mounted = instance ? instance.isMounted === true : root.hasAttribute("data-v-app");
|
|
105
|
+
if (!mounted)
|
|
106
|
+
return null;
|
|
107
|
+
const props = app.config && app.config.globalProperties;
|
|
108
|
+
const router = props && props.$router;
|
|
109
|
+
let route;
|
|
110
|
+
if (router && router.currentRoute && router.currentRoute.value) {
|
|
111
|
+
const current = router.currentRoute.value;
|
|
112
|
+
route = { path: String(current.fullPath) };
|
|
113
|
+
if (current.name !== undefined && current.name !== null)
|
|
114
|
+
route.name = String(current.name);
|
|
115
|
+
}
|
|
116
|
+
// A dev build stamps every rendered element with its component; a
|
|
117
|
+
// production build stamps none. Look at what the app rendered.
|
|
118
|
+
let production = true;
|
|
119
|
+
const rendered = root.querySelectorAll("*");
|
|
120
|
+
for (let i = 0; i < rendered.length && i < 50; i++) {
|
|
121
|
+
if ("__vueParentComponent" in rendered[i]) {
|
|
122
|
+
production = false;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (rendered.length === 0)
|
|
127
|
+
production = !("__vueParentComponent" in root);
|
|
128
|
+
const version = String(app.version || "3");
|
|
129
|
+
const out = { version, major: Number(version.split(".")[0]) || 3, router: !!router, production };
|
|
130
|
+
if (route)
|
|
131
|
+
out.route = route;
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
// Vue 2 leaves `__vue__` on component root elements.
|
|
135
|
+
const all = doc.querySelectorAll("*");
|
|
136
|
+
for (let i = 0; i < all.length; i++) {
|
|
137
|
+
const vm = all[i].__vue__;
|
|
138
|
+
if (!vm || !vm.$root)
|
|
139
|
+
continue;
|
|
140
|
+
const ctor = vm.$root.constructor;
|
|
141
|
+
const version = String((ctor && ctor.version) || (globalThis.Vue && globalThis.Vue.version) || "2");
|
|
142
|
+
const router = vm.$root.$router;
|
|
143
|
+
const out = { version, major: 2, router: !!router, production: false };
|
|
144
|
+
if (router && vm.$root.$route) {
|
|
145
|
+
out.route = { path: String(vm.$root.$route.fullPath) };
|
|
146
|
+
if (vm.$root.$route.name)
|
|
147
|
+
out.route.name = String(vm.$root.$route.name);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
async function readyInPage(config) {
|
|
154
|
+
const findApp = () => {
|
|
155
|
+
const doc = globalThis.document;
|
|
156
|
+
if (!doc)
|
|
157
|
+
return null;
|
|
158
|
+
const marked = doc.querySelector("[data-v-app]");
|
|
159
|
+
if (marked && marked.__vue_app__)
|
|
160
|
+
return { app: marked.__vue_app__, root: marked };
|
|
161
|
+
const all = doc.querySelectorAll("*");
|
|
162
|
+
for (let i = 0; i < all.length; i++) {
|
|
163
|
+
if (all[i].__vue_app__)
|
|
164
|
+
return { app: all[i].__vue_app__, root: all[i] };
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
};
|
|
168
|
+
const found = findApp();
|
|
169
|
+
if (!found)
|
|
170
|
+
return false;
|
|
171
|
+
const props = found.app.config && found.app.config.globalProperties;
|
|
172
|
+
const router = props && props.$router;
|
|
173
|
+
let ready = true;
|
|
174
|
+
if (router && typeof router.isReady === "function") {
|
|
175
|
+
ready = await Promise.race([
|
|
176
|
+
router.isReady().then(() => true, () => false),
|
|
177
|
+
new Promise((resolve) => setTimeout(() => resolve(false), config.timeout_ms)),
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
180
|
+
const raf = globalThis.requestAnimationFrame;
|
|
181
|
+
if (typeof raf === "function") {
|
|
182
|
+
await new Promise((resolve) => raf(() => raf(() => resolve())));
|
|
183
|
+
}
|
|
184
|
+
return ready;
|
|
185
|
+
}
|
|
186
|
+
function componentInPage(element, config) {
|
|
187
|
+
const findApp = () => {
|
|
188
|
+
const doc = globalThis.document;
|
|
189
|
+
if (!doc)
|
|
190
|
+
return null;
|
|
191
|
+
const marked = doc.querySelector("[data-v-app]");
|
|
192
|
+
if (marked && marked.__vue_app__)
|
|
193
|
+
return { app: marked.__vue_app__, root: marked };
|
|
194
|
+
const all = doc.querySelectorAll("*");
|
|
195
|
+
for (let i = 0; i < all.length; i++) {
|
|
196
|
+
if (all[i].__vue_app__)
|
|
197
|
+
return { app: all[i].__vue_app__, root: all[i] };
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
};
|
|
201
|
+
const serialise = (value) => {
|
|
202
|
+
if (value === null)
|
|
203
|
+
return "null";
|
|
204
|
+
if (value === undefined)
|
|
205
|
+
return "undefined";
|
|
206
|
+
const type = typeof value;
|
|
207
|
+
if (type === "string") {
|
|
208
|
+
const quoted = JSON.stringify(value);
|
|
209
|
+
return quoted.length > config.max_value + 2 ? JSON.stringify(value.slice(0, config.max_value)) + "…" : quoted;
|
|
210
|
+
}
|
|
211
|
+
if (type === "number" || type === "boolean" || type === "bigint")
|
|
212
|
+
return String(value);
|
|
213
|
+
if (type === "function")
|
|
214
|
+
return "fn";
|
|
215
|
+
if (type === "symbol")
|
|
216
|
+
return "symbol";
|
|
217
|
+
if (Array.isArray(value))
|
|
218
|
+
return "[" + value.length + "]";
|
|
219
|
+
if (value instanceof Date)
|
|
220
|
+
return value.toISOString();
|
|
221
|
+
if (value && typeof value === "object" && "__v_isRef" in value)
|
|
222
|
+
return serialise(value.value);
|
|
223
|
+
try {
|
|
224
|
+
const keys = Object.keys(value);
|
|
225
|
+
const shown = keys.slice(0, 4).join(", ");
|
|
226
|
+
return "{" + shown + (keys.length > 4 ? ", …" : "") + "}";
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return "{…}";
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const entries = (source, skip) => {
|
|
233
|
+
const out = {};
|
|
234
|
+
if (!source || typeof source !== "object")
|
|
235
|
+
return out;
|
|
236
|
+
let keys = [];
|
|
237
|
+
try {
|
|
238
|
+
keys = Object.keys(source);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
let count = 0;
|
|
244
|
+
for (const key of keys) {
|
|
245
|
+
if (skip && skip(key))
|
|
246
|
+
continue;
|
|
247
|
+
if (count >= config.max_entries) {
|
|
248
|
+
out["…"] = "+" + (keys.length - count) + " more";
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
let value;
|
|
252
|
+
try {
|
|
253
|
+
value = source[key];
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
value = undefined;
|
|
257
|
+
}
|
|
258
|
+
out[key] = serialise(value);
|
|
259
|
+
count++;
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
};
|
|
263
|
+
const nameOf = (instance) => {
|
|
264
|
+
const type = instance && instance.type;
|
|
265
|
+
if (!type)
|
|
266
|
+
return "Anonymous";
|
|
267
|
+
if (type.name)
|
|
268
|
+
return String(type.name);
|
|
269
|
+
if (type.__name)
|
|
270
|
+
return String(type.__name);
|
|
271
|
+
if (type.__file)
|
|
272
|
+
return String(type.__file).split("/").pop().replace(/\.vue$/, "");
|
|
273
|
+
return instance.parent === null ? "App" : "Anonymous";
|
|
274
|
+
};
|
|
275
|
+
// Vue 3 dev build: the nearest element that knows its component.
|
|
276
|
+
let node = element;
|
|
277
|
+
while (node && node.nodeType === 1) {
|
|
278
|
+
if ("__vueParentComponent" in node && node.__vueParentComponent) {
|
|
279
|
+
const instance = node.__vueParentComponent;
|
|
280
|
+
const path = [];
|
|
281
|
+
let parent = instance.parent;
|
|
282
|
+
while (parent) {
|
|
283
|
+
path.push(nameOf(parent));
|
|
284
|
+
parent = parent.parent;
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
name: nameOf(instance),
|
|
288
|
+
props: entries(instance.props),
|
|
289
|
+
state: {
|
|
290
|
+
...entries(instance.setupState, (key) => key.startsWith("_") || key.startsWith("$")),
|
|
291
|
+
...entries(instance.data, (key) => key.startsWith("_") || key.startsWith("$")),
|
|
292
|
+
},
|
|
293
|
+
path,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
node = node.parentElement;
|
|
297
|
+
}
|
|
298
|
+
// No component data. Say which kind of nothing this is.
|
|
299
|
+
node = element;
|
|
300
|
+
while (node && node.nodeType === 1) {
|
|
301
|
+
if (node.__vue__)
|
|
302
|
+
return { unavailable: "Vue 2 — component details need Vue 3" };
|
|
303
|
+
if (node.__vue_app__)
|
|
304
|
+
return { unavailable: "production build — no component data on elements" };
|
|
305
|
+
node = node.parentElement;
|
|
306
|
+
}
|
|
307
|
+
const found = findApp();
|
|
308
|
+
if (found && found.root.contains(element))
|
|
309
|
+
return { unavailable: "production build — no component data on elements" };
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
function treeInPage(config) {
|
|
313
|
+
const findApp = () => {
|
|
314
|
+
const doc = globalThis.document;
|
|
315
|
+
if (!doc)
|
|
316
|
+
return null;
|
|
317
|
+
const marked = doc.querySelector("[data-v-app]");
|
|
318
|
+
if (marked && marked.__vue_app__)
|
|
319
|
+
return { app: marked.__vue_app__, root: marked };
|
|
320
|
+
const all = doc.querySelectorAll("*");
|
|
321
|
+
for (let i = 0; i < all.length; i++) {
|
|
322
|
+
if (all[i].__vue_app__)
|
|
323
|
+
return { app: all[i].__vue_app__, root: all[i] };
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
327
|
+
const found = findApp();
|
|
328
|
+
if (!found || !found.app._instance)
|
|
329
|
+
return null;
|
|
330
|
+
const builtins = new Set(config.builtins);
|
|
331
|
+
let total = 0;
|
|
332
|
+
const nameOf = (instance) => {
|
|
333
|
+
const type = instance.type || {};
|
|
334
|
+
if (type.name)
|
|
335
|
+
return String(type.name);
|
|
336
|
+
if (type.__name)
|
|
337
|
+
return String(type.__name);
|
|
338
|
+
if (type.__file)
|
|
339
|
+
return String(type.__file).split("/").pop().replace(/\.vue$/, "");
|
|
340
|
+
return instance.parent === null ? "App" : "Anonymous";
|
|
341
|
+
};
|
|
342
|
+
const visit = (vnode, into) => {
|
|
343
|
+
if (!vnode || total >= config.max_nodes)
|
|
344
|
+
return;
|
|
345
|
+
if (vnode.component) {
|
|
346
|
+
const instance = vnode.component;
|
|
347
|
+
const name = nameOf(instance);
|
|
348
|
+
if (builtins.has(name)) {
|
|
349
|
+
visit(instance.subTree, into);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const node = { name, children: [] };
|
|
353
|
+
total++;
|
|
354
|
+
into.push(node);
|
|
355
|
+
visit(instance.subTree, node.children);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (vnode.suspense) {
|
|
359
|
+
visit(vnode.suspense.activeBranch, into);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (Array.isArray(vnode.children)) {
|
|
363
|
+
for (const child of vnode.children)
|
|
364
|
+
visit(child, into);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const rootInstance = found.app._instance;
|
|
368
|
+
const root = { name: nameOf(rootInstance), children: [] };
|
|
369
|
+
total = 1;
|
|
370
|
+
visit(rootInstance.subTree, root.children);
|
|
371
|
+
return { root, total };
|
|
372
|
+
}
|
|
373
|
+
async function routerPushInPage(config) {
|
|
374
|
+
const findApp = () => {
|
|
375
|
+
const doc = globalThis.document;
|
|
376
|
+
if (!doc)
|
|
377
|
+
return null;
|
|
378
|
+
const marked = doc.querySelector("[data-v-app]");
|
|
379
|
+
if (marked && marked.__vue_app__)
|
|
380
|
+
return { app: marked.__vue_app__, root: marked };
|
|
381
|
+
const all = doc.querySelectorAll("*");
|
|
382
|
+
for (let i = 0; i < all.length; i++) {
|
|
383
|
+
if (all[i].__vue_app__)
|
|
384
|
+
return { app: all[i].__vue_app__, root: all[i] };
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
};
|
|
388
|
+
const found = findApp();
|
|
389
|
+
if (!found)
|
|
390
|
+
return { ok: false, error: "no Vue app on this page" };
|
|
391
|
+
const props = found.app.config && found.app.config.globalProperties;
|
|
392
|
+
const router = props && props.$router;
|
|
393
|
+
if (!router || typeof router.push !== "function")
|
|
394
|
+
return { ok: false, error: "no vue-router on this app" };
|
|
395
|
+
try {
|
|
396
|
+
if (typeof router.resolve === "function") {
|
|
397
|
+
const resolved = router.resolve(config.path);
|
|
398
|
+
if (!resolved || !resolved.matched || resolved.matched.length === 0) {
|
|
399
|
+
return { ok: false, error: "no route matched " + config.path };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const failure = await router.push(config.path);
|
|
403
|
+
if (failure) {
|
|
404
|
+
return { ok: false, error: "navigation " + (failure.type === 8 ? "cancelled" : failure.type === 16 ? "duplicated" : "aborted") + (failure.message ? ": " + failure.message : "") };
|
|
405
|
+
}
|
|
406
|
+
const raf = globalThis.requestAnimationFrame;
|
|
407
|
+
if (typeof raf === "function")
|
|
408
|
+
await new Promise((resolve) => raf(() => raf(() => resolve())));
|
|
409
|
+
const current = router.currentRoute && router.currentRoute.value;
|
|
410
|
+
const route = { path: current ? String(current.fullPath) : config.path };
|
|
411
|
+
if (current && current.name !== undefined && current.name !== null)
|
|
412
|
+
route.name = String(current.name);
|
|
413
|
+
return { ok: true, route };
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.js","sourceRoot":"","sources":["../../src/engine/vue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Locator, Page } from \"playwright\";\nimport { MAX_COMPONENT_ENTRIES, MAX_COMPONENT_TREE_NODES, MAX_COMPONENT_VALUE_LENGTH } from \"../constants.js\";\nimport type { ComponentInfo, ComponentNode, ComponentUnavailable, VueInfo } from \"../utils/vue-rules.js\";\n\n/**\n * What a Vue app on the page is willing to tell us.\n *\n * Vue 3 leaves two handles behind in the DOM: the app on its container\n * (`__vue_app__`, and `data-v-app` on the same element), and on every element\n * a dev build rendered, the component instance that rendered it\n * (`__vueParentComponent`). From those come the version, the router and its\n * current route, the component name, props and state behind any element, the\n * whole component tree, and a way to navigate through the router instead of\n * the address bar. Production builds keep only the app handle, and say so.\n *\n * Everything in-page below is serialised into Chromium: it may not close over\n * a module value (CLAUDE.md, decision 50), so every cap is passed in.\n */\n\nexport async function detectVue(page: Page): Promise<VueInfo | null> {\n try {\n return await page.evaluate(detectInPage);\n } catch {\n return null;\n }\n}\n\nexport interface ReadyOptions {\n /** How long to keep looking for a mounted app. */\n detect_ms: number;\n /** Once found, how long to give the router to be ready. */\n ready_ms: number;\n}\n\nexport interface ReadyResult {\n vue: VueInfo | null;\n /** Wall time spent, whichever way it ended. */\n waited_ms: number;\n}\n\n/**\n * Wait for a Vue app to be mounted and its router (if any) to have resolved\n * its first navigation, then two frames so the first render is painted.\n * Returns as soon as that is true; a page with no Vue is given `detect_ms`\n * and then handed back untouched — no slower than the sleep it replaces.\n */\nexport async function waitForVueReady(page: Page, options: ReadyOptions): Promise<ReadyResult> {\n const started = Date.now();\n const deadline = started + Math.max(0, options.detect_ms);\n let vue: VueInfo | null = null;\n for (;;) {\n vue = await detectVue(page);\n if (vue !== null || Date.now() >= deadline) break;\n await page.waitForTimeout(Math.min(50, Math.max(1, deadline - Date.now())));\n }\n if (vue !== null) {\n await page.evaluate(readyInPage, { timeout_ms: options.ready_ms }).catch(() => undefined);\n vue = (await detectVue(page)) ?? vue;\n }\n return { vue, waited_ms: Date.now() - started };\n}\n\n/** The component that rendered the element `locator` points at, or why there is none. */\nexport async function componentOf(locator: Locator): Promise<ComponentInfo | ComponentUnavailable | null> {\n try {\n return await locator.evaluate(componentInPage, { max_value: MAX_COMPONENT_VALUE_LENGTH, max_entries: MAX_COMPONENT_ENTRIES });\n } catch {\n return null;\n }\n}\n\nexport interface ComponentTree {\n root: ComponentNode;\n total: number;\n}\n\nexport async function componentTree(page: Page): Promise<ComponentTree | null> {\n try {\n return await page.evaluate(treeInPage, {\n max_nodes: MAX_COMPONENT_TREE_NODES,\n builtins: [\"RouterView\", \"RouterLink\", \"Transition\", \"TransitionGroup\", \"KeepAlive\", \"BaseTransition\", \"Teleport\", \"Suspense\"],\n });\n } catch {\n return null;\n }\n}\n\nexport type RouterOutcome = { ok: true; route: { path: string; name?: string } } | { ok: false; error: string };\n\n/** `router.push(path)`, awaited, on the app's own router. */\nexport async function routerNavigate(page: Page, path: string): Promise<RouterOutcome> {\n try {\n return await page.evaluate(routerPushInPage, { path });\n } catch (error) {\n return { ok: false, error: (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0] };\n }\n}\n\n/* ── In-page ──────────────────────────────────────────────────────────────\n * Each function below is serialised whole into Chromium, so each carries its\n * own copy of `findApp`: a shared module-level helper would be a\n * ReferenceError in the page (CLAUDE.md, decision 50).\n */\n\nfunction detectInPage(): VueInfo | null {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const findApp = (): { app: any; root: any } | null => {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const marked = doc.querySelector(\"[data-v-app]\");\n if (marked && marked.__vue_app__) return { app: marked.__vue_app__, root: marked };\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n if (all[i].__vue_app__) return { app: all[i].__vue_app__, root: all[i] };\n }\n return null;\n };\n\n const found = findApp();\n if (found) {\n const { app, root } = found;\n // A dev build keeps the root instance on the app; a production build does\n // not, but both stamp `data-v-app` on the container at mount.\n const instance = app._instance;\n const mounted = instance ? instance.isMounted === true : root.hasAttribute(\"data-v-app\");\n if (!mounted) return null;\n const props = app.config && app.config.globalProperties;\n const router = props && props.$router;\n let route: { path: string; name?: string } | undefined;\n if (router && router.currentRoute && router.currentRoute.value) {\n const current = router.currentRoute.value;\n route = { path: String(current.fullPath) };\n if (current.name !== undefined && current.name !== null) route.name = String(current.name);\n }\n // A dev build stamps every rendered element with its component; a\n // production build stamps none. Look at what the app rendered.\n let production = true;\n const rendered = root.querySelectorAll(\"*\");\n for (let i = 0; i < rendered.length && i < 50; i++) {\n if (\"__vueParentComponent\" in rendered[i]) {\n production = false;\n break;\n }\n }\n if (rendered.length === 0) production = !(\"__vueParentComponent\" in root);\n const version = String(app.version || \"3\");\n const out: VueInfo = { version, major: Number(version.split(\".\")[0]) || 3, router: !!router, production };\n if (route) out.route = route;\n return out;\n }\n\n // Vue 2 leaves `__vue__` on component root elements.\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n const vm = all[i].__vue__;\n if (!vm || !vm.$root) continue;\n const ctor = vm.$root.constructor;\n const version = String((ctor && ctor.version) || ((globalThis as any).Vue && (globalThis as any).Vue.version) || \"2\");\n const router = vm.$root.$router;\n const out: VueInfo = { version, major: 2, router: !!router, production: false };\n if (router && vm.$root.$route) {\n out.route = { path: String(vm.$root.$route.fullPath) };\n if (vm.$root.$route.name) out.route.name = String(vm.$root.$route.name);\n }\n return out;\n }\n return null;\n}\n\nasync function readyInPage(config: { timeout_ms: number }): Promise<boolean> {\n const findApp = (): { app: any; root: any } | null => {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const marked = doc.querySelector(\"[data-v-app]\");\n if (marked && marked.__vue_app__) return { app: marked.__vue_app__, root: marked };\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n if (all[i].__vue_app__) return { app: all[i].__vue_app__, root: all[i] };\n }\n return null;\n };\n const found = findApp();\n if (!found) return false;\n const props = found.app.config && found.app.config.globalProperties;\n const router = props && props.$router;\n let ready = true;\n if (router && typeof router.isReady === \"function\") {\n ready = await Promise.race<boolean>([\n router.isReady().then(() => true, () => false),\n new Promise<boolean>((resolve) => setTimeout(() => resolve(false), config.timeout_ms)),\n ]);\n }\n const raf = (globalThis as any).requestAnimationFrame;\n if (typeof raf === \"function\") {\n await new Promise<void>((resolve) => raf(() => raf(() => resolve())));\n }\n return ready;\n}\n\nfunction componentInPage(element: any, config: { max_value: number; max_entries: number }): ComponentInfo | ComponentUnavailable | null {\n const findApp = (): { app: any; root: any } | null => {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const marked = doc.querySelector(\"[data-v-app]\");\n if (marked && marked.__vue_app__) return { app: marked.__vue_app__, root: marked };\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n if (all[i].__vue_app__) return { app: all[i].__vue_app__, root: all[i] };\n }\n return null;\n };\n const serialise = (value: any): string => {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n const type = typeof value;\n if (type === \"string\") {\n const quoted = JSON.stringify(value);\n return quoted.length > config.max_value + 2 ? JSON.stringify(value.slice(0, config.max_value)) + \"…\" : quoted;\n }\n if (type === \"number\" || type === \"boolean\" || type === \"bigint\") return String(value);\n if (type === \"function\") return \"fn\";\n if (type === \"symbol\") return \"symbol\";\n if (Array.isArray(value)) return \"[\" + value.length + \"]\";\n if (value instanceof Date) return value.toISOString();\n if (value && typeof value === \"object\" && \"__v_isRef\" in value) return serialise(value.value);\n try {\n const keys = Object.keys(value);\n const shown = keys.slice(0, 4).join(\", \");\n return \"{\" + shown + (keys.length > 4 ? \", …\" : \"\") + \"}\";\n } catch {\n return \"{…}\";\n }\n };\n const entries = (source: any, skip?: (key: string) => boolean): Record<string, string> => {\n const out: Record<string, string> = {};\n if (!source || typeof source !== \"object\") return out;\n let keys: string[] = [];\n try {\n keys = Object.keys(source);\n } catch {\n return out;\n }\n let count = 0;\n for (const key of keys) {\n if (skip && skip(key)) continue;\n if (count >= config.max_entries) {\n out[\"…\"] = \"+\" + (keys.length - count) + \" more\";\n break;\n }\n let value: any;\n try {\n value = source[key];\n } catch {\n value = undefined;\n }\n out[key] = serialise(value);\n count++;\n }\n return out;\n };\n const nameOf = (instance: any): string => {\n const type = instance && instance.type;\n if (!type) return \"Anonymous\";\n if (type.name) return String(type.name);\n if (type.__name) return String(type.__name);\n if (type.__file) return String(type.__file).split(\"/\").pop()!.replace(/\\.vue$/, \"\");\n return instance.parent === null ? \"App\" : \"Anonymous\";\n };\n\n // Vue 3 dev build: the nearest element that knows its component.\n let node: any = element;\n while (node && node.nodeType === 1) {\n if (\"__vueParentComponent\" in node && node.__vueParentComponent) {\n const instance = node.__vueParentComponent;\n const path: string[] = [];\n let parent = instance.parent;\n while (parent) {\n path.push(nameOf(parent));\n parent = parent.parent;\n }\n return {\n name: nameOf(instance),\n props: entries(instance.props),\n state: {\n ...entries(instance.setupState, (key) => key.startsWith(\"_\") || key.startsWith(\"$\")),\n ...entries(instance.data, (key) => key.startsWith(\"_\") || key.startsWith(\"$\")),\n },\n path,\n };\n }\n node = node.parentElement;\n }\n\n // No component data. Say which kind of nothing this is.\n node = element;\n while (node && node.nodeType === 1) {\n if (node.__vue__) return { unavailable: \"Vue 2 — component details need Vue 3\" };\n if (node.__vue_app__) return { unavailable: \"production build — no component data on elements\" };\n node = node.parentElement;\n }\n const found = findApp();\n if (found && found.root.contains(element)) return { unavailable: \"production build — no component data on elements\" };\n return null;\n}\n\nfunction treeInPage(config: { max_nodes: number; builtins: string[] }): { root: ComponentNode; total: number } | null {\n const findApp = (): { app: any; root: any } | null => {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const marked = doc.querySelector(\"[data-v-app]\");\n if (marked && marked.__vue_app__) return { app: marked.__vue_app__, root: marked };\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n if (all[i].__vue_app__) return { app: all[i].__vue_app__, root: all[i] };\n }\n return null;\n };\n const found = findApp();\n if (!found || !found.app._instance) return null;\n const builtins = new Set(config.builtins);\n let total = 0;\n const nameOf = (instance: any): string => {\n const type = instance.type || {};\n if (type.name) return String(type.name);\n if (type.__name) return String(type.__name);\n if (type.__file) return String(type.__file).split(\"/\").pop()!.replace(/\\.vue$/, \"\");\n return instance.parent === null ? \"App\" : \"Anonymous\";\n };\n const visit = (vnode: any, into: ComponentNode[]): void => {\n if (!vnode || total >= config.max_nodes) return;\n if (vnode.component) {\n const instance = vnode.component;\n const name = nameOf(instance);\n if (builtins.has(name)) {\n visit(instance.subTree, into);\n return;\n }\n const node: ComponentNode = { name, children: [] };\n total++;\n into.push(node);\n visit(instance.subTree, node.children);\n return;\n }\n if (vnode.suspense) {\n visit(vnode.suspense.activeBranch, into);\n return;\n }\n if (Array.isArray(vnode.children)) {\n for (const child of vnode.children) visit(child, into);\n }\n };\n const rootInstance = found.app._instance;\n const root: ComponentNode = { name: nameOf(rootInstance), children: [] };\n total = 1;\n visit(rootInstance.subTree, root.children);\n return { root, total };\n}\n\nasync function routerPushInPage(config: { path: string }): Promise<RouterOutcome> {\n const findApp = (): { app: any; root: any } | null => {\n const doc = (globalThis as any).document;\n if (!doc) return null;\n const marked = doc.querySelector(\"[data-v-app]\");\n if (marked && marked.__vue_app__) return { app: marked.__vue_app__, root: marked };\n const all = doc.querySelectorAll(\"*\");\n for (let i = 0; i < all.length; i++) {\n if (all[i].__vue_app__) return { app: all[i].__vue_app__, root: all[i] };\n }\n return null;\n };\n const found = findApp();\n if (!found) return { ok: false, error: \"no Vue app on this page\" };\n const props = found.app.config && found.app.config.globalProperties;\n const router = props && props.$router;\n if (!router || typeof router.push !== \"function\") return { ok: false, error: \"no vue-router on this app\" };\n try {\n if (typeof router.resolve === \"function\") {\n const resolved = router.resolve(config.path);\n if (!resolved || !resolved.matched || resolved.matched.length === 0) {\n return { ok: false, error: \"no route matched \" + config.path };\n }\n }\n const failure = await router.push(config.path);\n if (failure) {\n return { ok: false, error: \"navigation \" + (failure.type === 8 ? \"cancelled\" : failure.type === 16 ? \"duplicated\" : \"aborted\") + (failure.message ? \": \" + failure.message : \"\") };\n }\n const raf = (globalThis as any).requestAnimationFrame;\n if (typeof raf === \"function\") await new Promise<void>((resolve) => raf(() => raf(() => resolve())));\n const current = router.currentRoute && router.currentRoute.value;\n const route: { path: string; name?: string } = { path: current ? String(current.fullPath) : config.path };\n if (current && current.name !== undefined && current.name !== null) route.name = String(current.name);\n return { ok: true, route };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -25,13 +25,57 @@ export function createServer() {
|
|
|
25
25
|
"Pass `interactions` to replay a click/type/scroll/tap/swipe script while it records — that is how you " +
|
|
26
26
|
"test a flow such as a login. Use framewatch_interact for one action at a time: it keeps the page open " +
|
|
27
27
|
"between calls, so you can click, look at the result, type, and look again without replaying the flow. " +
|
|
28
|
+
"Before acting, use framewatch_snapshot to read that page as a tree of named elements — every field, button " +
|
|
29
|
+
"and link with its accessible name and a ref like `e8` — and pass the ref as `ref` to framewatch_interact " +
|
|
30
|
+
"instead of guessing a selector from a screenshot (mode `interactive` lists only what can be acted on; " +
|
|
31
|
+
"`include_snapshot` on interact returns fresh refs after each action). When you are building or checking UI, " +
|
|
32
|
+
"use framewatch_inspect on refs or selectors to measure how elements are really built: box in viewport px, " +
|
|
33
|
+
"font, text and background colour with the WCAG contrast ratio, padding, margin, gap, border, radius, " +
|
|
34
|
+
"clipping and overflow, and alignment to parent and previous sibling, with each target boxed on a " +
|
|
35
|
+
"screenshot; call it with no targets for a design inventory of the page (fonts, sizes, colours, spacing " +
|
|
36
|
+
"and radii in use, with counts) to spot the odd one out. On a Vue app, inspect also names the component " +
|
|
37
|
+
"behind each element with its props and state, snapshot's `include_components` returns the component tree, " +
|
|
38
|
+
"and every session tool reads the page the moment the app is mounted and its router ready instead of " +
|
|
39
|
+
"sleeping. After you save a file, use framewatch_wait_for (`until: hot_update`) to return the moment Vite " +
|
|
40
|
+
"has patched the open page — no reload, no replayed flow — and `navigate` goes through vue-router when the " +
|
|
41
|
+
"app has one, keeping its state. Every tool that returns frames states the viewport " +
|
|
42
|
+
"and image scale: coordinates and regions are always in viewport px, not image px. " +
|
|
28
43
|
"Both capture and interact can attach the context behind a frame — console output and uncaught errors " +
|
|
29
44
|
"(on by default), plus network requests, DOM mutations and paint timing via `include_network`, " +
|
|
30
45
|
"`include_dom` and `include_performance` — which is how you find out why a frame looks wrong. " +
|
|
31
46
|
"Use framewatch_responsive to see one page at mobile, tablet and desktop widths at once (it also " +
|
|
32
47
|
"reports content that overflows its viewport), framewatch_accessibility to run an axe-core WCAG audit, " +
|
|
33
48
|
"and framewatch_compare to diff two URLs — or the page interact has open against a URL — with an " +
|
|
34
|
-
"overlay of every pixel that differs.
|
|
49
|
+
"overlay of every pixel that differs. Use framewatch_form_test on anything with fields in it: it fills " +
|
|
50
|
+
"every form with valid data, with nothing, with special characters, with Arabic and with harmless XSS " +
|
|
51
|
+
"payloads, one fresh page per strategy, and reports what each one produced — the validation shown, the " +
|
|
52
|
+
"requests made, the console output, the values silently truncated. Use framewatch_seo to audit what a search " +
|
|
53
|
+
"engine and a link preview make of a page: title and description with their lengths, canonical, robots " +
|
|
54
|
+
"directives and robots.txt, Open Graph tags with the share image fetched and measured, the heading outline, " +
|
|
55
|
+
"images with no alt text, and JSON-LD structured data — all read from the rendered DOM, so a client-rendered " +
|
|
56
|
+
"app is measured properly. Use framewatch_dead_clicks to find the controls that look clickable and do " +
|
|
57
|
+
"nothing: it clicks every link, button, click role and pointer-cursor element on the page and reports the " +
|
|
58
|
+
"ones where absolutely nothing happened — no navigation, no DOM change, no request, no console output — " +
|
|
59
|
+
"plus the ones whose handler threw, with a screenshot of them boxed in red. It really does press every " +
|
|
60
|
+
"button, so pass `exclude` or `selector` to keep it away from anything destructive. " +
|
|
61
|
+
"Use framewatch_links to check where every link on a page actually goes: 404s, server errors, redirect " +
|
|
62
|
+
"chains, redirects that end on an error page, and `#fragment` links that point at no element. It reuses " +
|
|
63
|
+
"the browser's own result for the images, scripts and stylesheets the page already loaded, retries a " +
|
|
64
|
+
"refused HEAD as a GET before calling anything broken, and reports a 401/403/429 as a refused check " +
|
|
65
|
+
"rather than a dead link. Raise `depth` to follow the site's internal links a level at a time. " +
|
|
66
|
+
"Use framewatch_api_mock to answer the page's own API calls yourself and record what it does with the " +
|
|
67
|
+
"answer: the empty list, the 500, the 401, the response that takes five seconds, the body that is not " +
|
|
68
|
+
"valid JSON, the request that fails outright — one word each via `scenario`, or set the status, body, " +
|
|
69
|
+
"headers and delay by hand. It returns the same diff cards as framewatch_capture, plus a report of " +
|
|
70
|
+
"what each mock actually served, including any that matched nothing — which is how you find out a " +
|
|
71
|
+
"pattern was wrong instead of trusting a page that looked fine. " +
|
|
72
|
+
"Use framewatch_rtl to test a page in LTR and RTL and get back only what failed to mirror — a box that " +
|
|
73
|
+
"did not move, text that stayed left-aligned, padding that did not swap, an icon that did not flip — with " +
|
|
74
|
+
"both screenshots and every finding boxed. " +
|
|
75
|
+
"For an app behind a login, run the flow once with " +
|
|
76
|
+
"framewatch_save_auth and pass the file it writes as `storage_state` to any of these tools — they then " +
|
|
77
|
+
"open the page already signed in instead of replaying the login every time. " +
|
|
78
|
+
"If the app is not running yet, framewatch_start_server runs its " +
|
|
35
79
|
"dev server (e.g. `npm run dev`) and waits for the port; framewatch_stop_server stops it again.",
|
|
36
80
|
});
|
|
37
81
|
registerAllTools(server);
|