@treelocator/runtime 0.1.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/.eslintrc +3 -0
- package/.turbo/turbo-build.log +30 -0
- package/.turbo/turbo-test.log +18 -0
- package/.turbo/turbo-ts.log +4 -0
- package/LICENSE +22 -0
- package/babel.config.js +14 -0
- package/dist/_generated_styles.d.ts +2 -0
- package/dist/_generated_styles.js +1734 -0
- package/dist/_generated_tree_icon.d.ts +2 -0
- package/dist/_generated_tree_icon.js +2 -0
- package/dist/adapters/HtmlElementTreeNode.d.ts +16 -0
- package/dist/adapters/HtmlElementTreeNode.js +43 -0
- package/dist/adapters/adapterApi.d.ts +30 -0
- package/dist/adapters/adapterApi.js +1 -0
- package/dist/adapters/createTreeNode.d.ts +2 -0
- package/dist/adapters/createTreeNode.js +17 -0
- package/dist/adapters/getElementInfo.d.ts +2 -0
- package/dist/adapters/getElementInfo.js +19 -0
- package/dist/adapters/getParentsPath.d.ts +2 -0
- package/dist/adapters/getParentsPath.js +35 -0
- package/dist/adapters/getTree.d.ts +1 -0
- package/dist/adapters/getTree.js +35 -0
- package/dist/adapters/goUpByTheTree.d.ts +7 -0
- package/dist/adapters/goUpByTheTree.js +22 -0
- package/dist/adapters/jsx/getExpressionData.d.ts +2 -0
- package/dist/adapters/jsx/getExpressionData.js +44 -0
- package/dist/adapters/jsx/getJSXComponentBoundingBox.d.ts +5 -0
- package/dist/adapters/jsx/getJSXComponentBoundingBox.js +46 -0
- package/dist/adapters/jsx/jsxAdapter.d.ts +11 -0
- package/dist/adapters/jsx/jsxAdapter.js +208 -0
- package/dist/adapters/jsx/runtimeStore.d.ts +10 -0
- package/dist/adapters/jsx/runtimeStore.js +87 -0
- package/dist/adapters/react/fiberToSimple.d.ts +3 -0
- package/dist/adapters/react/fiberToSimple.js +55 -0
- package/dist/adapters/react/findDebugSource.d.ts +5 -0
- package/dist/adapters/react/findDebugSource.js +13 -0
- package/dist/adapters/react/findFiberByHtmlElement.d.ts +2 -0
- package/dist/adapters/react/findFiberByHtmlElement.js +22 -0
- package/dist/adapters/react/gatherFiberRoots.d.ts +2 -0
- package/dist/adapters/react/gatherFiberRoots.js +29 -0
- package/dist/adapters/react/getAllFiberChildren.d.ts +2 -0
- package/dist/adapters/react/getAllFiberChildren.js +9 -0
- package/dist/adapters/react/getAllParentsElementsAndRootComponent.d.ts +8 -0
- package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +34 -0
- package/dist/adapters/react/getAllWrappingParents.d.ts +2 -0
- package/dist/adapters/react/getAllWrappingParents.js +19 -0
- package/dist/adapters/react/getFiberComponentBoundingBox.d.ts +3 -0
- package/dist/adapters/react/getFiberComponentBoundingBox.js +27 -0
- package/dist/adapters/react/getFiberLabel.d.ts +3 -0
- package/dist/adapters/react/getFiberLabel.js +14 -0
- package/dist/adapters/react/getFiberOwnBoundingBox.d.ts +3 -0
- package/dist/adapters/react/getFiberOwnBoundingBox.js +6 -0
- package/dist/adapters/react/isStyled.d.ts +2 -0
- package/dist/adapters/react/isStyled.js +3 -0
- package/dist/adapters/react/makeFiberId.d.ts +2 -0
- package/dist/adapters/react/makeFiberId.js +16 -0
- package/dist/adapters/react/reactAdapter.d.ts +11 -0
- package/dist/adapters/react/reactAdapter.js +114 -0
- package/dist/adapters/react/searchDevtoolsRenderersForClosestTarget.d.ts +1 -0
- package/dist/adapters/react/searchDevtoolsRenderersForClosestTarget.js +11 -0
- package/dist/adapters/svelte/svelteAdapter.d.ts +22 -0
- package/dist/adapters/svelte/svelteAdapter.js +88 -0
- package/dist/adapters/vue/getVNodeBoundingBox.d.ts +4 -0
- package/dist/adapters/vue/getVNodeBoundingBox.js +31 -0
- package/dist/adapters/vue/vueAdapter.d.ts +15 -0
- package/dist/adapters/vue/vueAdapter.js +113 -0
- package/dist/browserApi.d.ts +103 -0
- package/dist/browserApi.js +160 -0
- package/dist/components/Button.d.ts +4 -0
- package/dist/components/Button.js +17 -0
- package/dist/components/ComponentOutline.d.ts +7 -0
- package/dist/components/ComponentOutline.js +93 -0
- package/dist/components/MaybeOutline.d.ts +7 -0
- package/dist/components/MaybeOutline.js +43 -0
- package/dist/components/Outline.d.ts +25 -0
- package/dist/components/Outline.js +135 -0
- package/dist/components/RenderBoxes.d.ts +4 -0
- package/dist/components/RenderBoxes.js +73 -0
- package/dist/components/Runtime.d.ts +3 -0
- package/dist/components/Runtime.js +188 -0
- package/dist/components/SimpleNodeOutline.d.ts +4 -0
- package/dist/components/SimpleNodeOutline.js +47 -0
- package/dist/components/Toast.d.ts +4 -0
- package/dist/components/Toast.js +68 -0
- package/dist/components/Tooltip.d.ts +5 -0
- package/dist/components/Tooltip.js +21 -0
- package/dist/consts.d.ts +6 -0
- package/dist/consts.js +5 -0
- package/dist/functions/cropPath.d.ts +1 -0
- package/dist/functions/cropPath.js +9 -0
- package/dist/functions/cropPath.test.d.ts +1 -0
- package/dist/functions/cropPath.test.js +16 -0
- package/dist/functions/deduplicateLabels.d.ts +2 -0
- package/dist/functions/deduplicateLabels.js +12 -0
- package/dist/functions/evalTemplate.d.ts +3 -0
- package/dist/functions/evalTemplate.js +7 -0
- package/dist/functions/evalTemplate.test.d.ts +1 -0
- package/dist/functions/evalTemplate.test.js +11 -0
- package/dist/functions/findNames.d.ts +5 -0
- package/dist/functions/findNames.js +15 -0
- package/dist/functions/formatAncestryChain.d.ts +9 -0
- package/dist/functions/formatAncestryChain.js +56 -0
- package/dist/functions/getBoundingRect.d.ts +1 -0
- package/dist/functions/getBoundingRect.js +11 -0
- package/dist/functions/getComposedBoundingBox.d.ts +2 -0
- package/dist/functions/getComposedBoundingBox.js +20 -0
- package/dist/functions/getIdsOnPathToRoot.d.ts +3 -0
- package/dist/functions/getIdsOnPathToRoot.js +15 -0
- package/dist/functions/getMultipleElementsBoundingBox.d.ts +2 -0
- package/dist/functions/getMultipleElementsBoundingBox.js +20 -0
- package/dist/functions/getPathToParent.d.ts +1 -0
- package/dist/functions/getPathToParent.js +15 -0
- package/dist/functions/getReferenceId.d.ts +1 -0
- package/dist/functions/getReferenceId.js +9 -0
- package/dist/functions/getUsableFileName.d.ts +1 -0
- package/dist/functions/getUsableFileName.js +17 -0
- package/dist/functions/getUsableFileName.test.d.ts +1 -0
- package/dist/functions/getUsableFileName.test.js +16 -0
- package/dist/functions/getUsableName.d.ts +2 -0
- package/dist/functions/getUsableName.js +47 -0
- package/dist/functions/isCombinationModifiersPressed.d.ts +4 -0
- package/dist/functions/isCombinationModifiersPressed.js +16 -0
- package/dist/functions/isLocatorsOwnElement.d.ts +1 -0
- package/dist/functions/isLocatorsOwnElement.js +3 -0
- package/dist/functions/mergeRects.d.ts +2 -0
- package/dist/functions/mergeRects.js +10 -0
- package/dist/functions/mergeRects.test.d.ts +1 -0
- package/dist/functions/mergeRects.test.js +23 -0
- package/dist/functions/nonNullable.d.ts +1 -0
- package/dist/functions/nonNullable.js +3 -0
- package/dist/functions/parseDataId.d.ts +3 -0
- package/dist/functions/parseDataId.js +44 -0
- package/dist/functions/transformPath.d.ts +1 -0
- package/dist/functions/transformPath.js +7 -0
- package/dist/functions/transformPath.test.d.ts +1 -0
- package/dist/functions/transformPath.test.js +16 -0
- package/dist/global.d.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/dist/initRuntime.d.ts +8 -0
- package/dist/initRuntime.js +80 -0
- package/dist/output.css +1733 -0
- package/dist/types/LabelData.d.ts +5 -0
- package/dist/types/LabelData.js +1 -0
- package/dist/types/TreeNode.d.ts +19 -0
- package/dist/types/TreeNode.js +1 -0
- package/dist/types/types.d.ts +53 -0
- package/dist/types/types.js +1 -0
- package/jest.config.ts +195 -0
- package/package.json +75 -0
- package/scripts/wrapCSS.js +26 -0
- package/scripts/wrapImage.js +24 -0
- package/src/_generated_styles.ts +1734 -0
- package/src/_generated_tree_icon.ts +2 -0
- package/src/adapters/HtmlElementTreeNode.ts +51 -0
- package/src/adapters/adapterApi.ts +35 -0
- package/src/adapters/createTreeNode.ts +25 -0
- package/src/adapters/getElementInfo.tsx +27 -0
- package/src/adapters/getParentsPath.tsx +49 -0
- package/src/adapters/getTree.tsx +45 -0
- package/src/adapters/goUpByTheTree.ts +20 -0
- package/src/adapters/jsx/getExpressionData.ts +47 -0
- package/src/adapters/jsx/getJSXComponentBoundingBox.ts +63 -0
- package/src/adapters/jsx/jsxAdapter.ts +276 -0
- package/src/adapters/jsx/runtimeStore.ts +94 -0
- package/src/adapters/react/fiberToSimple.tsx +72 -0
- package/src/adapters/react/findDebugSource.ts +15 -0
- package/src/adapters/react/findFiberByHtmlElement.ts +27 -0
- package/src/adapters/react/gatherFiberRoots.tsx +36 -0
- package/src/adapters/react/getAllFiberChildren.tsx +11 -0
- package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +52 -0
- package/src/adapters/react/getAllWrappingParents.ts +25 -0
- package/src/adapters/react/getFiberComponentBoundingBox.ts +30 -0
- package/src/adapters/react/getFiberLabel.ts +20 -0
- package/src/adapters/react/getFiberOwnBoundingBox.ts +9 -0
- package/src/adapters/react/isStyled.ts +5 -0
- package/src/adapters/react/makeFiberId.tsx +19 -0
- package/src/adapters/react/reactAdapter.ts +148 -0
- package/src/adapters/react/searchDevtoolsRenderersForClosestTarget.ts +15 -0
- package/src/adapters/svelte/svelteAdapter.ts +111 -0
- package/src/adapters/vue/getVNodeBoundingBox.tsx +42 -0
- package/src/adapters/vue/vueAdapter.ts +139 -0
- package/src/assets/tree-icon.png +0 -0
- package/src/browserApi.ts +288 -0
- package/src/components/Button.tsx +14 -0
- package/src/components/ComponentOutline.tsx +98 -0
- package/src/components/MaybeOutline.tsx +49 -0
- package/src/components/Outline.tsx +153 -0
- package/src/components/RenderBoxes.tsx +57 -0
- package/src/components/Runtime.tsx +246 -0
- package/src/components/SimpleNodeOutline.tsx +27 -0
- package/src/components/Toast.tsx +83 -0
- package/src/components/Tooltip.tsx +28 -0
- package/src/consts.ts +7 -0
- package/src/functions/cropPath.test.ts +18 -0
- package/src/functions/cropPath.ts +12 -0
- package/src/functions/deduplicateLabels.ts +16 -0
- package/src/functions/evalTemplate.test.ts +12 -0
- package/src/functions/evalTemplate.ts +8 -0
- package/src/functions/findNames.ts +20 -0
- package/src/functions/formatAncestryChain.ts +80 -0
- package/src/functions/getBoundingRect.tsx +11 -0
- package/src/functions/getComposedBoundingBox.tsx +25 -0
- package/src/functions/getIdsOnPathToRoot.tsx +21 -0
- package/src/functions/getMultipleElementsBoundingBox.tsx +25 -0
- package/src/functions/getPathToParent.tsx +17 -0
- package/src/functions/getReferenceId.tsx +10 -0
- package/src/functions/getUsableFileName.test.tsx +24 -0
- package/src/functions/getUsableFileName.tsx +19 -0
- package/src/functions/getUsableName.ts +52 -0
- package/src/functions/isCombinationModifiersPressed.ts +32 -0
- package/src/functions/isLocatorsOwnElement.tsx +9 -0
- package/src/functions/mergeRects.test.ts +15 -0
- package/src/functions/mergeRects.tsx +12 -0
- package/src/functions/nonNullable.ts +3 -0
- package/src/functions/parseDataId.ts +62 -0
- package/src/functions/transformPath.test.ts +28 -0
- package/src/functions/transformPath.ts +7 -0
- package/src/global.d.ts +31 -0
- package/src/index.ts +18 -0
- package/src/initRuntime.ts +83 -0
- package/src/main.css +3 -0
- package/src/types/LabelData.ts +6 -0
- package/src/types/TreeNode.ts +22 -0
- package/src/types/types.ts +55 -0
- package/tailwind.config.js +9 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { AdapterId } from "./consts";
|
|
2
|
+
import { createTreeNode } from "./adapters/createTreeNode";
|
|
3
|
+
import {
|
|
4
|
+
collectAncestry,
|
|
5
|
+
formatAncestryChain,
|
|
6
|
+
AncestryItem,
|
|
7
|
+
} from "./functions/formatAncestryChain";
|
|
8
|
+
|
|
9
|
+
export interface LocatorJSAPI {
|
|
10
|
+
/**
|
|
11
|
+
* Get formatted ancestry path for an element.
|
|
12
|
+
* Returns a human-readable string showing the component hierarchy from root to target element.
|
|
13
|
+
*
|
|
14
|
+
* @param elementOrSelector - HTMLElement or CSS selector string (e.g., 'button.submit', '#login-form')
|
|
15
|
+
* @returns Formatted ancestry chain as string, or null if element not found/unsupported
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Basic usage with CSS selector
|
|
19
|
+
* window.__locatorjs__.getPath('button.submit');
|
|
20
|
+
* // Returns:
|
|
21
|
+
* // "div in App at src/App.tsx:15
|
|
22
|
+
* // └─ button in SubmitButton at src/components/SubmitButton.tsx:12"
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Usage with HTMLElement
|
|
26
|
+
* const button = document.querySelector('button.submit');
|
|
27
|
+
* window.__locatorjs__.getPath(button);
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // In Playwright
|
|
31
|
+
* const path = await page.evaluate(() => {
|
|
32
|
+
* return window.__locatorjs__.getPath('button.submit');
|
|
33
|
+
* });
|
|
34
|
+
* console.log(path);
|
|
35
|
+
*/
|
|
36
|
+
getPath(elementOrSelector: HTMLElement | string): string | null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get raw ancestry data for an element.
|
|
40
|
+
* Returns an array of objects containing component names, file paths, and line numbers.
|
|
41
|
+
*
|
|
42
|
+
* @param elementOrSelector - HTMLElement or CSS selector string
|
|
43
|
+
* @returns Array of ancestry items with structure:
|
|
44
|
+
* - elementName: HTML element tag (e.g., 'div', 'button')
|
|
45
|
+
* - componentName: Component name (e.g., 'LoginButton')
|
|
46
|
+
* - filePath: Source file path (e.g., 'src/components/LoginButton.tsx')
|
|
47
|
+
* - line: Line number in source file
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Get structured ancestry data
|
|
51
|
+
* const ancestry = window.__locatorjs__.getAncestry('button.submit');
|
|
52
|
+
* // Returns: [
|
|
53
|
+
* // { elementName: 'div', componentName: 'App', filePath: 'src/App.tsx', line: 15 },
|
|
54
|
+
* // { elementName: 'button', componentName: 'SubmitButton', filePath: 'src/components/SubmitButton.tsx', line: 12 }
|
|
55
|
+
* // ]
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // In Playwright - extract just component names
|
|
59
|
+
* const components = await page.evaluate(() => {
|
|
60
|
+
* const ancestry = window.__locatorjs__.getAncestry('.my-element');
|
|
61
|
+
* return ancestry?.map(item => item.componentName).filter(Boolean);
|
|
62
|
+
* });
|
|
63
|
+
*/
|
|
64
|
+
getAncestry(elementOrSelector: HTMLElement | string): AncestryItem[] | null;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get both formatted path and raw ancestry data in a single call.
|
|
68
|
+
* Convenience method that combines getPath() and getAncestry().
|
|
69
|
+
*
|
|
70
|
+
* @param elementOrSelector - HTMLElement or CSS selector string
|
|
71
|
+
* @returns Object with { path: string, ancestry: AncestryItem[] }, or null
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Get both formats at once
|
|
75
|
+
* const data = window.__locatorjs__.getPathData('button.submit');
|
|
76
|
+
* console.log(data.path); // Human-readable string
|
|
77
|
+
* console.log(data.ancestry); // Structured array
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // In Playwright - useful for comprehensive debugging
|
|
81
|
+
* const data = await page.evaluate(() => {
|
|
82
|
+
* return window.__locatorjs__.getPathData('.error-message');
|
|
83
|
+
* });
|
|
84
|
+
* if (data) {
|
|
85
|
+
* console.log('Component tree:', data.path);
|
|
86
|
+
* console.log('Source files:', data.ancestry.map(a => a.filePath));
|
|
87
|
+
* }
|
|
88
|
+
*/
|
|
89
|
+
getPathData(
|
|
90
|
+
elementOrSelector: HTMLElement | string
|
|
91
|
+
): { path: string; ancestry: AncestryItem[] } | null;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Display help information about the LocatorJS API.
|
|
95
|
+
* Shows usage examples and method descriptions for browser automation tools.
|
|
96
|
+
*
|
|
97
|
+
* @returns Help text as a string
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // View help in browser console
|
|
101
|
+
* console.log(window.__locatorjs__.help());
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* // In Playwright - view help
|
|
105
|
+
* const help = await page.evaluate(() => window.__locatorjs__.help());
|
|
106
|
+
* console.log(help);
|
|
107
|
+
*/
|
|
108
|
+
help(): string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let adapterId: AdapterId | undefined;
|
|
112
|
+
|
|
113
|
+
function resolveElement(
|
|
114
|
+
elementOrSelector: HTMLElement | string
|
|
115
|
+
): HTMLElement | null {
|
|
116
|
+
if (typeof elementOrSelector === "string") {
|
|
117
|
+
const element = document.querySelector(elementOrSelector);
|
|
118
|
+
return element instanceof HTMLElement ? element : null;
|
|
119
|
+
}
|
|
120
|
+
return elementOrSelector;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getAncestryForElement(element: HTMLElement): AncestryItem[] | null {
|
|
124
|
+
const treeNode = createTreeNode(element, adapterId);
|
|
125
|
+
if (!treeNode) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return collectAncestry(treeNode);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const HELP_TEXT = `
|
|
132
|
+
╔═══════════════════════════════════════════════════════════════════════════╗
|
|
133
|
+
║ LocatorJS Browser API ║
|
|
134
|
+
║ Programmatic Component Ancestry Access ║
|
|
135
|
+
╚═══════════════════════════════════════════════════════════════════════════╝
|
|
136
|
+
|
|
137
|
+
METHODS:
|
|
138
|
+
--------
|
|
139
|
+
|
|
140
|
+
1. getPath(elementOrSelector)
|
|
141
|
+
Returns a formatted string showing the component hierarchy.
|
|
142
|
+
|
|
143
|
+
Usage:
|
|
144
|
+
window.__locatorjs__.getPath('button.submit')
|
|
145
|
+
window.__locatorjs__.getPath(document.querySelector('.my-button'))
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
"div in App at src/App.tsx:15
|
|
149
|
+
└─ button in SubmitButton at src/components/SubmitButton.tsx:12"
|
|
150
|
+
|
|
151
|
+
2. getAncestry(elementOrSelector)
|
|
152
|
+
Returns raw ancestry data as an array of objects.
|
|
153
|
+
|
|
154
|
+
Usage:
|
|
155
|
+
window.__locatorjs__.getAncestry('button.submit')
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
[
|
|
159
|
+
{ elementName: 'div', componentName: 'App',
|
|
160
|
+
filePath: 'src/App.tsx', line: 15 },
|
|
161
|
+
{ elementName: 'button', componentName: 'SubmitButton',
|
|
162
|
+
filePath: 'src/components/SubmitButton.tsx', line: 12 }
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
3. getPathData(elementOrSelector)
|
|
166
|
+
Returns both formatted path and raw ancestry in one call.
|
|
167
|
+
|
|
168
|
+
Usage:
|
|
169
|
+
const data = window.__locatorjs__.getPathData('button.submit')
|
|
170
|
+
console.log(data.path) // formatted string
|
|
171
|
+
console.log(data.ancestry) // structured array
|
|
172
|
+
|
|
173
|
+
4. help()
|
|
174
|
+
Displays this help message.
|
|
175
|
+
|
|
176
|
+
PLAYWRIGHT EXAMPLES:
|
|
177
|
+
-------------------
|
|
178
|
+
|
|
179
|
+
// Get component path for debugging
|
|
180
|
+
const path = await page.evaluate(() => {
|
|
181
|
+
return window.__locatorjs__.getPath('button.submit');
|
|
182
|
+
});
|
|
183
|
+
console.log(path);
|
|
184
|
+
|
|
185
|
+
// Extract component names
|
|
186
|
+
const components = await page.evaluate(() => {
|
|
187
|
+
const ancestry = window.__locatorjs__.getAncestry('.error-message');
|
|
188
|
+
return ancestry?.map(item => item.componentName).filter(Boolean);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Create a test helper
|
|
192
|
+
async function getComponentPath(page, selector) {
|
|
193
|
+
return await page.evaluate((sel) => {
|
|
194
|
+
return window.__locatorjs__.getPath(sel);
|
|
195
|
+
}, selector);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
PUPPETEER EXAMPLES:
|
|
199
|
+
------------------
|
|
200
|
+
|
|
201
|
+
const path = await page.evaluate(() => {
|
|
202
|
+
return window.__locatorjs__.getPath('.my-button');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
SELENIUM EXAMPLES:
|
|
206
|
+
-----------------
|
|
207
|
+
|
|
208
|
+
const path = await driver.executeScript(() => {
|
|
209
|
+
return window.__locatorjs__.getPath('button.submit');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
CYPRESS EXAMPLES:
|
|
213
|
+
----------------
|
|
214
|
+
|
|
215
|
+
cy.window().then((win) => {
|
|
216
|
+
const path = win.__locatorjs__.getPath('button.submit');
|
|
217
|
+
cy.log(path);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
NOTES:
|
|
221
|
+
------
|
|
222
|
+
• Accepts CSS selectors or HTMLElement objects
|
|
223
|
+
• Returns null if element not found or framework not supported
|
|
224
|
+
• Works with React, Vue, Svelte, Preact, and any JSX framework
|
|
225
|
+
• Automatically installed when LocatorJS runtime initializes
|
|
226
|
+
|
|
227
|
+
Documentation: https://github.com/infi-pc/locatorjs
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
export function createBrowserAPI(
|
|
231
|
+
adapterIdParam?: AdapterId
|
|
232
|
+
): LocatorJSAPI {
|
|
233
|
+
adapterId = adapterIdParam;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
getPath(elementOrSelector: HTMLElement | string): string | null {
|
|
237
|
+
const element = resolveElement(elementOrSelector);
|
|
238
|
+
if (!element) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const ancestry = getAncestryForElement(element);
|
|
243
|
+
if (!ancestry) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return formatAncestryChain(ancestry);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
getAncestry(elementOrSelector: HTMLElement | string): AncestryItem[] | null {
|
|
251
|
+
const element = resolveElement(elementOrSelector);
|
|
252
|
+
if (!element) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return getAncestryForElement(element);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
getPathData(
|
|
260
|
+
elementOrSelector: HTMLElement | string
|
|
261
|
+
): { path: string; ancestry: AncestryItem[] } | null {
|
|
262
|
+
const element = resolveElement(elementOrSelector);
|
|
263
|
+
if (!element) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ancestry = getAncestryForElement(element);
|
|
268
|
+
if (!ancestry) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
path: formatAncestryChain(ancestry),
|
|
274
|
+
ancestry,
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
help(): string {
|
|
279
|
+
return HELP_TEXT;
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function installBrowserAPI(adapterIdParam?: AdapterId): void {
|
|
285
|
+
if (typeof window !== "undefined") {
|
|
286
|
+
(window as any).__locatorjs__ = createBrowserAPI(adapterIdParam);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function Button(props: { onClick: () => void; children: any }) {
|
|
2
|
+
return (
|
|
3
|
+
<button
|
|
4
|
+
class="py-1 px-1 hover:bg-white/30 pointer hover:text-gray-100 rounded"
|
|
5
|
+
onClick={(e) => {
|
|
6
|
+
e.preventDefault();
|
|
7
|
+
e.stopPropagation();
|
|
8
|
+
props.onClick();
|
|
9
|
+
}}
|
|
10
|
+
>
|
|
11
|
+
{props.children}
|
|
12
|
+
</button>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { For } from "solid-js";
|
|
2
|
+
import { PADDING } from "../consts";
|
|
3
|
+
import { LabelData } from "../types/LabelData";
|
|
4
|
+
import { SimpleDOMRect } from "../types/types";
|
|
5
|
+
|
|
6
|
+
export function ComponentOutline(props: {
|
|
7
|
+
bbox: SimpleDOMRect;
|
|
8
|
+
labels: LabelData[];
|
|
9
|
+
element: HTMLElement;
|
|
10
|
+
}) {
|
|
11
|
+
const isInside = () => props.bbox.height >= window.innerHeight - 40;
|
|
12
|
+
const isBelow = () => props.bbox.y < 30 && !isInside();
|
|
13
|
+
|
|
14
|
+
const left = () => Math.max(props.bbox.x - PADDING, 0);
|
|
15
|
+
const top = () => Math.max(props.bbox.y - PADDING, 0);
|
|
16
|
+
|
|
17
|
+
const cutFromTop = () => (props.bbox.y < 0 ? -(props.bbox.y - PADDING) : 0);
|
|
18
|
+
const cutFromLeft = () => (props.bbox.x < 0 ? -(props.bbox.x - PADDING) : 0);
|
|
19
|
+
|
|
20
|
+
const width = () =>
|
|
21
|
+
Math.min(props.bbox.width - cutFromLeft() + PADDING * 2, window.innerWidth);
|
|
22
|
+
const height = () =>
|
|
23
|
+
Math.min(
|
|
24
|
+
props.bbox.height - cutFromTop() + PADDING * 2,
|
|
25
|
+
window.innerHeight
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
class="border border-purple-500"
|
|
31
|
+
style={{
|
|
32
|
+
"z-index": 1,
|
|
33
|
+
position: "fixed",
|
|
34
|
+
left: left() + "px",
|
|
35
|
+
top: top() + "px",
|
|
36
|
+
width: width() + "px",
|
|
37
|
+
height: height() + "px",
|
|
38
|
+
"border-top-left-radius": left() === 0 || top() === 0 ? "0" : "8px",
|
|
39
|
+
"border-top-right-radius":
|
|
40
|
+
left() + width() === window.innerWidth || top() === 0 ? "0" : "8px",
|
|
41
|
+
"border-bottom-left-radius":
|
|
42
|
+
left() === 0 || top() + height() === window.innerHeight ? "0" : "8px",
|
|
43
|
+
"border-bottom-right-radius":
|
|
44
|
+
left() + width() === window.innerWidth ||
|
|
45
|
+
top() + height() === window.innerHeight
|
|
46
|
+
? "0"
|
|
47
|
+
: "8px",
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
id="locatorjs-labels-section"
|
|
52
|
+
style={{
|
|
53
|
+
position: "absolute",
|
|
54
|
+
display: "flex",
|
|
55
|
+
"justify-content": "center",
|
|
56
|
+
bottom: isBelow() ? (isInside() ? "2px" : "-28px") : undefined,
|
|
57
|
+
top: isBelow() ? undefined : isInside() ? "2px" : "-28px",
|
|
58
|
+
left: "0px",
|
|
59
|
+
width: "100%",
|
|
60
|
+
"pointer-events": "auto",
|
|
61
|
+
cursor: "pointer",
|
|
62
|
+
...(isBelow()
|
|
63
|
+
? {
|
|
64
|
+
"border-bottom-left-radius": "100%",
|
|
65
|
+
"border-bottom-right-radius": "100%",
|
|
66
|
+
}
|
|
67
|
+
: {
|
|
68
|
+
"border-top-left-radius": "100%",
|
|
69
|
+
"border-top-right-radius": "100%",
|
|
70
|
+
}),
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
id="locatorjs-labels-wrapper"
|
|
75
|
+
style={{
|
|
76
|
+
padding: isBelow() ? "10px 10px 2px 10px" : "2px 10px 10px 10px",
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<For each={props.labels}>
|
|
80
|
+
{(label) => {
|
|
81
|
+
const labelClass =
|
|
82
|
+
"bg-purple-500 block text-white text-xs font-bold text-center px-1 py-0.5 rounded whitespace-nowrap pointer-events-auto";
|
|
83
|
+
const labelStyles = {
|
|
84
|
+
"line-height": "18px",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div class={labelClass} style={labelStyles}>
|
|
89
|
+
{label.label}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}}
|
|
93
|
+
</For>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Targets } from "@locator/shared";
|
|
2
|
+
import { createMemo } from "solid-js";
|
|
3
|
+
import { AdapterId } from "../consts";
|
|
4
|
+
import { getElementInfo } from "../adapters/getElementInfo";
|
|
5
|
+
import { Outline } from "./Outline";
|
|
6
|
+
|
|
7
|
+
export function MaybeOutline(props: {
|
|
8
|
+
currentElement: HTMLElement;
|
|
9
|
+
adapterId?: AdapterId;
|
|
10
|
+
targets: Targets;
|
|
11
|
+
}) {
|
|
12
|
+
const elInfo = createMemo(() =>
|
|
13
|
+
getElementInfo(props.currentElement, props.adapterId)
|
|
14
|
+
);
|
|
15
|
+
const box = () => props.currentElement.getBoundingClientRect();
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
{elInfo() ? (
|
|
19
|
+
<Outline
|
|
20
|
+
element={elInfo()!}
|
|
21
|
+
targets={props.targets}
|
|
22
|
+
/>
|
|
23
|
+
) : (
|
|
24
|
+
<div class="fixed top-0 left-0 w-screen h-screen flex items-center justify-center">
|
|
25
|
+
<div
|
|
26
|
+
class="flex items-center justify-center"
|
|
27
|
+
style={{
|
|
28
|
+
position: "absolute",
|
|
29
|
+
left: box().x + "px",
|
|
30
|
+
top: box().y + "px",
|
|
31
|
+
width: box().width + "px",
|
|
32
|
+
height: box().height + "px",
|
|
33
|
+
"background-color": "rgba(222, 0, 0, 0.3)",
|
|
34
|
+
border: "1px solid rgba(222, 0, 0, 0.5)",
|
|
35
|
+
"border-radius": "2px",
|
|
36
|
+
"font-size": "12px",
|
|
37
|
+
"font-weight": "bold",
|
|
38
|
+
"text-shadow":
|
|
39
|
+
"-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
|
|
40
|
+
"text-overflow": "ellipsis",
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
No source found
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Targets } from "@locator/shared";
|
|
2
|
+
import type { FullElementInfo } from "../adapters/adapterApi";
|
|
3
|
+
import { RenderBoxes } from "./RenderBoxes";
|
|
4
|
+
|
|
5
|
+
type Box = {
|
|
6
|
+
top: number;
|
|
7
|
+
left: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
12
|
+
type IndividualBoxes = {
|
|
13
|
+
top: Box;
|
|
14
|
+
left: Box;
|
|
15
|
+
right: Box;
|
|
16
|
+
bottom: Box;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type AllBoxes = {
|
|
20
|
+
margin: IndividualBoxes;
|
|
21
|
+
padding: IndividualBoxes;
|
|
22
|
+
innerBox: Box;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function Outline(props: {
|
|
26
|
+
element: FullElementInfo;
|
|
27
|
+
targets: Targets;
|
|
28
|
+
}) {
|
|
29
|
+
const box = () => props.element.thisElement.box;
|
|
30
|
+
|
|
31
|
+
const domElementInfo = () => {
|
|
32
|
+
const htmlElement = props.element.htmlElement;
|
|
33
|
+
const box = props.element.thisElement.box;
|
|
34
|
+
if (htmlElement && box) {
|
|
35
|
+
const style = window.getComputedStyle(htmlElement);
|
|
36
|
+
|
|
37
|
+
const margin = {
|
|
38
|
+
top: parseFloat(style.marginTop),
|
|
39
|
+
left: parseFloat(style.marginLeft),
|
|
40
|
+
right: parseFloat(style.marginRight),
|
|
41
|
+
bottom: parseFloat(style.marginBottom),
|
|
42
|
+
};
|
|
43
|
+
const padding = {
|
|
44
|
+
top: parseFloat(style.paddingTop),
|
|
45
|
+
left: parseFloat(style.paddingLeft),
|
|
46
|
+
right: parseFloat(style.paddingRight),
|
|
47
|
+
bottom: parseFloat(style.paddingBottom),
|
|
48
|
+
};
|
|
49
|
+
const individualMarginBoxes: IndividualBoxes = {
|
|
50
|
+
top: {
|
|
51
|
+
top: box.y - margin.top,
|
|
52
|
+
left: box.x,
|
|
53
|
+
width: box.width,
|
|
54
|
+
height: margin.top,
|
|
55
|
+
label: label(margin.top),
|
|
56
|
+
},
|
|
57
|
+
left: {
|
|
58
|
+
top: box.y - margin.top,
|
|
59
|
+
left: box.x - margin.left,
|
|
60
|
+
width: margin.left,
|
|
61
|
+
height: box.height + margin.top + margin.bottom,
|
|
62
|
+
label: label(margin.left),
|
|
63
|
+
},
|
|
64
|
+
right: {
|
|
65
|
+
top: box.y - margin.top,
|
|
66
|
+
left: box.x + box.width,
|
|
67
|
+
width: margin.right,
|
|
68
|
+
height: box.height + margin.top + margin.bottom,
|
|
69
|
+
label: label(margin.right),
|
|
70
|
+
},
|
|
71
|
+
bottom: {
|
|
72
|
+
top: box.y + box.height,
|
|
73
|
+
left: box.x,
|
|
74
|
+
width: box.width,
|
|
75
|
+
height: margin.bottom,
|
|
76
|
+
label: label(margin.bottom),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const individualPaddingBoxes: IndividualBoxes = {
|
|
81
|
+
top: {
|
|
82
|
+
top: box.y,
|
|
83
|
+
left: box.x,
|
|
84
|
+
width: box.width,
|
|
85
|
+
height: padding.top,
|
|
86
|
+
label: label(padding.top),
|
|
87
|
+
},
|
|
88
|
+
left: {
|
|
89
|
+
top: box.y + padding.top,
|
|
90
|
+
left: box.x,
|
|
91
|
+
width: padding.left,
|
|
92
|
+
height: box.height - padding.top - padding.bottom,
|
|
93
|
+
label: label(padding.left),
|
|
94
|
+
},
|
|
95
|
+
right: {
|
|
96
|
+
top: box.y + padding.top,
|
|
97
|
+
left: box.x + box.width - padding.right,
|
|
98
|
+
width: padding.right,
|
|
99
|
+
height: box.height - padding.top - padding.bottom,
|
|
100
|
+
label: label(padding.right),
|
|
101
|
+
},
|
|
102
|
+
bottom: {
|
|
103
|
+
top: box.y + box.height - padding.bottom,
|
|
104
|
+
left: box.x,
|
|
105
|
+
width: box.width,
|
|
106
|
+
height: padding.bottom,
|
|
107
|
+
label: label(padding.bottom),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
margin: individualMarginBoxes,
|
|
113
|
+
padding: individualPaddingBoxes,
|
|
114
|
+
innerBox: {
|
|
115
|
+
top: box.y + padding.top,
|
|
116
|
+
left: box.x + padding.left,
|
|
117
|
+
width: box.width - padding.left - padding.right,
|
|
118
|
+
height: box.height - padding.top - padding.bottom,
|
|
119
|
+
label: "",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
<div>
|
|
130
|
+
{domElementInfo() && <RenderBoxes allBoxes={domElementInfo()!} />}
|
|
131
|
+
<div
|
|
132
|
+
class="fixed flex text-xs font-bold items-center justify-center text-sky-500 rounded border border-solid border-sky-500"
|
|
133
|
+
style={{
|
|
134
|
+
"z-index": 2,
|
|
135
|
+
left: box().x + "px",
|
|
136
|
+
top: box().y + "px",
|
|
137
|
+
width: box().width + "px",
|
|
138
|
+
height: box().height + "px",
|
|
139
|
+
"text-shadow":
|
|
140
|
+
"-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
|
|
141
|
+
"text-overflow": "ellipsis",
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{props.element.thisElement.label}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function label(value: number) {
|
|
152
|
+
return value ? `${value}px` : "";
|
|
153
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AllBoxes } from "./Outline";
|
|
2
|
+
|
|
3
|
+
export function RenderBoxes(props: { allBoxes: AllBoxes }) {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
{Object.entries(props.allBoxes.margin).map(([, box]) => {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
class="fixed flex text-xs font-bold items-center justify-center text-orange-500 bg-orange-500/30"
|
|
10
|
+
style={{
|
|
11
|
+
left: box.left + "px",
|
|
12
|
+
top: box.top + "px",
|
|
13
|
+
width: box.width + "px",
|
|
14
|
+
height: box.height + "px",
|
|
15
|
+
"text-shadow":
|
|
16
|
+
"-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
{/* {box.label} */}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
})}
|
|
23
|
+
{Object.entries(props.allBoxes.padding).map(([, box]) => {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
class="fixed flex text-xs font-bold items-center justify-center text-green-500 bg-green-500/30"
|
|
27
|
+
style={{
|
|
28
|
+
left: box.left + "px",
|
|
29
|
+
top: box.top + "px",
|
|
30
|
+
width: box.width + "px",
|
|
31
|
+
height: box.height + "px",
|
|
32
|
+
"text-shadow":
|
|
33
|
+
"-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{/* {box.label} */}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
})}
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
class="fixed flex text-xs font-bold items-center justify-center text-blue-500 bg-blue-500/30"
|
|
43
|
+
style={{
|
|
44
|
+
left: props.allBoxes.innerBox.left + "px",
|
|
45
|
+
top: props.allBoxes.innerBox.top + "px",
|
|
46
|
+
width: props.allBoxes.innerBox.width + "px",
|
|
47
|
+
height: props.allBoxes.innerBox.height + "px",
|
|
48
|
+
|
|
49
|
+
"text-shadow":
|
|
50
|
+
"-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{props.allBoxes.innerBox.label}
|
|
54
|
+
</div>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|