brave-real-browser-mcp-server 2.42.0 → 2.43.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 +10 -3
- package/package.json +3 -3
- package/packages/brave-real-blocker/package.json +2 -2
- package/packages/brave-real-launcher/package.json +2 -2
- package/packages/brave-real-playwright-core/package.json +1 -1
- package/packages/brave-real-puppeteer-core/package.json +2 -2
- package/src/mcp/handlers.js +560 -36
- package/src/shared/tools.js +38 -13
package/README.md
CHANGED
|
@@ -681,15 +681,22 @@ console.log(blocker === sameBlocker); // true
|
|
|
681
681
|
| `npm run mcp` | Start MCP server (alias) |
|
|
682
682
|
| `npm run mcp:verbose` | Start MCP server with tool details |
|
|
683
683
|
| `npm run lsp` | Start LSP server for IDE intelligence |
|
|
684
|
+
| `npm run lsp:tcp` | Start LSP server on TCP port 7777 |
|
|
684
685
|
| `npm run list` | List all 23 optimized tools with categories |
|
|
685
|
-
| `npm install` | Install
|
|
686
|
+
| `npm install` | Install dependencies + auto-build workspaces + Brave check |
|
|
686
687
|
| `npm test` | Run all tests (CJS + ESM) |
|
|
687
688
|
| `npm run cjs_test` | Run CommonJS tests only |
|
|
688
689
|
| `npm run esm_test` | Run ESM tests only |
|
|
689
|
-
| `npm run build` | Build root package |
|
|
690
|
-
| `npm run build:all` | Build all workspace packages |
|
|
690
|
+
| `npm run build` | Build root package (pre-built lib/) |
|
|
691
|
+
| `npm run build:all` | Build all workspace packages (blocker, launcher, puppeteer-core) |
|
|
692
|
+
| `npm run clean:all` | Clean all workspace build outputs |
|
|
693
|
+
| `npm run test:all` | Run tests for all workspace packages |
|
|
694
|
+
| `npm run update-deps` | Manually check for dependency updates |
|
|
695
|
+
| `npm run upstream-patch` | Update to latest upstream (puppeteer-core, playwright-core) |
|
|
691
696
|
| `npm run lint` | Run linter |
|
|
692
697
|
|
|
698
|
+
> **Note:** `npm install` automatically runs `npm run build:all` to compile TypeScript packages (brave-real-blocker, brave-real-launcher) before the auto-update check. This ensures the browser tools work immediately after installation.
|
|
699
|
+
|
|
693
700
|
### Workspace Scripts
|
|
694
701
|
|
|
695
702
|
| Command | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.43.0",
|
|
4
4
|
"description": "MCP Server for Brave Real Browser - Puppeteer with Brave Browser, Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.mjs",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"build:all": "npm run build --workspaces --if-present",
|
|
44
44
|
"clean:all": "npm run clean --workspaces --if-present",
|
|
45
45
|
"test:all": "npm run test --workspaces --if-present",
|
|
46
|
-
"postinstall": "node scripts/auto-update-deps.js || true",
|
|
46
|
+
"postinstall": "npm run build:all && node scripts/auto-update-deps.js || true",
|
|
47
47
|
"update-deps": "node scripts/auto-update-deps.js",
|
|
48
48
|
"upstream-patch": "node scripts/upstream-patcher.js"
|
|
49
49
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"license": "ISC",
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
77
|
-
"brave-real-puppeteer-core": "^24.37.2-brave.
|
|
77
|
+
"brave-real-puppeteer-core": "^24.37.2-brave.6",
|
|
78
78
|
"ghost-cursor": "^1.4.2",
|
|
79
79
|
"puppeteer-extra": "^3.3.6",
|
|
80
80
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-blocker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Advanced uBlock Origin management and stealth features for Brave Real Browser",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@types/adm-zip": "^0.5.5",
|
|
65
65
|
"@types/fs-extra": "^11.0.4",
|
|
66
66
|
"@types/node": "^20.0.0",
|
|
67
|
-
"brave-real-puppeteer-core": "^24.37.2-brave.
|
|
67
|
+
"brave-real-puppeteer-core": "^24.37.2-brave.6",
|
|
68
68
|
"mocha": "^10.4.0",
|
|
69
69
|
"puppeteer-core": ">=24.0.0",
|
|
70
70
|
"sinon": "^17.0.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-launcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Launch Brave Browser with ease from node. Based on chrome-launcher with Brave-specific support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"typescript": "^5.0.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"brave-real-blocker": "^1.
|
|
57
|
+
"brave-real-blocker": "^1.19.0",
|
|
58
58
|
"escape-string-regexp": "^5.0.0",
|
|
59
59
|
"is-wsl": "^3.1.0",
|
|
60
60
|
"which": "^6.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-puppeteer-core",
|
|
3
|
-
"version": "24.37.2-brave.
|
|
3
|
+
"version": "24.37.2-brave.6",
|
|
4
4
|
"description": "🦁 Brave Real-World Optimized Puppeteer & Playwright Core with 1-5ms ultra-fast timing, 50+ professional stealth features, intelligent browser auto-detection, and 100% bot detection bypass. Features cross-platform Brave browser integration, comprehensive anti-detection, and breakthrough performance improvements.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
},
|
|
136
136
|
"dependencies": {
|
|
137
137
|
"@puppeteer/browsers": "2.12.0",
|
|
138
|
-
"brave-real-launcher": "^1.
|
|
138
|
+
"brave-real-launcher": "^1.25.0",
|
|
139
139
|
"chromium-bidi": "13.1.1",
|
|
140
140
|
"debug": "^4.4.3",
|
|
141
141
|
"devtools-protocol": "0.0.1566079",
|
package/src/mcp/handlers.js
CHANGED
|
@@ -793,17 +793,209 @@ const handlers = {
|
|
|
793
793
|
return { success: true, type, value };
|
|
794
794
|
},
|
|
795
795
|
|
|
796
|
-
// 5. Click
|
|
796
|
+
// 5. Click (ENHANCED: iframe + hover + auto video player detection)
|
|
797
797
|
async click(params) {
|
|
798
798
|
const { page } = requireBrowser();
|
|
799
|
-
const {
|
|
799
|
+
const {
|
|
800
|
+
selector,
|
|
801
|
+
humanLike = true,
|
|
802
|
+
clickCount = 1,
|
|
803
|
+
delay = 0,
|
|
804
|
+
autoAcceptDialogs = true,
|
|
805
|
+
retries = 3,
|
|
806
|
+
timeout = 60000,
|
|
807
|
+
// Hover support for video player dynamic controls
|
|
808
|
+
hoverFirst = false,
|
|
809
|
+
hoverOnly = false,
|
|
810
|
+
hoverDuration = 500,
|
|
811
|
+
// iframe support
|
|
812
|
+
iframe,
|
|
813
|
+
iframeSelector,
|
|
814
|
+
// Additional options
|
|
815
|
+
scrollIntoView = true,
|
|
816
|
+
forceClick = false,
|
|
817
|
+
// NEW: Auto Video Player Detection & Control
|
|
818
|
+
autoDetectPlayer = false,
|
|
819
|
+
usePlayerAPI = true,
|
|
820
|
+
waitForPlay = false,
|
|
821
|
+
playerTimeout = 15000
|
|
822
|
+
} = params;
|
|
823
|
+
|
|
824
|
+
notifyProgress('click', 'started', `${hoverOnly ? 'Hovering' : 'Clicking'}: ${selector}${iframe !== undefined ? ` (iframe ${iframe})` : ''}${autoDetectPlayer ? ' (auto-detect player)' : ''}`);
|
|
800
825
|
|
|
801
|
-
|
|
826
|
+
// Get the correct context (page or iframe)
|
|
827
|
+
let context = page;
|
|
828
|
+
let frameInfo = null;
|
|
829
|
+
let detectedPlayer = null;
|
|
830
|
+
|
|
831
|
+
// ═══════════════════════════════════════════════════════════════
|
|
832
|
+
// AUTO DETECT VIDEO PLAYER - Scan all iframes for video players
|
|
833
|
+
// Supports: JWPlayer, VideoJS, Plyr, VidStack, DooPlayer, HTML5
|
|
834
|
+
// ═══════════════════════════════════════════════════════════════
|
|
835
|
+
if (autoDetectPlayer) {
|
|
836
|
+
notifyProgress('click', 'progress', '🔍 Scanning all iframes for video players...');
|
|
837
|
+
|
|
838
|
+
const frames = page.frames();
|
|
839
|
+
|
|
840
|
+
for (let i = 0; i < frames.length; i++) {
|
|
841
|
+
try {
|
|
842
|
+
const frame = frames[i];
|
|
843
|
+
const frameUrl = frame.url();
|
|
844
|
+
|
|
845
|
+
// Skip blank frames
|
|
846
|
+
if (frameUrl === 'about:blank' || !frameUrl) continue;
|
|
847
|
+
|
|
848
|
+
// Detect player in this frame
|
|
849
|
+
const playerInfo = await frame.evaluate(() => {
|
|
850
|
+
const result = {
|
|
851
|
+
hasPlayer: false,
|
|
852
|
+
playerType: null,
|
|
853
|
+
hasVideo: false,
|
|
854
|
+
videoState: null,
|
|
855
|
+
controls: [],
|
|
856
|
+
downloadButton: null
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Check for video element
|
|
860
|
+
const video = document.querySelector('video');
|
|
861
|
+
if (video) {
|
|
862
|
+
result.hasVideo = true;
|
|
863
|
+
result.videoState = {
|
|
864
|
+
paused: video.paused,
|
|
865
|
+
currentTime: video.currentTime,
|
|
866
|
+
duration: video.duration,
|
|
867
|
+
readyState: video.readyState
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 1. JWPlayer Detection
|
|
872
|
+
if (window.jwplayer && typeof window.jwplayer === 'function') {
|
|
873
|
+
try {
|
|
874
|
+
const jw = window.jwplayer();
|
|
875
|
+
if (jw && jw.getState) {
|
|
876
|
+
result.hasPlayer = true;
|
|
877
|
+
result.playerType = 'jwplayer';
|
|
878
|
+
result.playerState = jw.getState();
|
|
879
|
+
result.controls.push('.jw-icon-display', '.jw-icon-playback', '[aria-label="Play"]');
|
|
880
|
+
|
|
881
|
+
// Find download button in JWPlayer
|
|
882
|
+
const dlBtn = document.querySelector('[aria-label="Download"], .jw-icon-download, [class*="download"]');
|
|
883
|
+
if (dlBtn) result.downloadButton = '[aria-label="Download"]';
|
|
884
|
+
}
|
|
885
|
+
} catch (e) {}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// 2. VideoJS Detection
|
|
889
|
+
if (window.videojs || document.querySelector('.video-js')) {
|
|
890
|
+
result.hasPlayer = true;
|
|
891
|
+
result.playerType = result.playerType || 'videojs';
|
|
892
|
+
result.controls.push('.vjs-big-play-button', '.vjs-play-control');
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// 3. Plyr Detection
|
|
896
|
+
if (window.Plyr || document.querySelector('.plyr')) {
|
|
897
|
+
result.hasPlayer = true;
|
|
898
|
+
result.playerType = result.playerType || 'plyr';
|
|
899
|
+
result.controls.push('.plyr__control--play', '[data-plyr="play"]');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// 4. VidStack Detection
|
|
903
|
+
if (window.VidStack || document.querySelector('media-player')) {
|
|
904
|
+
result.hasPlayer = true;
|
|
905
|
+
result.playerType = result.playerType || 'vidstack';
|
|
906
|
+
result.controls.push('media-play-button', '[data-media-play]');
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 5. DooPlayer Detection
|
|
910
|
+
if (window.DooPlay || document.querySelector('#dooplay') || document.querySelector('.dooplay')) {
|
|
911
|
+
result.hasPlayer = true;
|
|
912
|
+
result.playerType = result.playerType || 'dooplayer';
|
|
913
|
+
result.controls.push('.play-btn', '.dooplay-play');
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// 6. Generic HTML5 Video
|
|
917
|
+
if (result.hasVideo && !result.hasPlayer) {
|
|
918
|
+
result.hasPlayer = true;
|
|
919
|
+
result.playerType = 'html5';
|
|
920
|
+
result.controls.push('video');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Find any download button
|
|
924
|
+
if (!result.downloadButton) {
|
|
925
|
+
const dlSelectors = [
|
|
926
|
+
'[aria-label="Download"]', '[aria-label*="download"]',
|
|
927
|
+
'.download-btn', '.download', '[class*="download"]',
|
|
928
|
+
'a[download]', 'button[class*="download"]'
|
|
929
|
+
];
|
|
930
|
+
for (const sel of dlSelectors) {
|
|
931
|
+
if (document.querySelector(sel)) {
|
|
932
|
+
result.downloadButton = sel;
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return result;
|
|
939
|
+
}).catch(() => ({ hasPlayer: false }));
|
|
940
|
+
|
|
941
|
+
if (playerInfo.hasPlayer) {
|
|
942
|
+
context = frame;
|
|
943
|
+
frameInfo = {
|
|
944
|
+
index: i,
|
|
945
|
+
url: frameUrl,
|
|
946
|
+
autoDetected: true
|
|
947
|
+
};
|
|
948
|
+
detectedPlayer = {
|
|
949
|
+
type: playerInfo.playerType,
|
|
950
|
+
state: playerInfo.playerState || playerInfo.videoState,
|
|
951
|
+
controls: playerInfo.controls,
|
|
952
|
+
downloadButton: playerInfo.downloadButton
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
notifyProgress('click', 'progress',
|
|
956
|
+
`✅ Found ${playerInfo.playerType.toUpperCase()} in iframe ${i}: ${frameUrl.substring(0, 50)}...`);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
} catch (e) {
|
|
960
|
+
// Skip frames that can't be accessed
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (!detectedPlayer) {
|
|
966
|
+
notifyProgress('click', 'progress', '⚠️ No video player found in any iframe, using main page');
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Manual iframe selection (if not auto-detected)
|
|
971
|
+
if (!autoDetectPlayer && (iframe !== undefined || iframeSelector)) {
|
|
972
|
+
try {
|
|
973
|
+
const frames = page.frames();
|
|
974
|
+
|
|
975
|
+
if (iframe !== undefined && frames[iframe]) {
|
|
976
|
+
context = frames[iframe];
|
|
977
|
+
frameInfo = { index: iframe, url: frames[iframe].url() };
|
|
978
|
+
notifyProgress('click', 'progress', `Switched to iframe ${iframe}: ${frames[iframe].url().substring(0, 50)}...`);
|
|
979
|
+
} else if (iframeSelector) {
|
|
980
|
+
const iframeHandle = await page.$(iframeSelector);
|
|
981
|
+
if (iframeHandle) {
|
|
982
|
+
const frame = await iframeHandle.contentFrame();
|
|
983
|
+
if (frame) {
|
|
984
|
+
context = frame;
|
|
985
|
+
frameInfo = { selector: iframeSelector, url: frame.url() };
|
|
986
|
+
notifyProgress('click', 'progress', `Switched to iframe by selector: ${iframeSelector}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch (e) {
|
|
991
|
+
notifyProgress('click', 'progress', `Warning: Could not switch to iframe - ${e.message}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
802
994
|
|
|
803
995
|
// Auto-close any blocking modals before clicking
|
|
804
996
|
await handlers._handleBlockingModals(page);
|
|
805
997
|
|
|
806
|
-
// Auto-handle dialogs
|
|
998
|
+
// Auto-handle dialogs
|
|
807
999
|
let dialogHandled = false;
|
|
808
1000
|
const dialogHandler = async (dialog) => {
|
|
809
1001
|
dialogHandled = true;
|
|
@@ -812,64 +1004,326 @@ const handlers = {
|
|
|
812
1004
|
notifyProgress('click', 'progress', `🔔 Auto-accepting ${type}: ${message.substring(0, 50)}...`);
|
|
813
1005
|
try {
|
|
814
1006
|
await dialog.accept();
|
|
815
|
-
} catch (e) {
|
|
816
|
-
// Ignore if already handled by global handler
|
|
817
|
-
}
|
|
1007
|
+
} catch (e) { }
|
|
818
1008
|
};
|
|
819
1009
|
|
|
820
1010
|
if (autoAcceptDialogs) {
|
|
821
1011
|
page.on('dialog', dialogHandler);
|
|
822
1012
|
}
|
|
823
1013
|
|
|
1014
|
+
let lastError = null;
|
|
1015
|
+
let playerResult = null;
|
|
1016
|
+
|
|
824
1017
|
try {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1018
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1019
|
+
// USE PLAYER API - More reliable than DOM click for video players
|
|
1020
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1021
|
+
if (usePlayerAPI && detectedPlayer && (selector === 'video' || selector.includes('play') || selector.includes('Play'))) {
|
|
1022
|
+
notifyProgress('click', 'progress', `🎬 Using ${detectedPlayer.type} API for playback...`);
|
|
1023
|
+
|
|
1024
|
+
playerResult = await context.evaluate((playerType) => {
|
|
1025
|
+
const result = { success: false, method: null, state: null };
|
|
1026
|
+
|
|
1027
|
+
try {
|
|
1028
|
+
if (playerType === 'jwplayer' && window.jwplayer) {
|
|
1029
|
+
const jw = window.jwplayer();
|
|
1030
|
+
const stateBefore = jw.getState();
|
|
1031
|
+
jw.play();
|
|
1032
|
+
result.success = true;
|
|
1033
|
+
result.method = 'jwplayer.play()';
|
|
1034
|
+
result.stateBefore = stateBefore;
|
|
1035
|
+
result.stateAfter = jw.getState();
|
|
1036
|
+
} else if (playerType === 'videojs' && window.videojs) {
|
|
1037
|
+
const player = window.videojs.getPlayers()[Object.keys(window.videojs.getPlayers())[0]];
|
|
1038
|
+
if (player) {
|
|
1039
|
+
player.play();
|
|
1040
|
+
result.success = true;
|
|
1041
|
+
result.method = 'videojs.play()';
|
|
1042
|
+
}
|
|
1043
|
+
} else if (playerType === 'plyr' && window.Plyr) {
|
|
1044
|
+
const plyr = document.querySelector('.plyr')?.__plyr;
|
|
1045
|
+
if (plyr) {
|
|
1046
|
+
plyr.play();
|
|
1047
|
+
result.success = true;
|
|
1048
|
+
result.method = 'plyr.play()';
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
// Fallback to HTML5 video
|
|
1052
|
+
const video = document.querySelector('video');
|
|
1053
|
+
if (video) {
|
|
1054
|
+
video.play();
|
|
1055
|
+
result.success = true;
|
|
1056
|
+
result.method = 'video.play()';
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
} catch (e) {
|
|
1060
|
+
result.error = e.message;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return result;
|
|
1064
|
+
}, detectedPlayer.type).catch(e => ({ success: false, error: e.message }));
|
|
1065
|
+
|
|
1066
|
+
if (playerResult.success) {
|
|
1067
|
+
notifyProgress('click', 'progress', `✅ ${playerResult.method} executed`);
|
|
1068
|
+
|
|
1069
|
+
// Wait for play if requested
|
|
1070
|
+
if (waitForPlay) {
|
|
1071
|
+
notifyProgress('click', 'progress', '⏳ Waiting for video to start playing...');
|
|
1072
|
+
|
|
1073
|
+
const startTime = Date.now();
|
|
1074
|
+
let isPlaying = false;
|
|
1075
|
+
|
|
1076
|
+
while (Date.now() - startTime < playerTimeout) {
|
|
1077
|
+
const state = await context.evaluate(() => {
|
|
1078
|
+
const video = document.querySelector('video');
|
|
1079
|
+
if (video) {
|
|
1080
|
+
return {
|
|
1081
|
+
paused: video.paused,
|
|
1082
|
+
currentTime: video.currentTime,
|
|
1083
|
+
playing: !video.paused && video.currentTime > 0
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (window.jwplayer) {
|
|
1087
|
+
const jw = window.jwplayer();
|
|
1088
|
+
return { playing: jw.getState() === 'playing', jwState: jw.getState() };
|
|
1089
|
+
}
|
|
1090
|
+
return { playing: false };
|
|
1091
|
+
}).catch(() => ({ playing: false }));
|
|
1092
|
+
|
|
1093
|
+
if (state.playing || state.currentTime > 0) {
|
|
1094
|
+
isPlaying = true;
|
|
1095
|
+
notifyProgress('click', 'progress', `▶️ Video is now playing (${state.currentTime?.toFixed(1) || 0}s)`);
|
|
1096
|
+
break;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (!isPlaying) {
|
|
1103
|
+
notifyProgress('click', 'progress', '⚠️ Video may still be buffering');
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
notifyProgress('click', 'completed', `Video playback started via ${playerResult.method}`, {
|
|
1108
|
+
selector,
|
|
1109
|
+
clicked: true,
|
|
1110
|
+
playerAPI: true,
|
|
1111
|
+
detectedPlayer,
|
|
1112
|
+
iframe: frameInfo,
|
|
1113
|
+
playerResult
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
return {
|
|
1117
|
+
success: true,
|
|
1118
|
+
selector,
|
|
1119
|
+
clicked: true,
|
|
1120
|
+
playerAPI: true,
|
|
1121
|
+
detectedPlayer,
|
|
1122
|
+
iframe: frameInfo,
|
|
1123
|
+
playerResult,
|
|
1124
|
+
dialogHandled
|
|
1125
|
+
};
|
|
833
1126
|
}
|
|
834
|
-
} else {
|
|
835
|
-
await page.click(selector, { clickCount, delay });
|
|
836
1127
|
}
|
|
837
1128
|
|
|
838
|
-
//
|
|
839
|
-
|
|
1129
|
+
// Retry loop for regular click
|
|
1130
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
1131
|
+
try {
|
|
1132
|
+
// Wait for selector with timeout
|
|
1133
|
+
try {
|
|
1134
|
+
await context.waitForSelector(selector, { timeout: Math.min(timeout / retries, 10000) });
|
|
1135
|
+
} catch (e) {
|
|
1136
|
+
if (attempt < retries) {
|
|
1137
|
+
notifyProgress('click', 'progress', `Selector not found, retry ${attempt}/${retries}...`);
|
|
1138
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
throw new Error(`Selector not found: ${selector}`);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Scroll into view if needed
|
|
1145
|
+
if (scrollIntoView) {
|
|
1146
|
+
await context.evaluate((sel) => {
|
|
1147
|
+
const el = document.querySelector(sel);
|
|
1148
|
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1149
|
+
}, selector);
|
|
1150
|
+
await new Promise(r => setTimeout(r, 300));
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// HOVER functionality (for video player dynamic controls)
|
|
1154
|
+
if (hoverFirst || hoverOnly) {
|
|
1155
|
+
notifyProgress('click', 'progress', `Hovering over ${selector}...`);
|
|
1156
|
+
|
|
1157
|
+
try {
|
|
1158
|
+
await context.hover(selector);
|
|
1159
|
+
notifyProgress('click', 'progress', `Hover successful, waiting ${hoverDuration}ms for controls...`);
|
|
1160
|
+
await new Promise(r => setTimeout(r, hoverDuration));
|
|
1161
|
+
} catch (hoverErr) {
|
|
1162
|
+
notifyProgress('click', 'progress', `Standard hover failed, trying mouse movement...`);
|
|
1163
|
+
const element = await context.$(selector);
|
|
1164
|
+
if (element) {
|
|
1165
|
+
const box = await element.boundingBox();
|
|
1166
|
+
if (box) {
|
|
1167
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
1168
|
+
await new Promise(r => setTimeout(r, hoverDuration));
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (hoverOnly) {
|
|
1174
|
+
notifyProgress('click', 'completed', `Hover completed: ${selector}`, { selector, hovered: true, iframe: frameInfo });
|
|
1175
|
+
return { success: true, selector, hovered: true, clicked: false, iframe: frameInfo, detectedPlayer };
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// CLICK functionality
|
|
1180
|
+
if (forceClick) {
|
|
1181
|
+
await context.evaluate((sel) => {
|
|
1182
|
+
const el = document.querySelector(sel);
|
|
1183
|
+
if (el) el.click();
|
|
1184
|
+
}, selector);
|
|
1185
|
+
notifyProgress('click', 'progress', 'Used force click (JS)');
|
|
1186
|
+
} else if (humanLike) {
|
|
1187
|
+
try {
|
|
1188
|
+
const { createCursor } = require('ghost-cursor');
|
|
1189
|
+
const cursor = createCursor(page);
|
|
1190
|
+
|
|
1191
|
+
if (context !== page) {
|
|
1192
|
+
const element = await context.$(selector);
|
|
1193
|
+
if (element) {
|
|
1194
|
+
const box = await element.boundingBox();
|
|
1195
|
+
if (box) {
|
|
1196
|
+
await cursor.moveTo({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
|
|
1197
|
+
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { clickCount, delay });
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
} else {
|
|
1201
|
+
await cursor.click(selector);
|
|
1202
|
+
}
|
|
1203
|
+
notifyProgress('click', 'progress', 'Used human-like cursor movement');
|
|
1204
|
+
} catch (e) {
|
|
1205
|
+
await context.click(selector, { clickCount, delay });
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
await context.click(selector, { clickCount, delay });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
await new Promise(r => setTimeout(r, 300));
|
|
1212
|
+
|
|
1213
|
+
notifyProgress('click', 'completed',
|
|
1214
|
+
`${hoverFirst ? 'Hovered+' : ''}Clicked: ${selector}${dialogHandled ? ' (dialog auto-accepted)' : ''}`,
|
|
1215
|
+
{ selector, humanLike, dialogHandled, iframe: frameInfo, detectedPlayer, attempts: attempt }
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
return {
|
|
1219
|
+
success: true,
|
|
1220
|
+
selector,
|
|
1221
|
+
clicked: true,
|
|
1222
|
+
dialogHandled,
|
|
1223
|
+
iframe: frameInfo,
|
|
1224
|
+
detectedPlayer,
|
|
1225
|
+
attempts: attempt
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
} catch (attemptError) {
|
|
1229
|
+
lastError = attemptError;
|
|
1230
|
+
if (attempt < retries) {
|
|
1231
|
+
notifyProgress('click', 'progress', `Attempt ${attempt} failed: ${attemptError.message}, retrying...`);
|
|
1232
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
840
1236
|
|
|
841
|
-
|
|
1237
|
+
throw lastError || new Error('Click failed after all retries');
|
|
842
1238
|
|
|
843
|
-
return { success: true, selector, clicked: true, dialogHandled };
|
|
844
1239
|
} finally {
|
|
845
|
-
// Remove dialog handler to prevent memory leaks
|
|
846
1240
|
if (autoAcceptDialogs) {
|
|
847
1241
|
page.off('dialog', dialogHandler);
|
|
848
1242
|
}
|
|
849
1243
|
}
|
|
850
1244
|
},
|
|
851
1245
|
|
|
852
|
-
// 6. Type
|
|
1246
|
+
// 6. Type (ENHANCED: iframe support)
|
|
853
1247
|
async type(params) {
|
|
854
1248
|
const { page } = requireBrowser();
|
|
855
|
-
const {
|
|
1249
|
+
const {
|
|
1250
|
+
selector,
|
|
1251
|
+
text,
|
|
1252
|
+
delay = 50,
|
|
1253
|
+
clear = false,
|
|
1254
|
+
// NEW: iframe support
|
|
1255
|
+
iframe,
|
|
1256
|
+
iframeSelector,
|
|
1257
|
+
// NEW: Additional options
|
|
1258
|
+
pressEnter = false,
|
|
1259
|
+
waitForSelector = true
|
|
1260
|
+
} = params;
|
|
1261
|
+
|
|
1262
|
+
notifyProgress('type', 'started', `Typing ${text.length} characters into ${selector}${iframe !== undefined ? ` (iframe ${iframe})` : ''}`);
|
|
856
1263
|
|
|
857
|
-
|
|
1264
|
+
// Get the correct context (page or iframe)
|
|
1265
|
+
let context = page;
|
|
1266
|
+
let frameInfo = null;
|
|
1267
|
+
|
|
1268
|
+
if (iframe !== undefined || iframeSelector) {
|
|
1269
|
+
try {
|
|
1270
|
+
const frames = page.frames();
|
|
1271
|
+
|
|
1272
|
+
if (iframe !== undefined && frames[iframe]) {
|
|
1273
|
+
context = frames[iframe];
|
|
1274
|
+
frameInfo = { index: iframe, url: frames[iframe].url() };
|
|
1275
|
+
notifyProgress('type', 'progress', `Switched to iframe ${iframe}`);
|
|
1276
|
+
} else if (iframeSelector) {
|
|
1277
|
+
const iframeHandle = await page.$(iframeSelector);
|
|
1278
|
+
if (iframeHandle) {
|
|
1279
|
+
const frame = await iframeHandle.contentFrame();
|
|
1280
|
+
if (frame) {
|
|
1281
|
+
context = frame;
|
|
1282
|
+
frameInfo = { selector: iframeSelector, url: frame.url() };
|
|
1283
|
+
notifyProgress('type', 'progress', `Switched to iframe by selector`);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
notifyProgress('type', 'progress', `Warning: Could not switch to iframe - ${e.message}`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
858
1291
|
|
|
859
1292
|
// Auto-close any blocking modals before typing
|
|
860
1293
|
await handlers._handleBlockingModals(page);
|
|
861
1294
|
|
|
1295
|
+
// Wait for selector if enabled
|
|
1296
|
+
if (waitForSelector) {
|
|
1297
|
+
try {
|
|
1298
|
+
await context.waitForSelector(selector, { timeout: 10000 });
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
notifyProgress('type', 'error', `Selector not found: ${selector}`);
|
|
1301
|
+
return { success: false, error: `Selector not found: ${selector}` };
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Clear existing text if needed
|
|
862
1306
|
if (clear) {
|
|
863
|
-
await
|
|
864
|
-
await
|
|
1307
|
+
await context.click(selector, { clickCount: 3 });
|
|
1308
|
+
await context.evaluate((sel) => {
|
|
1309
|
+
const el = document.querySelector(sel);
|
|
1310
|
+
if (el) el.value = '';
|
|
1311
|
+
}, selector);
|
|
865
1312
|
notifyProgress('type', 'progress', 'Cleared existing text');
|
|
866
1313
|
}
|
|
867
1314
|
|
|
868
|
-
|
|
1315
|
+
// Type text with human-like delays
|
|
1316
|
+
await context.type(selector, text, { delay });
|
|
1317
|
+
|
|
1318
|
+
// Press Enter if requested
|
|
1319
|
+
if (pressEnter) {
|
|
1320
|
+
await context.keyboard.press('Enter');
|
|
1321
|
+
notifyProgress('type', 'progress', 'Pressed Enter');
|
|
1322
|
+
}
|
|
869
1323
|
|
|
870
|
-
notifyProgress('type', 'completed', `Typed ${text.length} characters`, { selector, textLength: text.length });
|
|
1324
|
+
notifyProgress('type', 'completed', `Typed ${text.length} characters`, { selector, textLength: text.length, iframe: frameInfo });
|
|
871
1325
|
|
|
872
|
-
return { success: true, selector, textLength: text.length };
|
|
1326
|
+
return { success: true, selector, textLength: text.length, iframe: frameInfo };
|
|
873
1327
|
},
|
|
874
1328
|
|
|
875
1329
|
// 7. Browser Close
|
|
@@ -2295,18 +2749,88 @@ const handlers = {
|
|
|
2295
2749
|
return { success: true, selector, content };
|
|
2296
2750
|
},
|
|
2297
2751
|
|
|
2298
|
-
// 26. Execute JS
|
|
2752
|
+
// 26. Execute JS (ENHANCED: iframe context support - FIXED)
|
|
2299
2753
|
async execute_js(params) {
|
|
2300
2754
|
const { page } = requireBrowser();
|
|
2301
|
-
const {
|
|
2755
|
+
const {
|
|
2756
|
+
code,
|
|
2757
|
+
returnValue = true,
|
|
2758
|
+
// NEW: iframe support (FIXED)
|
|
2759
|
+
iframe,
|
|
2760
|
+
iframeSelector,
|
|
2761
|
+
waitForIframe = true,
|
|
2762
|
+
timeout = 30000
|
|
2763
|
+
} = params;
|
|
2764
|
+
|
|
2765
|
+
notifyProgress('execute_js', 'started', `Executing JavaScript...${iframe !== undefined ? ` (iframe ${iframe})` : ''}`);
|
|
2766
|
+
|
|
2767
|
+
// Get the correct context (page or iframe)
|
|
2768
|
+
let context = page;
|
|
2769
|
+
let frameInfo = null;
|
|
2770
|
+
|
|
2771
|
+
if (iframe !== undefined || iframeSelector) {
|
|
2772
|
+
try {
|
|
2773
|
+
const frames = page.frames();
|
|
2774
|
+
|
|
2775
|
+
if (iframe !== undefined) {
|
|
2776
|
+
// iframe index provided
|
|
2777
|
+
if (iframe === 0) {
|
|
2778
|
+
// index 0 = main frame
|
|
2779
|
+
context = page.mainFrame();
|
|
2780
|
+
frameInfo = { index: 0, url: page.url(), isMain: true };
|
|
2781
|
+
} else if (frames[iframe]) {
|
|
2782
|
+
context = frames[iframe];
|
|
2783
|
+
frameInfo = { index: iframe, url: frames[iframe].url() };
|
|
2784
|
+
notifyProgress('execute_js', 'progress', `Switched to iframe ${iframe}: ${frames[iframe].url().substring(0, 50)}...`);
|
|
2785
|
+
} else {
|
|
2786
|
+
notifyProgress('execute_js', 'error', `iframe index ${iframe} not found. Total frames: ${frames.length}`);
|
|
2787
|
+
return { success: false, error: `iframe index ${iframe} not found. Available: 0-${frames.length - 1}` };
|
|
2788
|
+
}
|
|
2789
|
+
} else if (iframeSelector) {
|
|
2790
|
+
// Find iframe by selector
|
|
2791
|
+
const iframeHandle = await page.$(iframeSelector);
|
|
2792
|
+
if (iframeHandle) {
|
|
2793
|
+
const frame = await iframeHandle.contentFrame();
|
|
2794
|
+
if (frame) {
|
|
2795
|
+
context = frame;
|
|
2796
|
+
frameInfo = { selector: iframeSelector, url: frame.url() };
|
|
2797
|
+
notifyProgress('execute_js', 'progress', `Switched to iframe by selector: ${iframeSelector}`);
|
|
2798
|
+
}
|
|
2799
|
+
} else {
|
|
2800
|
+
return { success: false, error: `iframe selector not found: ${iframeSelector}` };
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// Wait for iframe to be ready if needed
|
|
2805
|
+
if (waitForIframe && context !== page) {
|
|
2806
|
+
try {
|
|
2807
|
+
await context.waitForFunction(() => document.readyState === 'complete', { timeout: 5000 });
|
|
2808
|
+
} catch (e) {
|
|
2809
|
+
notifyProgress('execute_js', 'progress', 'Warning: iframe may not be fully loaded');
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2302
2812
|
|
|
2303
|
-
|
|
2813
|
+
} catch (e) {
|
|
2814
|
+
notifyProgress('execute_js', 'error', `iframe switch failed: ${e.message}`);
|
|
2815
|
+
return { success: false, error: `iframe switch failed: ${e.message}` };
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2304
2818
|
|
|
2305
|
-
|
|
2819
|
+
try {
|
|
2820
|
+
// Execute the code in the correct context
|
|
2821
|
+
const result = await context.evaluate(code);
|
|
2822
|
+
|
|
2823
|
+
notifyProgress('execute_js', 'completed', 'JavaScript executed', {
|
|
2824
|
+
hasResult: result !== undefined,
|
|
2825
|
+
iframe: frameInfo
|
|
2826
|
+
});
|
|
2306
2827
|
|
|
2307
|
-
|
|
2828
|
+
return { success: true, result: returnValue ? result : undefined, iframe: frameInfo };
|
|
2308
2829
|
|
|
2309
|
-
|
|
2830
|
+
} catch (evalError) {
|
|
2831
|
+
notifyProgress('execute_js', 'error', `Execution error: ${evalError.message}`);
|
|
2832
|
+
return { success: false, error: evalError.message, iframe: frameInfo };
|
|
2833
|
+
}
|
|
2310
2834
|
},
|
|
2311
2835
|
|
|
2312
2836
|
// 27. Player API Hook (ENHANCED - searches iframes, detects more player types)
|
package/src/shared/tools.js
CHANGED
|
@@ -111,12 +111,12 @@ const TOOLS = [
|
|
|
111
111
|
}
|
|
112
112
|
},
|
|
113
113
|
|
|
114
|
-
// 5. Click
|
|
114
|
+
// 5. Click (ENHANCED: iframe + hover + auto video player detection)
|
|
115
115
|
{
|
|
116
116
|
name: 'click',
|
|
117
117
|
emoji: '👆',
|
|
118
|
-
description: 'Human-like click with AI healing and auto
|
|
119
|
-
descriptionHindi: 'क्लिक करना (AI healing +
|
|
118
|
+
description: 'Human-like click with AI healing, iframe support, hover for dynamic controls, and auto video player detection',
|
|
119
|
+
descriptionHindi: 'क्लिक करना (AI healing + iframe + auto video player detection)',
|
|
120
120
|
category: 'interaction',
|
|
121
121
|
requiresBrowser: true,
|
|
122
122
|
requiresPage: true,
|
|
@@ -127,20 +127,36 @@ const TOOLS = [
|
|
|
127
127
|
humanLike: { type: 'boolean', default: true, description: 'Ghost cursor human movement' },
|
|
128
128
|
aiHeal: { type: 'boolean', default: true, description: 'Auto-find alternative selector if broken' },
|
|
129
129
|
autoAcceptDialogs: { type: 'boolean', default: true, description: 'Auto-accept alerts/confirms to prevent blocking' },
|
|
130
|
-
retries: { type: 'number', default: 3 },
|
|
130
|
+
retries: { type: 'number', default: 3, description: 'Auto-retry on failure' },
|
|
131
131
|
clickCount: { type: 'number', default: 1 },
|
|
132
|
-
delay: { type: 'number', default: 0 }
|
|
132
|
+
delay: { type: 'number', default: 0 },
|
|
133
|
+
timeout: { type: 'number', default: 60000, description: 'Timeout for element to appear' },
|
|
134
|
+
// Hover support for video player dynamic controls
|
|
135
|
+
hoverFirst: { type: 'boolean', default: false, description: 'Hover before click (for dynamic controls like video players)' },
|
|
136
|
+
hoverOnly: { type: 'boolean', default: false, description: 'Only hover, do not click (to reveal hidden controls)' },
|
|
137
|
+
hoverDuration: { type: 'number', default: 500, description: 'Wait time after hover before click (ms)' },
|
|
138
|
+
// iframe support
|
|
139
|
+
iframe: { type: 'number', description: 'Execute in specific iframe index (use media_extractor list_iframes to get index)' },
|
|
140
|
+
iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector instead of index' },
|
|
141
|
+
// Scroll into view
|
|
142
|
+
scrollIntoView: { type: 'boolean', default: true, description: 'Auto-scroll element into view before click' },
|
|
143
|
+
forceClick: { type: 'boolean', default: false, description: 'Force click even if element not visible (use JS click)' },
|
|
144
|
+
// NEW: Auto Video Player Detection & Control
|
|
145
|
+
autoDetectPlayer: { type: 'boolean', default: false, description: 'Auto-detect video player iframe (JWPlayer, VideoJS, Plyr, VidStack, DooPlayer)' },
|
|
146
|
+
usePlayerAPI: { type: 'boolean', default: true, description: 'Use player API (jwplayer.play()) instead of DOM click for reliable playback' },
|
|
147
|
+
waitForPlay: { type: 'boolean', default: false, description: 'Wait until video actually starts playing' },
|
|
148
|
+
playerTimeout: { type: 'number', default: 15000, description: 'Max wait time for video to start playing (ms)' }
|
|
133
149
|
},
|
|
134
150
|
required: ['selector']
|
|
135
151
|
}
|
|
136
152
|
},
|
|
137
153
|
|
|
138
|
-
// 6. Type
|
|
154
|
+
// 6. Type (ENHANCED: iframe support)
|
|
139
155
|
{
|
|
140
156
|
name: 'type',
|
|
141
157
|
emoji: '⌨️',
|
|
142
|
-
description: 'Type text with human speed variation and
|
|
143
|
-
descriptionHindi: 'टेक्स्ट टाइप करना (human speed)',
|
|
158
|
+
description: 'Type text with human speed variation, smart clearing, and iframe support',
|
|
159
|
+
descriptionHindi: 'टेक्स्ट टाइप करना (human speed + iframe support)',
|
|
144
160
|
category: 'interaction',
|
|
145
161
|
requiresBrowser: true,
|
|
146
162
|
requiresPage: true,
|
|
@@ -151,7 +167,13 @@ const TOOLS = [
|
|
|
151
167
|
text: { type: 'string' },
|
|
152
168
|
delay: { type: 'number', default: 50, description: 'Keystroke delay with natural variation' },
|
|
153
169
|
clear: { type: 'boolean', default: true },
|
|
154
|
-
aiHeal: { type: 'boolean', default: true }
|
|
170
|
+
aiHeal: { type: 'boolean', default: true },
|
|
171
|
+
// NEW: iframe support
|
|
172
|
+
iframe: { type: 'number', description: 'Execute in specific iframe index' },
|
|
173
|
+
iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector' },
|
|
174
|
+
// NEW: Additional options
|
|
175
|
+
pressEnter: { type: 'boolean', default: false, description: 'Press Enter after typing' },
|
|
176
|
+
waitForSelector: { type: 'boolean', default: true, description: 'Wait for selector before typing' }
|
|
155
177
|
},
|
|
156
178
|
required: ['selector', 'text']
|
|
157
179
|
}
|
|
@@ -520,12 +542,12 @@ const TOOLS = [
|
|
|
520
542
|
}
|
|
521
543
|
},
|
|
522
544
|
|
|
523
|
-
// 22. Execute JS
|
|
545
|
+
// 22. Execute JS (ENHANCED: iframe context fix)
|
|
524
546
|
{
|
|
525
547
|
name: 'execute_js',
|
|
526
548
|
emoji: '💻',
|
|
527
|
-
description: 'Execute custom JavaScript with async support and
|
|
528
|
-
descriptionHindi: 'कस्टम JS चलाना (async +
|
|
549
|
+
description: 'Execute custom JavaScript with async support, error handling, and iframe context',
|
|
550
|
+
descriptionHindi: 'कस्टम JS चलाना (async + iframe context support)',
|
|
529
551
|
category: 'interaction',
|
|
530
552
|
requiresBrowser: true,
|
|
531
553
|
requiresPage: true,
|
|
@@ -536,7 +558,10 @@ const TOOLS = [
|
|
|
536
558
|
returnValue: { type: 'boolean', default: true },
|
|
537
559
|
async: { type: 'boolean', default: false, description: 'Execute async code' },
|
|
538
560
|
timeout: { type: 'number', default: 30000 },
|
|
539
|
-
|
|
561
|
+
// FIXED: iframe context now works properly
|
|
562
|
+
iframe: { type: 'number', description: 'Execute in specific iframe index (0=main, 1+=iframes)' },
|
|
563
|
+
iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector' },
|
|
564
|
+
waitForIframe: { type: 'boolean', default: true, description: 'Wait for iframe to be ready' }
|
|
540
565
|
},
|
|
541
566
|
required: ['code']
|
|
542
567
|
}
|