haltija 1.1.21 → 1.2.2
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/apps/desktop/index.html +1 -1
- package/apps/desktop/main.js +264 -64
- package/apps/desktop/package.json +11 -3
- package/apps/desktop/preload.js +17 -0
- package/apps/desktop/renderer/agent-status.js +210 -0
- package/apps/desktop/renderer/settings.js +55 -0
- package/apps/desktop/renderer/state.js +98 -0
- package/apps/desktop/renderer/status.js +38 -0
- package/apps/desktop/renderer/tabs.js +374 -0
- package/apps/desktop/renderer/ui-utils.js +180 -0
- package/apps/desktop/renderer/video-capture.js +154 -0
- package/apps/desktop/renderer/webview-events.js +225 -0
- package/apps/desktop/renderer.js +98 -1604
- package/apps/desktop/resources/component.js +265 -55
- package/apps/desktop/webview-preload.js +19 -1
- package/bin/cli-subcommand.mjs +90 -27
- package/bin/hints.json +9 -4
- package/bin/hj.mjs +61 -2
- package/bin/test-data.mjs +291 -0
- package/bin/tosijs-dev.mjs +95 -20
- package/dist/client.js +5 -1
- package/dist/component.js +265 -55
- package/dist/index.js +444 -76
- package/dist/server.js +444 -76
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -670,7 +674,7 @@ var injectorCode = `
|
|
|
670
674
|
`;
|
|
671
675
|
|
|
672
676
|
// src/version.ts
|
|
673
|
-
var VERSION = "1.
|
|
677
|
+
var VERSION = "1.2.2";
|
|
674
678
|
|
|
675
679
|
// src/embedded-assets.ts
|
|
676
680
|
var APP_MD = `# Haltija App
|
|
@@ -1118,11 +1122,16 @@ Response: { tagName, id, className, textContent, attributes: {...} }
|
|
|
1118
1122
|
|
|
1119
1123
|
| Name | Type | Description |
|
|
1120
1124
|
|------|------|-------------|
|
|
1121
|
-
| \`
|
|
1125
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1126
|
+
| \`selector\` | string,null | CSS selector |
|
|
1122
1127
|
| \`all\` | boolean,null | Return all matches (default false = first only) |
|
|
1123
1128
|
|
|
1124
1129
|
**Examples:**
|
|
1125
1130
|
|
|
1131
|
+
- **by-ref**: Query element by ref ID from /tree
|
|
1132
|
+
\`\`\`json
|
|
1133
|
+
{"ref":"42"}
|
|
1134
|
+
\`\`\`
|
|
1126
1135
|
- **by-id**: Find element by ID
|
|
1127
1136
|
\`\`\`json
|
|
1128
1137
|
{"selector":"#submit-btn"}
|
|
@@ -1172,13 +1181,18 @@ Use before clicking to verify element is visible and enabled.
|
|
|
1172
1181
|
|
|
1173
1182
|
| Name | Type | Description |
|
|
1174
1183
|
|------|------|-------------|
|
|
1175
|
-
| \`
|
|
1184
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1185
|
+
| \`selector\` | string,null | CSS selector |
|
|
1176
1186
|
| \`fullStyles\` | boolean,null | Include all computed styles (default: false) |
|
|
1177
1187
|
| \`matchedRules\` | boolean,null | Include matched CSS rules with specificity (default: false) |
|
|
1178
1188
|
| \`window\` | string,null | Target window ID |
|
|
1179
1189
|
|
|
1180
1190
|
**Examples:**
|
|
1181
1191
|
|
|
1192
|
+
- **by-ref**: Inspect element by ref ID from /tree
|
|
1193
|
+
\`\`\`json
|
|
1194
|
+
{"ref":"42"}
|
|
1195
|
+
\`\`\`
|
|
1182
1196
|
- **check-button**: Verify button is clickable
|
|
1183
1197
|
\`\`\`json
|
|
1184
1198
|
{"selector":"#submit"}
|
|
@@ -1256,7 +1270,8 @@ Response: array of inspection objects
|
|
|
1256
1270
|
|
|
1257
1271
|
| Name | Type | Description |
|
|
1258
1272
|
|------|------|-------------|
|
|
1259
|
-
| \`
|
|
1273
|
+
| \`ref\` | string,null | Ref ID from /tree output - returns single element as array |
|
|
1274
|
+
| \`selector\` | string,null | CSS selector |
|
|
1260
1275
|
| \`limit\` | number,null | Max elements (default 10) |
|
|
1261
1276
|
| \`fullStyles\` | boolean,null | Include all computed styles (default: false) |
|
|
1262
1277
|
| \`matchedRules\` | boolean,null | Include matched CSS rules with specificity (default: false) |
|
|
@@ -1590,7 +1605,8 @@ Good for: sliders, resize handles, drag-and-drop reordering, range inputs.
|
|
|
1590
1605
|
|
|
1591
1606
|
| Name | Type | Description |
|
|
1592
1607
|
|------|------|-------------|
|
|
1593
|
-
| \`
|
|
1608
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1609
|
+
| \`selector\` | string,null | CSS selector of drag handle |
|
|
1594
1610
|
| \`deltaX\` | number,null | Horizontal distance in pixels |
|
|
1595
1611
|
| \`deltaY\` | number,null | Vertical distance in pixels |
|
|
1596
1612
|
| \`duration\` | number,null | Drag duration in ms (default 300) |
|
|
@@ -1598,6 +1614,10 @@ Good for: sliders, resize handles, drag-and-drop reordering, range inputs.
|
|
|
1598
1614
|
|
|
1599
1615
|
**Examples:**
|
|
1600
1616
|
|
|
1617
|
+
- **by-ref**: Drag element by ref ID
|
|
1618
|
+
\`\`\`json
|
|
1619
|
+
{"ref":"15","deltaX":100}
|
|
1620
|
+
\`\`\`
|
|
1601
1621
|
- **slider-right**: Move slider right
|
|
1602
1622
|
\`\`\`json
|
|
1603
1623
|
{"selector":".slider-handle","deltaX":100}
|
|
@@ -1625,7 +1645,8 @@ Great for showing users what you found or pointing out issues. Use /unhighlight
|
|
|
1625
1645
|
|
|
1626
1646
|
| Name | Type | Description |
|
|
1627
1647
|
|------|------|-------------|
|
|
1628
|
-
| \`
|
|
1648
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1649
|
+
| \`selector\` | string,null | CSS selector |
|
|
1629
1650
|
| \`label\` | string,null | Label text to show |
|
|
1630
1651
|
| \`color\` | string,null | CSS color (default #6366f1) |
|
|
1631
1652
|
| \`duration\` | number,null | Auto-hide after ms (omit for manual) |
|
|
@@ -1633,6 +1654,10 @@ Great for showing users what you found or pointing out issues. Use /unhighlight
|
|
|
1633
1654
|
|
|
1634
1655
|
**Examples:**
|
|
1635
1656
|
|
|
1657
|
+
- **by-ref**: Highlight element by ref ID
|
|
1658
|
+
\`\`\`json
|
|
1659
|
+
{"ref":"42","label":"Found it!"}
|
|
1660
|
+
\`\`\`
|
|
1636
1661
|
- **point-out**: Show user where to click
|
|
1637
1662
|
\`\`\`json
|
|
1638
1663
|
{"selector":"#login-btn","label":"Click here"}
|
|
@@ -1662,16 +1687,17 @@ Remove any active highlight overlay created by /highlight.
|
|
|
1662
1687
|
|
|
1663
1688
|
Smooth scroll with natural easing. Multiple modes:
|
|
1664
1689
|
|
|
1665
|
-
- selector: Scroll element into view (most common)
|
|
1690
|
+
- ref/selector: Scroll element into view (most common)
|
|
1666
1691
|
- x/y: Scroll to absolute position
|
|
1667
1692
|
- deltaX/deltaY: Scroll relative to current position
|
|
1668
1693
|
|
|
1669
|
-
At least one of selector, x, y, deltaX, or deltaY must be provided.
|
|
1694
|
+
At least one of ref, selector, x, y, deltaX, or deltaY must be provided.
|
|
1670
1695
|
|
|
1671
1696
|
**Parameters:**
|
|
1672
1697
|
|
|
1673
1698
|
| Name | Type | Description |
|
|
1674
1699
|
|------|------|-------------|
|
|
1700
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1675
1701
|
| \`selector\` | string,null | CSS selector to scroll into view |
|
|
1676
1702
|
| \`x\` | number,null | Absolute X position in pixels |
|
|
1677
1703
|
| \`y\` | number,null | Absolute Y position in pixels |
|
|
@@ -1684,6 +1710,10 @@ At least one of selector, x, y, deltaX, or deltaY must be provided.
|
|
|
1684
1710
|
|
|
1685
1711
|
**Examples:**
|
|
1686
1712
|
|
|
1713
|
+
- **by-ref**: Scroll element into view by ref ID
|
|
1714
|
+
\`\`\`json
|
|
1715
|
+
{"ref":"42"}
|
|
1716
|
+
\`\`\`
|
|
1687
1717
|
- **to-element**: Scroll pricing section into view
|
|
1688
1718
|
\`\`\`json
|
|
1689
1719
|
{"selector":"#pricing"}
|
|
@@ -1775,13 +1805,18 @@ Response: { success: true, data: <return value> }
|
|
|
1775
1805
|
|
|
1776
1806
|
| Name | Type | Description |
|
|
1777
1807
|
|------|------|-------------|
|
|
1778
|
-
| \`
|
|
1808
|
+
| \`ref\` | string,null | Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency |
|
|
1809
|
+
| \`selector\` | string,null | CSS selector of the element |
|
|
1779
1810
|
| \`method\` | string | Method name to call or property name to get *(required)* |
|
|
1780
1811
|
| \`args\` | array,null | Arguments to pass (omit to get property value) |
|
|
1781
1812
|
| \`window\` | string,null | Target window ID |
|
|
1782
1813
|
|
|
1783
1814
|
**Examples:**
|
|
1784
1815
|
|
|
1816
|
+
- **by-ref**: Get property by ref ID
|
|
1817
|
+
\`\`\`json
|
|
1818
|
+
{"ref":"42","method":"value"}
|
|
1819
|
+
\`\`\`
|
|
1785
1820
|
- **get-value**: Get input value
|
|
1786
1821
|
\`\`\`json
|
|
1787
1822
|
{"selector":"#email","method":"value"}
|
|
@@ -1867,18 +1902,18 @@ Use /location after to verify navigation succeeded.
|
|
|
1867
1902
|
|
|
1868
1903
|
**Refresh the page**
|
|
1869
1904
|
|
|
1870
|
-
Hard reload the current page, bypassing
|
|
1905
|
+
Hard reload the current page, bypassing all caches (CSS, JS, images). Use soft: true for cache-friendly reload.
|
|
1871
1906
|
|
|
1872
1907
|
**Parameters:**
|
|
1873
1908
|
|
|
1874
1909
|
| Name | Type | Description |
|
|
1875
1910
|
|------|------|-------------|
|
|
1876
|
-
| \`soft\` | boolean,null | Use cached resources if available (default false = hard refresh) |
|
|
1911
|
+
| \`soft\` | boolean,null | Use cached resources if available (default false = hard refresh that busts all caches) |
|
|
1877
1912
|
| \`window\` | string,null | Target window ID |
|
|
1878
1913
|
|
|
1879
1914
|
**Examples:**
|
|
1880
1915
|
|
|
1881
|
-
- **hard**: Hard refresh (default, bypasses
|
|
1916
|
+
- **hard**: Hard refresh (default, bypasses all caches)
|
|
1882
1917
|
\`\`\`json
|
|
1883
1918
|
{}
|
|
1884
1919
|
\`\`\`
|
|
@@ -2515,16 +2550,20 @@ Use this when you see a blob URL in the DOM and need to access its content.
|
|
|
2515
2550
|
|
|
2516
2551
|
**Capture a screenshot**
|
|
2517
2552
|
|
|
2518
|
-
Capture the page or a specific element as
|
|
2553
|
+
Capture the page or a specific element as PNG/WebP/JPEG.
|
|
2519
2554
|
|
|
2520
2555
|
Works automatically in the Haltija Desktop app. In browser widget mode, captures viewport only.
|
|
2521
2556
|
|
|
2522
|
-
|
|
2557
|
+
When file=true (default from CLI), saves to /tmp/haltija-screenshots/ and returns file path.
|
|
2558
|
+
When file=false, returns base64 data URL in response JSON.
|
|
2559
|
+
|
|
2560
|
+
Response: { success, path?, image?, width, height, source }
|
|
2523
2561
|
|
|
2524
2562
|
**Parameters:**
|
|
2525
2563
|
|
|
2526
2564
|
| Name | Type | Description |
|
|
2527
2565
|
|------|------|-------------|
|
|
2566
|
+
| \`ref\` | string,null | Ref ID from /tree output - capture specific element |
|
|
2528
2567
|
| \`selector\` | string,null | Element to capture (omit for full page) |
|
|
2529
2568
|
| \`scale\` | number,null | Scale factor (default 1) |
|
|
2530
2569
|
| \`maxWidth\` | number,null | Max width in pixels |
|
|
@@ -2532,6 +2571,7 @@ Response: { success, image: "data:image/png;base64,...", width, height, source }
|
|
|
2532
2571
|
| \`window\` | string,null | Target window ID |
|
|
2533
2572
|
| \`chyron\` | boolean,null | Burn page title, URL, timestamp into image (default true, set false for clean screenshot) |
|
|
2534
2573
|
| \`delay\` | number,null | Wait ms before capturing (e.g. 1000 to let page settle after navigation) |
|
|
2574
|
+
| \`file\` | boolean,null | Save to disk and return file path instead of data URL (default true \u2014 pass false for base64) |
|
|
2535
2575
|
|
|
2536
2576
|
**Examples:**
|
|
2537
2577
|
|
|
@@ -2539,6 +2579,10 @@ Response: { success, image: "data:image/png;base64,...", width, height, source }
|
|
|
2539
2579
|
\`\`\`json
|
|
2540
2580
|
{}
|
|
2541
2581
|
\`\`\`
|
|
2582
|
+
- **by-ref**: Capture element by ref ID
|
|
2583
|
+
\`\`\`json
|
|
2584
|
+
{"ref":"42"}
|
|
2585
|
+
\`\`\`
|
|
2542
2586
|
- **element**: Capture specific element
|
|
2543
2587
|
\`\`\`json
|
|
2544
2588
|
{"selector":"#chart"}
|
|
@@ -2584,6 +2628,76 @@ Great for debugging test failures - call this when something goes wrong.
|
|
|
2584
2628
|
{"trigger":"test-failure","context":{"step":3,"error":"Element not found"}}
|
|
2585
2629
|
\`\`\`
|
|
2586
2630
|
|
|
2631
|
+
---
|
|
2632
|
+
|
|
2633
|
+
### \`POST /video/start\`
|
|
2634
|
+
|
|
2635
|
+
**Start video recording**
|
|
2636
|
+
|
|
2637
|
+
Start recording the browser tab as WebM video. Requires the Haltija Desktop app.
|
|
2638
|
+
|
|
2639
|
+
The recording saves to /tmp/haltija-videos/ when stopped. Max duration is capped to prevent runaway recordings.
|
|
2640
|
+
|
|
2641
|
+
Response: { success, recordingId }
|
|
2642
|
+
|
|
2643
|
+
**Parameters:**
|
|
2644
|
+
|
|
2645
|
+
| Name | Type | Description |
|
|
2646
|
+
|------|------|-------------|
|
|
2647
|
+
| \`maxDuration\` | number,null | Max recording duration in seconds (default 60, max 300) |
|
|
2648
|
+
| \`window\` | string,null | Target window ID |
|
|
2649
|
+
|
|
2650
|
+
**Examples:**
|
|
2651
|
+
|
|
2652
|
+
- **start**: Start recording active tab
|
|
2653
|
+
\`\`\`json
|
|
2654
|
+
{}
|
|
2655
|
+
\`\`\`
|
|
2656
|
+
- **long**: Record up to 2 minutes
|
|
2657
|
+
\`\`\`json
|
|
2658
|
+
{"maxDuration":120}
|
|
2659
|
+
\`\`\`
|
|
2660
|
+
|
|
2661
|
+
---
|
|
2662
|
+
|
|
2663
|
+
### \`POST /video/stop\`
|
|
2664
|
+
|
|
2665
|
+
**Stop video recording**
|
|
2666
|
+
|
|
2667
|
+
Stop recording and save the video file.
|
|
2668
|
+
|
|
2669
|
+
Response: { success, path, duration, size }
|
|
2670
|
+
|
|
2671
|
+
**Parameters:**
|
|
2672
|
+
|
|
2673
|
+
| Name | Type | Description |
|
|
2674
|
+
|------|------|-------------|
|
|
2675
|
+
| \`window\` | string,null | Target window ID |
|
|
2676
|
+
|
|
2677
|
+
**Examples:**
|
|
2678
|
+
|
|
2679
|
+
- **stop**: Stop recording and get file path
|
|
2680
|
+
\`\`\`json
|
|
2681
|
+
{}
|
|
2682
|
+
\`\`\`
|
|
2683
|
+
|
|
2684
|
+
---
|
|
2685
|
+
|
|
2686
|
+
### \`GET /video/status\`
|
|
2687
|
+
|
|
2688
|
+
**Check video recording status**
|
|
2689
|
+
|
|
2690
|
+
Check if video recording is active.
|
|
2691
|
+
|
|
2692
|
+
Response: { recording, recordingId?, duration?, window? }
|
|
2693
|
+
|
|
2694
|
+
**Examples:**
|
|
2695
|
+
|
|
2696
|
+
- **check**: Check recording state
|
|
2697
|
+
\`\`\`json
|
|
2698
|
+
{}
|
|
2699
|
+
\`\`\`
|
|
2700
|
+
|
|
2587
2701
|
---
|
|
2588
2702
|
`;
|
|
2589
2703
|
var DOCS_MD = `# Haltija: Browser Control for AI Agents
|
|
@@ -2619,9 +2733,9 @@ hj --help # All commands
|
|
|
2619
2733
|
### See the Page
|
|
2620
2734
|
|
|
2621
2735
|
- \`hj tree [selector, depth, includeText, ...]\` - Get DOM tree structure
|
|
2622
|
-
- \`hj query [selector, all]\` - Query DOM elements by selector
|
|
2623
|
-
- \`hj inspect [selector, fullStyles,
|
|
2624
|
-
- \`hj inspectAll [selector, limit,
|
|
2736
|
+
- \`hj query [ref, selector, all]\` - Query DOM elements by selector
|
|
2737
|
+
- \`hj inspect [ref, selector, fullStyles, ...]\` - Deep inspection of an element
|
|
2738
|
+
- \`hj inspectAll [ref, selector, limit, ...]\` - Inspect multiple elements
|
|
2625
2739
|
- \`hj find [text, tag, exact, ...]\` - Find elements by text content
|
|
2626
2740
|
- \`hj form [selector, includeDisabled, includeHidden, ...]\` - Extract all form values as structured JSON
|
|
2627
2741
|
|
|
@@ -2630,12 +2744,12 @@ hj --help # All commands
|
|
|
2630
2744
|
- \`hj click [ref, selector, text, ...]\` - Click an element
|
|
2631
2745
|
- \`hj type [ref, selector, text, ...]\` - Type text into an element
|
|
2632
2746
|
- \`hj key [key, ref, selector, ...]\` - Send keyboard input
|
|
2633
|
-
- \`hj drag [selector, deltaX,
|
|
2634
|
-
- \`hj highlight [selector, label,
|
|
2747
|
+
- \`hj drag [ref, selector, deltaX, ...]\` - Drag from an element
|
|
2748
|
+
- \`hj highlight [ref, selector, label, ...]\` - Visually highlight an element
|
|
2635
2749
|
- \`hj unhighlight\` - Remove highlight
|
|
2636
|
-
- \`hj scroll [selector, x,
|
|
2750
|
+
- \`hj scroll [ref, selector, x, ...]\` - Scroll to element or position
|
|
2637
2751
|
- \`hj wait [ms, forElement, hidden, ...]\` - Wait for time, element, or condition
|
|
2638
|
-
- \`hj call [selector, method,
|
|
2752
|
+
- \`hj call [ref, selector, method, ...]\` - Call a method or get a property on an element
|
|
2639
2753
|
|
|
2640
2754
|
### Navigate
|
|
2641
2755
|
|
|
@@ -2691,8 +2805,11 @@ hj --help # All commands
|
|
|
2691
2805
|
- \`hj console\` - Get console output
|
|
2692
2806
|
- \`hj eval [code, window]\` - Execute JavaScript
|
|
2693
2807
|
- \`hj fetch [url, window]\` - Fetch a URL from within the tab context
|
|
2694
|
-
- \`hj screenshot [selector, scale,
|
|
2808
|
+
- \`hj screenshot [ref, selector, scale, ...]\` - Capture a screenshot
|
|
2695
2809
|
- \`hj snapshot [trigger, context]\` - Capture page snapshot
|
|
2810
|
+
- \`hj video-start [maxDuration, window]\` - Start video recording
|
|
2811
|
+
- \`hj video-stop [window]\` - Stop video recording
|
|
2812
|
+
- \`hj video-status\` - Check video recording status
|
|
2696
2813
|
|
|
2697
2814
|
## Tips
|
|
2698
2815
|
|
|
@@ -2856,6 +2973,15 @@ function getPlaygroundHtml() {
|
|
|
2856
2973
|
<button id="btn-danger" class="btn-test danger" onclick="showOutput('Danger button clicked!')">Danger Button</button>
|
|
2857
2974
|
</div>
|
|
2858
2975
|
|
|
2976
|
+
<h3>Background Colors</h3>
|
|
2977
|
+
<p>Set the page background to a solid color. Useful for verifying screenshots and video capture.</p>
|
|
2978
|
+
<div class="test-buttons">
|
|
2979
|
+
<button id="bg-red" class="btn-test" style="background:#ef4444;color:white" onclick="document.body.style.backgroundColor='#ef4444';showOutput('Background: red')">Red</button>
|
|
2980
|
+
<button id="bg-green" class="btn-test" style="background:#22c55e;color:white" onclick="document.body.style.backgroundColor='#22c55e';showOutput('Background: green')">Green</button>
|
|
2981
|
+
<button id="bg-blue" class="btn-test" style="background:#3b82f6;color:white" onclick="document.body.style.backgroundColor='#3b82f6';showOutput('Background: blue')">Blue</button>
|
|
2982
|
+
<button id="bg-reset" class="btn-test" style="background:#f8fafc;border:1px solid #cbd5e1" onclick="document.body.style.backgroundColor='#f8fafc';showOutput('Background: reset')">Reset</button>
|
|
2983
|
+
</div>
|
|
2984
|
+
|
|
2859
2985
|
<h3>Form Inputs</h3>
|
|
2860
2986
|
<div class="test-form">
|
|
2861
2987
|
<div class="form-row">
|
|
@@ -2918,6 +3044,12 @@ function getPlaygroundHtml() {
|
|
|
2918
3044
|
<pre><code>hj query "#output"</code></pre>
|
|
2919
3045
|
</div>
|
|
2920
3046
|
|
|
3047
|
+
<p>Set background to red and take a screenshot:</p>
|
|
3048
|
+
<div class="code-block">
|
|
3049
|
+
<button class="copy-btn" data-copy-type="command" onclick="copyCode(this)">Copy</button>
|
|
3050
|
+
<pre><code>hj click "#bg-red" && hj screenshot</code></pre>
|
|
3051
|
+
</div>
|
|
3052
|
+
|
|
2921
3053
|
<p>Get the page tree:</p>
|
|
2922
3054
|
<div class="code-block">
|
|
2923
3055
|
<button class="copy-btn" data-copy-type="command" onclick="copyCode(this)">Copy</button>
|
|
@@ -3572,10 +3704,16 @@ Use this to check if an element exists before clicking/typing. For detailed info
|
|
|
3572
3704
|
Response: { tagName, id, className, textContent, attributes: {...} }`,
|
|
3573
3705
|
category: "dom",
|
|
3574
3706
|
input: L.object({
|
|
3575
|
-
|
|
3707
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
3708
|
+
selector: L.string.describe("CSS selector").optional,
|
|
3576
3709
|
all: L.boolean.describe("Return all matches (default false = first only)").optional
|
|
3577
3710
|
}),
|
|
3578
3711
|
examples: [
|
|
3712
|
+
{
|
|
3713
|
+
name: "by-ref",
|
|
3714
|
+
input: { ref: "42" },
|
|
3715
|
+
description: "Query element by ref ID from /tree"
|
|
3716
|
+
},
|
|
3579
3717
|
{
|
|
3580
3718
|
name: "by-id",
|
|
3581
3719
|
input: { selector: "#submit-btn" },
|
|
@@ -3602,14 +3740,14 @@ Response: { tagName, id, className, textContent, attributes: {...} }`,
|
|
|
3602
3740
|
}
|
|
3603
3741
|
],
|
|
3604
3742
|
invalidExamples: [
|
|
3605
|
-
{ name: "missing-
|
|
3743
|
+
{ name: "missing-target", input: {}, error: "ref or selector is required" },
|
|
3606
3744
|
{
|
|
3607
3745
|
name: "wrong-type",
|
|
3608
3746
|
input: { selector: 123 },
|
|
3609
3747
|
error: "selector must be string"
|
|
3610
3748
|
}
|
|
3611
3749
|
],
|
|
3612
|
-
hints: '"selector", --all | see: tree, inspect'
|
|
3750
|
+
hints: '@ref or "selector", --all | see: tree, inspect'
|
|
3613
3751
|
});
|
|
3614
3752
|
var inspect = endpoint({
|
|
3615
3753
|
path: "/inspect",
|
|
@@ -3629,12 +3767,18 @@ Response includes:
|
|
|
3629
3767
|
Use before clicking to verify element is visible and enabled.`,
|
|
3630
3768
|
category: "dom",
|
|
3631
3769
|
input: L.object({
|
|
3632
|
-
|
|
3770
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
3771
|
+
selector: L.string.describe("CSS selector").optional,
|
|
3633
3772
|
fullStyles: L.boolean.describe("Include all computed styles (default: false)").optional,
|
|
3634
3773
|
matchedRules: L.boolean.describe("Include matched CSS rules with specificity (default: false)").optional,
|
|
3635
3774
|
window: L.string.describe("Target window ID").optional
|
|
3636
3775
|
}),
|
|
3637
3776
|
examples: [
|
|
3777
|
+
{
|
|
3778
|
+
name: "by-ref",
|
|
3779
|
+
input: { ref: "42" },
|
|
3780
|
+
description: "Inspect element by ref ID from /tree"
|
|
3781
|
+
},
|
|
3638
3782
|
{
|
|
3639
3783
|
name: "check-button",
|
|
3640
3784
|
input: { selector: "#submit" },
|
|
@@ -3678,8 +3822,9 @@ Use before clicking to verify element is visible and enabled.`,
|
|
|
3678
3822
|
}
|
|
3679
3823
|
],
|
|
3680
3824
|
invalidExamples: [
|
|
3681
|
-
{ name: "missing-
|
|
3682
|
-
]
|
|
3825
|
+
{ name: "missing-target", input: {}, error: "ref or selector is required" }
|
|
3826
|
+
],
|
|
3827
|
+
hints: '@ref or "selector", --styles, --rules, --ancestors | see: tree, query'
|
|
3683
3828
|
});
|
|
3684
3829
|
var inspectAll = endpoint({
|
|
3685
3830
|
path: "/inspectAll",
|
|
@@ -3695,7 +3840,8 @@ Same detailed info as /inspect, but for multiple elements. Great for:
|
|
|
3695
3840
|
Response: array of inspection objects`,
|
|
3696
3841
|
category: "dom",
|
|
3697
3842
|
input: L.object({
|
|
3698
|
-
|
|
3843
|
+
ref: L.string.describe("Ref ID from /tree output - returns single element as array").optional,
|
|
3844
|
+
selector: L.string.describe("CSS selector").optional,
|
|
3699
3845
|
limit: L.number.describe("Max elements (default 10)").optional,
|
|
3700
3846
|
fullStyles: L.boolean.describe("Include all computed styles (default: false)").optional,
|
|
3701
3847
|
matchedRules: L.boolean.describe("Include matched CSS rules with specificity (default: false)").optional,
|
|
@@ -3971,13 +4117,19 @@ var drag = endpoint({
|
|
|
3971
4117
|
Good for: sliders, resize handles, drag-and-drop reordering, range inputs.`,
|
|
3972
4118
|
category: "interaction",
|
|
3973
4119
|
input: L.object({
|
|
3974
|
-
|
|
4120
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
4121
|
+
selector: L.string.describe("CSS selector of drag handle").optional,
|
|
3975
4122
|
deltaX: L.number.describe("Horizontal distance in pixels").optional,
|
|
3976
4123
|
deltaY: L.number.describe("Vertical distance in pixels").optional,
|
|
3977
4124
|
duration: L.number.describe("Drag duration in ms (default 300)").optional,
|
|
3978
4125
|
window: L.string.describe("Target window ID").optional
|
|
3979
4126
|
}),
|
|
3980
4127
|
examples: [
|
|
4128
|
+
{
|
|
4129
|
+
name: "by-ref",
|
|
4130
|
+
input: { ref: "15", deltaX: 100 },
|
|
4131
|
+
description: "Drag element by ref ID"
|
|
4132
|
+
},
|
|
3981
4133
|
{
|
|
3982
4134
|
name: "slider-right",
|
|
3983
4135
|
input: { selector: ".slider-handle", deltaX: 100 },
|
|
@@ -3996,12 +4148,12 @@ Good for: sliders, resize handles, drag-and-drop reordering, range inputs.`,
|
|
|
3996
4148
|
],
|
|
3997
4149
|
invalidExamples: [
|
|
3998
4150
|
{
|
|
3999
|
-
name: "missing-
|
|
4151
|
+
name: "missing-target",
|
|
4000
4152
|
input: { deltaX: 100 },
|
|
4001
|
-
error: "selector is required"
|
|
4153
|
+
error: "ref or selector is required"
|
|
4002
4154
|
}
|
|
4003
4155
|
],
|
|
4004
|
-
hints: '"selector" <deltaX> <deltaY>, --duration 500 | see: click, scroll'
|
|
4156
|
+
hints: '@ref or "selector" <deltaX> <deltaY>, --duration 500 | see: click, scroll'
|
|
4005
4157
|
});
|
|
4006
4158
|
var highlight = endpoint({
|
|
4007
4159
|
path: "/highlight",
|
|
@@ -4012,13 +4164,19 @@ var highlight = endpoint({
|
|
|
4012
4164
|
Great for showing users what you found or pointing out issues. Use /unhighlight to remove.`,
|
|
4013
4165
|
category: "interaction",
|
|
4014
4166
|
input: L.object({
|
|
4015
|
-
|
|
4167
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
4168
|
+
selector: L.string.describe("CSS selector").optional,
|
|
4016
4169
|
label: L.string.describe("Label text to show").optional,
|
|
4017
4170
|
color: L.string.describe("CSS color (default #6366f1)").optional,
|
|
4018
4171
|
duration: L.number.describe("Auto-hide after ms (omit for manual)").optional,
|
|
4019
4172
|
window: L.string.describe("Target window ID").optional
|
|
4020
4173
|
}),
|
|
4021
4174
|
examples: [
|
|
4175
|
+
{
|
|
4176
|
+
name: "by-ref",
|
|
4177
|
+
input: { ref: "42", label: "Found it!" },
|
|
4178
|
+
description: "Highlight element by ref ID"
|
|
4179
|
+
},
|
|
4022
4180
|
{
|
|
4023
4181
|
name: "point-out",
|
|
4024
4182
|
input: { selector: "#login-btn", label: "Click here" },
|
|
@@ -4036,9 +4194,9 @@ Great for showing users what you found or pointing out issues. Use /unhighlight
|
|
|
4036
4194
|
}
|
|
4037
4195
|
],
|
|
4038
4196
|
invalidExamples: [
|
|
4039
|
-
{ name: "missing-
|
|
4197
|
+
{ name: "missing-target", input: {}, error: "ref or selector is required" }
|
|
4040
4198
|
],
|
|
4041
|
-
hints: '"selector", --label "text", --color #f00, --duration 3000 | see: unhighlight, screenshot'
|
|
4199
|
+
hints: '@ref or "selector", --label "text", --color #f00, --duration 3000 | see: unhighlight, screenshot'
|
|
4042
4200
|
});
|
|
4043
4201
|
var unhighlight = endpoint({
|
|
4044
4202
|
path: "/unhighlight",
|
|
@@ -4054,13 +4212,14 @@ var scroll = endpoint({
|
|
|
4054
4212
|
summary: "Scroll to element or position",
|
|
4055
4213
|
description: `Smooth scroll with natural easing. Multiple modes:
|
|
4056
4214
|
|
|
4057
|
-
- selector: Scroll element into view (most common)
|
|
4215
|
+
- ref/selector: Scroll element into view (most common)
|
|
4058
4216
|
- x/y: Scroll to absolute position
|
|
4059
4217
|
- deltaX/deltaY: Scroll relative to current position
|
|
4060
4218
|
|
|
4061
|
-
At least one of selector, x, y, deltaX, or deltaY must be provided.`,
|
|
4219
|
+
At least one of ref, selector, x, y, deltaX, or deltaY must be provided.`,
|
|
4062
4220
|
category: "interaction",
|
|
4063
4221
|
input: L.object({
|
|
4222
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
4064
4223
|
selector: L.string.describe("CSS selector to scroll into view").optional,
|
|
4065
4224
|
x: L.number.describe("Absolute X position in pixels").optional,
|
|
4066
4225
|
y: L.number.describe("Absolute Y position in pixels").optional,
|
|
@@ -4072,6 +4231,11 @@ At least one of selector, x, y, deltaX, or deltaY must be provided.`,
|
|
|
4072
4231
|
window: L.string.describe("Target window ID").optional
|
|
4073
4232
|
}),
|
|
4074
4233
|
examples: [
|
|
4234
|
+
{
|
|
4235
|
+
name: "by-ref",
|
|
4236
|
+
input: { ref: "42" },
|
|
4237
|
+
description: "Scroll element into view by ref ID"
|
|
4238
|
+
},
|
|
4075
4239
|
{
|
|
4076
4240
|
name: "to-element",
|
|
4077
4241
|
input: { selector: "#pricing" },
|
|
@@ -4094,7 +4258,7 @@ At least one of selector, x, y, deltaX, or deltaY must be provided.`,
|
|
|
4094
4258
|
description: "Slow animated scroll"
|
|
4095
4259
|
}
|
|
4096
4260
|
],
|
|
4097
|
-
hints: '"selector" or <deltaY>, --duration 500 | see: click, wait'
|
|
4261
|
+
hints: '@ref or "selector" or <deltaY>, --duration 500 | see: click, wait'
|
|
4098
4262
|
});
|
|
4099
4263
|
var wait = endpoint({
|
|
4100
4264
|
path: "/wait",
|
|
@@ -4265,17 +4429,17 @@ var refresh = endpoint({
|
|
|
4265
4429
|
path: "/refresh",
|
|
4266
4430
|
method: "POST",
|
|
4267
4431
|
summary: "Refresh the page",
|
|
4268
|
-
description: "Hard reload the current page, bypassing
|
|
4432
|
+
description: "Hard reload the current page, bypassing all caches (CSS, JS, images). Use soft: true for cache-friendly reload.",
|
|
4269
4433
|
category: "navigation",
|
|
4270
4434
|
input: L.object({
|
|
4271
|
-
soft: L.boolean.describe("Use cached resources if available (default false = hard refresh)").optional,
|
|
4435
|
+
soft: L.boolean.describe("Use cached resources if available (default false = hard refresh that busts all caches)").optional,
|
|
4272
4436
|
window: L.string.describe("Target window ID").optional
|
|
4273
4437
|
}),
|
|
4274
4438
|
examples: [
|
|
4275
4439
|
{
|
|
4276
4440
|
name: "hard",
|
|
4277
4441
|
input: {},
|
|
4278
|
-
description: "Hard refresh (default, bypasses
|
|
4442
|
+
description: "Hard refresh (default, bypasses all caches)"
|
|
4279
4443
|
},
|
|
4280
4444
|
{
|
|
4281
4445
|
name: "soft",
|
|
@@ -4530,12 +4694,18 @@ Return value is JSON-serialized. Promises are awaited.
|
|
|
4530
4694
|
Response: { success: true, data: <return value> }`,
|
|
4531
4695
|
category: "interaction",
|
|
4532
4696
|
input: L.object({
|
|
4533
|
-
|
|
4697
|
+
ref: L.string.describe("Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency").optional,
|
|
4698
|
+
selector: L.string.describe("CSS selector of the element").optional,
|
|
4534
4699
|
method: L.string.describe("Method name to call or property name to get"),
|
|
4535
4700
|
args: L.array(L.any).describe("Arguments to pass (omit to get property value)").optional,
|
|
4536
4701
|
window: L.string.describe("Target window ID").optional
|
|
4537
4702
|
}),
|
|
4538
4703
|
examples: [
|
|
4704
|
+
{
|
|
4705
|
+
name: "by-ref",
|
|
4706
|
+
input: { ref: "42", method: "value" },
|
|
4707
|
+
description: "Get property by ref ID"
|
|
4708
|
+
},
|
|
4539
4709
|
{
|
|
4540
4710
|
name: "get-value",
|
|
4541
4711
|
input: { selector: "#email", method: "value" },
|
|
@@ -4607,38 +4777,49 @@ Response: { success: true, data: <return value> }`,
|
|
|
4607
4777
|
],
|
|
4608
4778
|
invalidExamples: [
|
|
4609
4779
|
{
|
|
4610
|
-
name: "missing-
|
|
4780
|
+
name: "missing-target",
|
|
4611
4781
|
input: { method: "click" },
|
|
4612
|
-
error: "selector is required"
|
|
4782
|
+
error: "ref or selector is required"
|
|
4613
4783
|
},
|
|
4614
4784
|
{
|
|
4615
4785
|
name: "missing-method",
|
|
4616
4786
|
input: { selector: "#btn" },
|
|
4617
4787
|
error: "method is required"
|
|
4618
4788
|
}
|
|
4619
|
-
]
|
|
4789
|
+
],
|
|
4790
|
+
hints: '@ref or "selector" <method>, --args [...] | see: eval, inspect'
|
|
4620
4791
|
});
|
|
4621
4792
|
var screenshot = endpoint({
|
|
4622
4793
|
path: "/screenshot",
|
|
4623
4794
|
method: "POST",
|
|
4624
4795
|
summary: "Capture a screenshot",
|
|
4625
|
-
description: `Capture the page or a specific element as
|
|
4796
|
+
description: `Capture the page or a specific element as PNG/WebP/JPEG.
|
|
4626
4797
|
|
|
4627
4798
|
Works automatically in the Haltija Desktop app. In browser widget mode, captures viewport only.
|
|
4628
4799
|
|
|
4629
|
-
|
|
4800
|
+
When file=true (default from CLI), saves to /tmp/haltija-screenshots/ and returns file path.
|
|
4801
|
+
When file=false, returns base64 data URL in response JSON.
|
|
4802
|
+
|
|
4803
|
+
Response: { success, path?, image?, width, height, source }`,
|
|
4630
4804
|
category: "debug",
|
|
4631
4805
|
input: L.object({
|
|
4806
|
+
ref: L.string.describe("Ref ID from /tree output - capture specific element").optional,
|
|
4632
4807
|
selector: L.string.describe("Element to capture (omit for full page)").optional,
|
|
4633
4808
|
scale: L.number.describe("Scale factor (default 1)").optional,
|
|
4634
4809
|
maxWidth: L.number.describe("Max width in pixels").optional,
|
|
4635
4810
|
maxHeight: L.number.describe("Max height in pixels").optional,
|
|
4636
4811
|
window: L.string.describe("Target window ID").optional,
|
|
4637
4812
|
chyron: L.boolean.describe("Burn page title, URL, timestamp into image (default true, set false for clean screenshot)").optional,
|
|
4638
|
-
delay: L.number.describe("Wait ms before capturing (e.g. 1000 to let page settle after navigation)").optional
|
|
4813
|
+
delay: L.number.describe("Wait ms before capturing (e.g. 1000 to let page settle after navigation)").optional,
|
|
4814
|
+
file: L.boolean.describe("Save to disk and return file path instead of data URL (default true \u2014 pass false for base64)").optional
|
|
4639
4815
|
}),
|
|
4640
4816
|
examples: [
|
|
4641
4817
|
{ name: "full-page", input: {}, description: "Capture entire page with chyron showing URL/title" },
|
|
4818
|
+
{
|
|
4819
|
+
name: "by-ref",
|
|
4820
|
+
input: { ref: "42" },
|
|
4821
|
+
description: "Capture element by ref ID"
|
|
4822
|
+
},
|
|
4642
4823
|
{
|
|
4643
4824
|
name: "element",
|
|
4644
4825
|
input: { selector: "#chart" },
|
|
@@ -5080,6 +5261,119 @@ Great for debugging test failures - call this when something goes wrong.`,
|
|
|
5080
5261
|
}
|
|
5081
5262
|
]
|
|
5082
5263
|
});
|
|
5264
|
+
var videoStart = endpoint({
|
|
5265
|
+
path: "/video/start",
|
|
5266
|
+
method: "POST",
|
|
5267
|
+
summary: "Start video recording",
|
|
5268
|
+
description: `Start recording the browser tab as WebM video. Requires the Haltija Desktop app.
|
|
5269
|
+
|
|
5270
|
+
The recording saves to /tmp/haltija-videos/ when stopped. Max duration is capped to prevent runaway recordings.
|
|
5271
|
+
|
|
5272
|
+
Response: { success, recordingId }`,
|
|
5273
|
+
category: "debug",
|
|
5274
|
+
input: L.object({
|
|
5275
|
+
maxDuration: L.number.describe("Max recording duration in seconds (default 60, max 300)").optional,
|
|
5276
|
+
window: L.string.describe("Target window ID").optional
|
|
5277
|
+
}),
|
|
5278
|
+
examples: [
|
|
5279
|
+
{ name: "start", input: {}, description: "Start recording active tab" },
|
|
5280
|
+
{ name: "long", input: { maxDuration: 120 }, description: "Record up to 2 minutes" }
|
|
5281
|
+
],
|
|
5282
|
+
hints: "--maxDuration 120 | see: video-stop, video-status, screenshot"
|
|
5283
|
+
});
|
|
5284
|
+
var videoStop = endpoint({
|
|
5285
|
+
path: "/video/stop",
|
|
5286
|
+
method: "POST",
|
|
5287
|
+
summary: "Stop video recording",
|
|
5288
|
+
description: `Stop recording and save the video file.
|
|
5289
|
+
|
|
5290
|
+
Response: { success, path, duration, size }`,
|
|
5291
|
+
category: "debug",
|
|
5292
|
+
input: L.object({
|
|
5293
|
+
window: L.string.describe("Target window ID").optional
|
|
5294
|
+
}),
|
|
5295
|
+
examples: [
|
|
5296
|
+
{ name: "stop", input: {}, description: "Stop recording and get file path" }
|
|
5297
|
+
],
|
|
5298
|
+
hints: "| see: video-start, video-status"
|
|
5299
|
+
});
|
|
5300
|
+
var videoStatus = endpoint({
|
|
5301
|
+
path: "/video/status",
|
|
5302
|
+
method: "GET",
|
|
5303
|
+
summary: "Check video recording status",
|
|
5304
|
+
description: `Check if video recording is active.
|
|
5305
|
+
|
|
5306
|
+
Response: { recording, recordingId?, duration?, window? }`,
|
|
5307
|
+
category: "debug",
|
|
5308
|
+
input: L.object({}),
|
|
5309
|
+
examples: [
|
|
5310
|
+
{ name: "check", input: {}, description: "Check recording state" }
|
|
5311
|
+
],
|
|
5312
|
+
hints: "| see: video-start, video-stop"
|
|
5313
|
+
});
|
|
5314
|
+
var dialogConfigure = endpoint({
|
|
5315
|
+
path: "/dialog/configure",
|
|
5316
|
+
method: "POST",
|
|
5317
|
+
summary: "Configure native dialog auto-response policy",
|
|
5318
|
+
description: `Set how native browser dialogs (alert, confirm, prompt) are handled.
|
|
5319
|
+
|
|
5320
|
+
By default, Haltija intercepts all native dialogs and auto-responds:
|
|
5321
|
+
- alert: dismissed immediately
|
|
5322
|
+
- confirm: accepted (returns true)
|
|
5323
|
+
- prompt: dismissed (returns null)
|
|
5324
|
+
|
|
5325
|
+
Configure the policy **before** triggering actions that cause dialogs.
|
|
5326
|
+
Each dialog is logged and reported via the dialog/opened push event.
|
|
5327
|
+
|
|
5328
|
+
Response: { policy: { alert, confirm, prompt, beforeunload } }`,
|
|
5329
|
+
category: "dialog",
|
|
5330
|
+
input: L.object({
|
|
5331
|
+
alert: L.string.describe('"dismiss" (only option for alerts)').optional,
|
|
5332
|
+
confirm: L.string.describe('"accept" or "dismiss"').optional,
|
|
5333
|
+
prompt: L.any.describe('"dismiss" or { "response": "text" } to auto-fill').optional,
|
|
5334
|
+
beforeunload: L.string.describe('"allow" or "block" \u2014 controls page unload').optional,
|
|
5335
|
+
window: L.string.describe("Target window ID").optional
|
|
5336
|
+
}),
|
|
5337
|
+
examples: [
|
|
5338
|
+
{
|
|
5339
|
+
name: "accept-confirms",
|
|
5340
|
+
input: { confirm: "accept" },
|
|
5341
|
+
description: "Auto-accept all confirm dialogs"
|
|
5342
|
+
},
|
|
5343
|
+
{
|
|
5344
|
+
name: "dismiss-confirms",
|
|
5345
|
+
input: { confirm: "dismiss" },
|
|
5346
|
+
description: "Auto-dismiss (cancel) all confirm dialogs"
|
|
5347
|
+
},
|
|
5348
|
+
{
|
|
5349
|
+
name: "auto-fill-prompt",
|
|
5350
|
+
input: { prompt: { response: "my answer" } },
|
|
5351
|
+
description: "Auto-fill prompt dialogs with text"
|
|
5352
|
+
}
|
|
5353
|
+
],
|
|
5354
|
+
cli: {
|
|
5355
|
+
name: "dialog-configure",
|
|
5356
|
+
args: [],
|
|
5357
|
+
flags: ["--confirm", "--prompt", "--beforeunload"]
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
var dialogHistory = endpoint({
|
|
5361
|
+
path: "/dialog/history",
|
|
5362
|
+
method: "GET",
|
|
5363
|
+
summary: "Get recent dialog history",
|
|
5364
|
+
description: `Returns a list of recently intercepted native dialogs.
|
|
5365
|
+
|
|
5366
|
+
Each entry includes: type (alert/confirm/prompt), message, response given, timestamp.
|
|
5367
|
+
Buffer holds the last 50 dialogs.
|
|
5368
|
+
|
|
5369
|
+
Response: { history: [{ type, message, defaultValue?, response, timestamp }] }`,
|
|
5370
|
+
category: "dialog",
|
|
5371
|
+
cli: {
|
|
5372
|
+
name: "dialog-history",
|
|
5373
|
+
args: [],
|
|
5374
|
+
isGet: true
|
|
5375
|
+
}
|
|
5376
|
+
});
|
|
5083
5377
|
var status = endpoint({
|
|
5084
5378
|
path: "/status",
|
|
5085
5379
|
method: "GET",
|
|
@@ -5287,6 +5581,11 @@ var endpoints = {
|
|
|
5287
5581
|
testSuite,
|
|
5288
5582
|
testValidate,
|
|
5289
5583
|
snapshot,
|
|
5584
|
+
videoStart,
|
|
5585
|
+
videoStop,
|
|
5586
|
+
videoStatus,
|
|
5587
|
+
dialogConfigure,
|
|
5588
|
+
dialogHistory,
|
|
5290
5589
|
status,
|
|
5291
5590
|
stats,
|
|
5292
5591
|
version,
|
|
@@ -5761,6 +6060,7 @@ registerHandler(click, async (body, ctx) => {
|
|
|
5761
6060
|
registerHandler(query, async (body, ctx) => {
|
|
5762
6061
|
const windowId = body.window || ctx.targetWindowId;
|
|
5763
6062
|
const response = await ctx.requestFromBrowser("dom", "query", {
|
|
6063
|
+
ref: body.ref,
|
|
5764
6064
|
selector: body.selector,
|
|
5765
6065
|
all: body.all
|
|
5766
6066
|
}, 5000, windowId);
|
|
@@ -5778,16 +6078,18 @@ registerHandler(fetchUrl, async (body, ctx) => {
|
|
|
5778
6078
|
});
|
|
5779
6079
|
registerHandler(call, async (body, ctx) => {
|
|
5780
6080
|
const windowId = body.window || ctx.targetWindowId;
|
|
5781
|
-
const
|
|
5782
|
-
const
|
|
6081
|
+
const ref = body.ref;
|
|
6082
|
+
const selector = body.selector;
|
|
5783
6083
|
const method = body.method;
|
|
5784
6084
|
const args = body.args;
|
|
6085
|
+
const resolveExpr = ref ? `window.__haltija_refRegistry?.resolve(${JSON.stringify(ref)})` : `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(selector)})`;
|
|
6086
|
+
const targetDesc = ref ? `@${ref}` : selector || "(none)";
|
|
5785
6087
|
let code;
|
|
5786
6088
|
if (args !== undefined) {
|
|
5787
6089
|
const argsJson = JSON.stringify(args);
|
|
5788
6090
|
code = `(function() {
|
|
5789
6091
|
const el = ${resolveExpr};
|
|
5790
|
-
if (!el) return { success: false, error: 'Element not found: ${
|
|
6092
|
+
if (!el) return { success: false, error: 'Element not found: ${targetDesc.replace(/'/g, "\\'")}' };
|
|
5791
6093
|
if (typeof el[${JSON.stringify(method)}] !== 'function') {
|
|
5792
6094
|
return { success: false, error: 'Method not found: ${method}' };
|
|
5793
6095
|
}
|
|
@@ -5801,7 +6103,7 @@ registerHandler(call, async (body, ctx) => {
|
|
|
5801
6103
|
} else {
|
|
5802
6104
|
code = `(function() {
|
|
5803
6105
|
const el = ${resolveExpr};
|
|
5804
|
-
if (!el) return { success: false, error: 'Element not found: ${
|
|
6106
|
+
if (!el) return { success: false, error: 'Element not found: ${targetDesc.replace(/'/g, "\\'")}' };
|
|
5805
6107
|
try {
|
|
5806
6108
|
const value = el[${JSON.stringify(method)}];
|
|
5807
6109
|
return { success: true, data: value };
|
|
@@ -5817,31 +6119,41 @@ registerHandler(call, async (body, ctx) => {
|
|
|
5817
6119
|
return Response.json(response, { headers: ctx.headers });
|
|
5818
6120
|
});
|
|
5819
6121
|
registerHandler(drag, async (body, ctx) => {
|
|
6122
|
+
const ref = body.ref;
|
|
5820
6123
|
const selector = body.selector;
|
|
5821
6124
|
const deltaX = body.deltaX || 0;
|
|
5822
6125
|
const deltaY = body.deltaY || 0;
|
|
5823
6126
|
const duration = body.duration || 300;
|
|
5824
6127
|
const steps = Math.max(5, Math.floor(duration / 16));
|
|
5825
6128
|
const windowId = body.window || ctx.targetWindowId;
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
6129
|
+
const targetDesc = ref ? `@${ref}` : selector;
|
|
6130
|
+
if (ref) {
|
|
6131
|
+
await ctx.requestFromBrowser("eval", "exec", {
|
|
6132
|
+
code: `(window.__haltija_refRegistry?.resolve(${JSON.stringify(ref)}) || document.body)?.scrollIntoView({behavior: "smooth", block: "center"})`
|
|
6133
|
+
}, 5000, windowId);
|
|
6134
|
+
} else if (selector) {
|
|
6135
|
+
await ctx.requestFromBrowser("eval", "exec", {
|
|
6136
|
+
code: `${qs(selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
|
|
6137
|
+
}, 5000, windowId);
|
|
6138
|
+
}
|
|
5829
6139
|
await sleep(100);
|
|
5830
|
-
const inspectResponse = await ctx.requestFromBrowser("dom", "inspect", { selector }, 5000, windowId);
|
|
6140
|
+
const inspectResponse = await ctx.requestFromBrowser("dom", "inspect", { ref, selector }, 5000, windowId);
|
|
5831
6141
|
if (!inspectResponse.success || !inspectResponse.data) {
|
|
5832
|
-
return Response.json({ success: false, error:
|
|
6142
|
+
return Response.json({ success: false, error: `Element not found: ${targetDesc}` }, { headers: ctx.headers });
|
|
5833
6143
|
}
|
|
5834
6144
|
const box = inspectResponse.data.box;
|
|
5835
6145
|
const startX = box.x + box.width / 2;
|
|
5836
6146
|
const startY = box.y + box.height / 2;
|
|
5837
6147
|
for (const event of ["mouseenter", "mouseover", "mousemove"]) {
|
|
5838
6148
|
await ctx.requestFromBrowser("events", "dispatch", {
|
|
6149
|
+
ref,
|
|
5839
6150
|
selector,
|
|
5840
6151
|
event,
|
|
5841
6152
|
options: { clientX: startX, clientY: startY }
|
|
5842
6153
|
}, 5000, windowId);
|
|
5843
6154
|
}
|
|
5844
6155
|
await ctx.requestFromBrowser("events", "dispatch", {
|
|
6156
|
+
ref,
|
|
5845
6157
|
selector,
|
|
5846
6158
|
event: "mousedown",
|
|
5847
6159
|
options: { clientX: startX, clientY: startY }
|
|
@@ -5926,6 +6238,7 @@ registerHandler(key, async (body, ctx) => {
|
|
|
5926
6238
|
registerHandler(inspect, async (body, ctx) => {
|
|
5927
6239
|
const windowId = body.window || ctx.targetWindowId;
|
|
5928
6240
|
const response = await ctx.requestFromBrowser("dom", "inspect", {
|
|
6241
|
+
ref: body.ref,
|
|
5929
6242
|
selector: body.selector,
|
|
5930
6243
|
fullStyles: body.fullStyles,
|
|
5931
6244
|
matchedRules: body.matchedRules
|
|
@@ -5935,6 +6248,7 @@ registerHandler(inspect, async (body, ctx) => {
|
|
|
5935
6248
|
registerHandler(inspectAll, async (body, ctx) => {
|
|
5936
6249
|
const windowId = body.window || ctx.targetWindowId;
|
|
5937
6250
|
const response = await ctx.requestFromBrowser("dom", "inspectAll", {
|
|
6251
|
+
ref: body.ref,
|
|
5938
6252
|
selector: body.selector,
|
|
5939
6253
|
limit: body.limit || 10,
|
|
5940
6254
|
fullStyles: body.fullStyles,
|
|
@@ -5944,11 +6258,18 @@ registerHandler(inspectAll, async (body, ctx) => {
|
|
|
5944
6258
|
});
|
|
5945
6259
|
registerHandler(highlight, async (body, ctx) => {
|
|
5946
6260
|
const windowId = body.window || ctx.targetWindowId;
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
6261
|
+
if (body.ref) {
|
|
6262
|
+
await ctx.requestFromBrowser("eval", "exec", {
|
|
6263
|
+
code: `(window.__haltija_refRegistry?.resolve(${JSON.stringify(body.ref)}) || document.body)?.scrollIntoView({behavior: "smooth", block: "center"})`
|
|
6264
|
+
}, 5000, windowId);
|
|
6265
|
+
} else if (body.selector) {
|
|
6266
|
+
await ctx.requestFromBrowser("eval", "exec", {
|
|
6267
|
+
code: `${qs(body.selector)}?.scrollIntoView({behavior: "smooth", block: "center"})`
|
|
6268
|
+
}, 5000, windowId);
|
|
6269
|
+
}
|
|
5950
6270
|
await sleep(100);
|
|
5951
6271
|
const response = await ctx.requestFromBrowser("dom", "highlight", {
|
|
6272
|
+
ref: body.ref,
|
|
5952
6273
|
selector: body.selector,
|
|
5953
6274
|
label: body.label,
|
|
5954
6275
|
color: body.color,
|
|
@@ -5966,9 +6287,9 @@ registerHandler(navigate, async (body, ctx) => {
|
|
|
5966
6287
|
return Response.json(response, { headers: ctx.headers });
|
|
5967
6288
|
});
|
|
5968
6289
|
registerHandler(refresh, async (body, ctx) => {
|
|
5969
|
-
const
|
|
6290
|
+
const soft = body.soft ?? false;
|
|
5970
6291
|
const windowId = body.window || ctx.targetWindowId;
|
|
5971
|
-
const response = await ctx.requestFromBrowser("navigation", "refresh", {
|
|
6292
|
+
const response = await ctx.requestFromBrowser("navigation", "refresh", { soft }, 5000, windowId);
|
|
5972
6293
|
return Response.json(response, { headers: ctx.headers });
|
|
5973
6294
|
});
|
|
5974
6295
|
registerHandler(tree, async (body, ctx) => {
|
|
@@ -5991,6 +6312,7 @@ registerHandler(screenshot, async (body, ctx) => {
|
|
|
5991
6312
|
ctx.updateSessionAffinity(windowId);
|
|
5992
6313
|
}
|
|
5993
6314
|
const response = await ctx.requestFromBrowser("dom", "screenshot", {
|
|
6315
|
+
ref: body.ref,
|
|
5994
6316
|
selector: body.selector,
|
|
5995
6317
|
format: body.format,
|
|
5996
6318
|
quality: body.quality,
|
|
@@ -6004,6 +6326,23 @@ registerHandler(screenshot, async (body, ctx) => {
|
|
|
6004
6326
|
...response,
|
|
6005
6327
|
window: windowInfo || { id: windowId || "unknown", url: "unknown", title: "unknown" }
|
|
6006
6328
|
};
|
|
6329
|
+
if (body.file !== false && enrichedResponse.data?.image) {
|
|
6330
|
+
const dataUrl = enrichedResponse.data.image;
|
|
6331
|
+
const match = dataUrl.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
6332
|
+
if (match) {
|
|
6333
|
+
const ext = match[1] === "jpeg" ? "jpg" : match[1];
|
|
6334
|
+
const base64 = match[2];
|
|
6335
|
+
const dir = "/tmp/haltija-screenshots";
|
|
6336
|
+
const { mkdirSync, writeFileSync } = await import("fs");
|
|
6337
|
+
mkdirSync(dir, { recursive: true });
|
|
6338
|
+
const shortId = Math.random().toString(36).slice(2, 6);
|
|
6339
|
+
const filename = `hj-${Date.now()}-${shortId}.${ext}`;
|
|
6340
|
+
const filepath = `${dir}/${filename}`;
|
|
6341
|
+
writeFileSync(filepath, Buffer.from(base64, "base64"));
|
|
6342
|
+
enrichedResponse.data.path = filepath;
|
|
6343
|
+
delete enrichedResponse.data.image;
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6007
6346
|
return Response.json(enrichedResponse, { headers: ctx.headers });
|
|
6008
6347
|
});
|
|
6009
6348
|
registerHandler(tabsOpen, async (body, ctx) => {
|
|
@@ -6434,11 +6773,13 @@ registerHandler(scroll, async (body, ctx) => {
|
|
|
6434
6773
|
};
|
|
6435
6774
|
const easing = easings[${JSON.stringify(easing)}] || easings['ease-out'];
|
|
6436
6775
|
`;
|
|
6437
|
-
if (body.selector) {
|
|
6776
|
+
if (body.ref || body.selector) {
|
|
6777
|
+
const resolveCode = body.ref ? `window.__haltija_refRegistry?.resolve(${JSON.stringify(body.ref)})` : `(window.__haltija_resolveSelector || document.querySelector.bind(document))(${JSON.stringify(body.selector)})`;
|
|
6778
|
+
const targetDesc = body.ref ? `@${body.ref}` : body.selector;
|
|
6438
6779
|
const code = `
|
|
6439
6780
|
(async () => {
|
|
6440
|
-
const el =
|
|
6441
|
-
if (!el) return { success: false, error: 'Element not found' };
|
|
6781
|
+
const el = ${resolveCode};
|
|
6782
|
+
if (!el) return { success: false, error: 'Element not found: ${targetDesc}' };
|
|
6442
6783
|
const rect = el.getBoundingClientRect();
|
|
6443
6784
|
const blockAlign = ${JSON.stringify(block)};
|
|
6444
6785
|
let targetY;
|
|
@@ -6510,6 +6851,41 @@ registerHandler(scroll, async (body, ctx) => {
|
|
|
6510
6851
|
return Response.json({ success: false, error: "Must provide selector, x/y coordinates, or deltaX/deltaY" }, { status: 400, headers: ctx.headers });
|
|
6511
6852
|
}
|
|
6512
6853
|
});
|
|
6854
|
+
registerHandler(videoStart, async (body, ctx) => {
|
|
6855
|
+
const windowId = body.window || ctx.targetWindowId;
|
|
6856
|
+
const maxDuration = Math.min(body.maxDuration || 60, 300);
|
|
6857
|
+
const response = await ctx.requestFromBrowser("video", "start", { maxDuration }, 1e4, windowId);
|
|
6858
|
+
return Response.json(response, { headers: ctx.headers });
|
|
6859
|
+
});
|
|
6860
|
+
registerHandler(videoStop, async (body, ctx) => {
|
|
6861
|
+
const windowId = body.window || ctx.targetWindowId;
|
|
6862
|
+
const response = await ctx.requestFromBrowser("video", "stop", {}, 30000, windowId);
|
|
6863
|
+
if (response.success && response.data) {
|
|
6864
|
+
return Response.json({
|
|
6865
|
+
success: true,
|
|
6866
|
+
path: response.data.path,
|
|
6867
|
+
duration: response.data.duration,
|
|
6868
|
+
size: response.data.size,
|
|
6869
|
+
format: response.data.format || "webm"
|
|
6870
|
+
}, { headers: ctx.headers });
|
|
6871
|
+
}
|
|
6872
|
+
return Response.json(response, { headers: ctx.headers });
|
|
6873
|
+
});
|
|
6874
|
+
registerHandler(videoStatus, async (_body, ctx) => {
|
|
6875
|
+
const windowId = ctx.targetWindowId;
|
|
6876
|
+
const response = await ctx.requestFromBrowser("video", "status", {}, 5000, windowId);
|
|
6877
|
+
return Response.json(response, { headers: ctx.headers });
|
|
6878
|
+
});
|
|
6879
|
+
registerHandler(dialogConfigure, async (body, ctx) => {
|
|
6880
|
+
const windowId = body.window || ctx.targetWindowId;
|
|
6881
|
+
const response = await ctx.requestFromBrowser("dialog", "configure", body, 5000, windowId);
|
|
6882
|
+
return Response.json(response, { headers: ctx.headers });
|
|
6883
|
+
});
|
|
6884
|
+
registerHandler(dialogHistory, async (_body, ctx) => {
|
|
6885
|
+
const windowId = ctx.targetWindowId;
|
|
6886
|
+
const response = await ctx.requestFromBrowser("dialog", "history", {}, 5000, windowId);
|
|
6887
|
+
return Response.json(response, { headers: ctx.headers });
|
|
6888
|
+
});
|
|
6513
6889
|
|
|
6514
6890
|
// src/api-router.ts
|
|
6515
6891
|
function isDeprecated(endpoint2) {
|
|
@@ -10012,15 +10388,7 @@ var serverConfig = {
|
|
|
10012
10388
|
}
|
|
10013
10389
|
const widgetSessionId = data.payload.serverSessionId;
|
|
10014
10390
|
if (widgetSessionId && widgetSessionId !== SERVER_SESSION_ID) {
|
|
10015
|
-
console.log(`${LOG_PREFIX} Widget session
|
|
10016
|
-
wsTyped.send(JSON.stringify({
|
|
10017
|
-
id: uid(),
|
|
10018
|
-
channel: "system",
|
|
10019
|
-
action: "reload",
|
|
10020
|
-
payload: { reason: "session_mismatch" },
|
|
10021
|
-
timestamp: Date.now(),
|
|
10022
|
-
source: "server"
|
|
10023
|
-
}));
|
|
10391
|
+
console.log(`${LOG_PREFIX} Widget from different session (${widgetSessionId.slice(0, 8)}... vs ${SERVER_SESSION_ID.slice(0, 8)}...), accepting anyway`);
|
|
10024
10392
|
}
|
|
10025
10393
|
if (windowId) {
|
|
10026
10394
|
const recordingSession = activeRecordingSessions.get(windowId);
|