firefox-devtools-mcp 0.3.0 → 0.4.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/dist/index.js +214 -42
- package/dist/snapshot.injected.global.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12211,6 +12211,30 @@ var init_core = __esm({
|
|
|
12211
12211
|
}
|
|
12212
12212
|
return this.driver;
|
|
12213
12213
|
}
|
|
12214
|
+
/**
|
|
12215
|
+
* Check if Firefox is still connected and responsive
|
|
12216
|
+
* Returns false if Firefox was closed or connection is broken
|
|
12217
|
+
*/
|
|
12218
|
+
async isConnected() {
|
|
12219
|
+
if (!this.driver) {
|
|
12220
|
+
return false;
|
|
12221
|
+
}
|
|
12222
|
+
try {
|
|
12223
|
+
await this.driver.getWindowHandle();
|
|
12224
|
+
return true;
|
|
12225
|
+
} catch (error) {
|
|
12226
|
+
logDebug("Connection check failed: Firefox is not responsive");
|
|
12227
|
+
return false;
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
/**
|
|
12231
|
+
* Reset driver state (used when Firefox is detected as closed)
|
|
12232
|
+
*/
|
|
12233
|
+
reset() {
|
|
12234
|
+
this.driver = null;
|
|
12235
|
+
this.currentContextId = null;
|
|
12236
|
+
logDebug("Driver state reset");
|
|
12237
|
+
}
|
|
12214
12238
|
/**
|
|
12215
12239
|
* Get current browsing context ID
|
|
12216
12240
|
*/
|
|
@@ -12986,6 +13010,7 @@ var init_pages = __esm({
|
|
|
12986
13010
|
if (index >= 0 && index < handles.length) {
|
|
12987
13011
|
await this.driver.switchTo().window(handles[index]);
|
|
12988
13012
|
this.setCurrentContextId(handles[index]);
|
|
13013
|
+
this.cachedSelectedIdx = index;
|
|
12989
13014
|
}
|
|
12990
13015
|
}
|
|
12991
13016
|
/**
|
|
@@ -12996,6 +13021,7 @@ var init_pages = __esm({
|
|
|
12996
13021
|
const handles = await this.driver.getAllWindowHandles();
|
|
12997
13022
|
const newIdx = handles.length - 1;
|
|
12998
13023
|
this.setCurrentContextId(handles[newIdx]);
|
|
13024
|
+
this.cachedSelectedIdx = newIdx;
|
|
12999
13025
|
await this.driver.get(url);
|
|
13000
13026
|
return newIdx;
|
|
13001
13027
|
}
|
|
@@ -13300,10 +13326,12 @@ var init_manager = __esm({
|
|
|
13300
13326
|
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
13301
13327
|
const currentDir = dirname(currentFilePath);
|
|
13302
13328
|
const possiblePaths = [
|
|
13303
|
-
// Production: relative to
|
|
13304
|
-
resolve(currentDir, "
|
|
13305
|
-
//
|
|
13306
|
-
resolve(process.cwd(), "dist/snapshot.injected.global.js")
|
|
13329
|
+
// Production: relative to bundled dist/index.js (same directory)
|
|
13330
|
+
resolve(currentDir, "snapshot.injected.global.js"),
|
|
13331
|
+
// Development: relative to current working directory
|
|
13332
|
+
resolve(process.cwd(), "dist/snapshot.injected.global.js"),
|
|
13333
|
+
// npx: package is in node_modules, try to find it relative to the binary
|
|
13334
|
+
resolve(currentDir, "../snapshot.injected.global.js")
|
|
13307
13335
|
];
|
|
13308
13336
|
const attemptedPaths = [];
|
|
13309
13337
|
for (const path of possiblePaths) {
|
|
@@ -13358,7 +13386,8 @@ ${attemptedPaths.map((p) => ` - ${p}`).join("\n")}`
|
|
|
13358
13386
|
root: result.tree,
|
|
13359
13387
|
snapshotId,
|
|
13360
13388
|
timestamp: Date.now(),
|
|
13361
|
-
truncated: result.truncated || false
|
|
13389
|
+
truncated: result.truncated || false,
|
|
13390
|
+
uidMap: result.uidMap
|
|
13362
13391
|
};
|
|
13363
13392
|
const snapshot = {
|
|
13364
13393
|
text: formatSnapshotTree(result.tree),
|
|
@@ -13726,6 +13755,24 @@ var init_firefox = __esm({
|
|
|
13726
13755
|
getDriver() {
|
|
13727
13756
|
return this.core.getDriver();
|
|
13728
13757
|
}
|
|
13758
|
+
/**
|
|
13759
|
+
* Check if Firefox is still connected and responsive
|
|
13760
|
+
* Returns false if Firefox was closed or connection is broken
|
|
13761
|
+
*/
|
|
13762
|
+
async isConnected() {
|
|
13763
|
+
return await this.core.isConnected();
|
|
13764
|
+
}
|
|
13765
|
+
/**
|
|
13766
|
+
* Reset all internal state (used when Firefox is detected as closed)
|
|
13767
|
+
*/
|
|
13768
|
+
reset() {
|
|
13769
|
+
this.core.reset();
|
|
13770
|
+
this.consoleEvents = null;
|
|
13771
|
+
this.networkEvents = null;
|
|
13772
|
+
this.dom = null;
|
|
13773
|
+
this.pages = null;
|
|
13774
|
+
this.snapshot = null;
|
|
13775
|
+
}
|
|
13729
13776
|
// ============================================================================
|
|
13730
13777
|
// Cleanup
|
|
13731
13778
|
// ============================================================================
|
|
@@ -13737,6 +13784,33 @@ var init_firefox = __esm({
|
|
|
13737
13784
|
});
|
|
13738
13785
|
|
|
13739
13786
|
// src/utils/response-helpers.ts
|
|
13787
|
+
function estimateTokens(text) {
|
|
13788
|
+
return Math.ceil(text.length / 4);
|
|
13789
|
+
}
|
|
13790
|
+
function truncateText(text, maxChars, suffix = "\n\n[... truncated - exceeded size limit]") {
|
|
13791
|
+
if (text.length <= maxChars) {
|
|
13792
|
+
return text;
|
|
13793
|
+
}
|
|
13794
|
+
return text.slice(0, maxChars - suffix.length) + suffix;
|
|
13795
|
+
}
|
|
13796
|
+
function truncateHeaders(headers) {
|
|
13797
|
+
if (!headers) {
|
|
13798
|
+
return null;
|
|
13799
|
+
}
|
|
13800
|
+
const result = {};
|
|
13801
|
+
let totalChars = 0;
|
|
13802
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
13803
|
+
const truncatedValue = value.length > TOKEN_LIMITS.MAX_HEADER_VALUE_CHARS ? value.slice(0, TOKEN_LIMITS.MAX_HEADER_VALUE_CHARS) + "...[truncated]" : value;
|
|
13804
|
+
const entrySize = key.length + truncatedValue.length;
|
|
13805
|
+
if (totalChars + entrySize > TOKEN_LIMITS.MAX_HEADERS_TOTAL_CHARS) {
|
|
13806
|
+
result["__truncated__"] = "Headers truncated due to size limit";
|
|
13807
|
+
break;
|
|
13808
|
+
}
|
|
13809
|
+
result[key] = truncatedValue;
|
|
13810
|
+
totalChars += entrySize;
|
|
13811
|
+
}
|
|
13812
|
+
return result;
|
|
13813
|
+
}
|
|
13740
13814
|
function successResponse(message) {
|
|
13741
13815
|
return {
|
|
13742
13816
|
content: [
|
|
@@ -13769,9 +13843,26 @@ function jsonResponse(data) {
|
|
|
13769
13843
|
]
|
|
13770
13844
|
};
|
|
13771
13845
|
}
|
|
13846
|
+
var TOKEN_LIMITS;
|
|
13772
13847
|
var init_response_helpers = __esm({
|
|
13773
13848
|
"src/utils/response-helpers.ts"() {
|
|
13774
13849
|
"use strict";
|
|
13850
|
+
TOKEN_LIMITS = {
|
|
13851
|
+
/** Maximum characters for a single response (~12.5k tokens at ~4 chars/token) */
|
|
13852
|
+
MAX_RESPONSE_CHARS: 5e4,
|
|
13853
|
+
/** Maximum characters for screenshot base64 data (~10k tokens) */
|
|
13854
|
+
MAX_SCREENSHOT_CHARS: 4e4,
|
|
13855
|
+
/** Maximum characters per console message text */
|
|
13856
|
+
MAX_CONSOLE_MESSAGE_CHARS: 2e3,
|
|
13857
|
+
/** Maximum characters for network header values (per header) */
|
|
13858
|
+
MAX_HEADER_VALUE_CHARS: 500,
|
|
13859
|
+
/** Maximum total characters for all headers combined */
|
|
13860
|
+
MAX_HEADERS_TOTAL_CHARS: 5e3,
|
|
13861
|
+
/** Hard cap on snapshot lines (even if user requests more) */
|
|
13862
|
+
MAX_SNAPSHOT_LINES_CAP: 500,
|
|
13863
|
+
/** Warning threshold - show warning when response exceeds this */
|
|
13864
|
+
WARNING_THRESHOLD_CHARS: 3e4
|
|
13865
|
+
};
|
|
13775
13866
|
}
|
|
13776
13867
|
});
|
|
13777
13868
|
|
|
@@ -14034,6 +14125,10 @@ async function handleListConsoleMessages(args2) {
|
|
|
14034
14125
|
if (source) {
|
|
14035
14126
|
messages = messages.filter((msg) => msg.source?.toLowerCase() === source.toLowerCase());
|
|
14036
14127
|
}
|
|
14128
|
+
messages = messages.map((msg) => ({
|
|
14129
|
+
...msg,
|
|
14130
|
+
text: truncateText(msg.text, TOKEN_LIMITS.MAX_CONSOLE_MESSAGE_CHARS, "...[truncated]")
|
|
14131
|
+
}));
|
|
14037
14132
|
const maxLimit = limit ?? DEFAULT_LIMIT;
|
|
14038
14133
|
const filteredCount = messages.length;
|
|
14039
14134
|
const truncated = messages.length > maxLimit;
|
|
@@ -14292,8 +14387,8 @@ async function handleListNetworkRequests(args2) {
|
|
|
14292
14387
|
resourceType: req.resourceType,
|
|
14293
14388
|
isXHR: req.isXHR,
|
|
14294
14389
|
timings: req.timings || null,
|
|
14295
|
-
requestHeaders: req.requestHeaders
|
|
14296
|
-
responseHeaders: req.responseHeaders
|
|
14390
|
+
requestHeaders: truncateHeaders(req.requestHeaders),
|
|
14391
|
+
responseHeaders: truncateHeaders(req.responseHeaders)
|
|
14297
14392
|
}));
|
|
14298
14393
|
}
|
|
14299
14394
|
return jsonResponse(responseData);
|
|
@@ -14338,8 +14433,8 @@ async function handleListNetworkRequests(args2) {
|
|
|
14338
14433
|
resourceType: req.resourceType,
|
|
14339
14434
|
isXHR: req.isXHR,
|
|
14340
14435
|
timings: req.timings || null,
|
|
14341
|
-
requestHeaders: req.requestHeaders
|
|
14342
|
-
responseHeaders: req.responseHeaders
|
|
14436
|
+
requestHeaders: truncateHeaders(req.requestHeaders),
|
|
14437
|
+
responseHeaders: truncateHeaders(req.responseHeaders)
|
|
14343
14438
|
}));
|
|
14344
14439
|
return successResponse(
|
|
14345
14440
|
`Found ${requests.length} requests${hasMore ? ` (showing first ${limit})` : ""}
|
|
@@ -14411,8 +14506,8 @@ Please use one of these IDs with the "id" parameter:
|
|
|
14411
14506
|
isXHR: request.isXHR ?? false,
|
|
14412
14507
|
timestamp: request.timestamp ?? null,
|
|
14413
14508
|
timings: request.timings ?? null,
|
|
14414
|
-
requestHeaders: request.requestHeaders
|
|
14415
|
-
responseHeaders: request.responseHeaders
|
|
14509
|
+
requestHeaders: truncateHeaders(request.requestHeaders),
|
|
14510
|
+
responseHeaders: truncateHeaders(request.responseHeaders)
|
|
14416
14511
|
};
|
|
14417
14512
|
if (format === "json") {
|
|
14418
14513
|
return jsonResponse(details);
|
|
@@ -14518,11 +14613,13 @@ var init_network2 = __esm({
|
|
|
14518
14613
|
async function handleTakeSnapshot(args2) {
|
|
14519
14614
|
try {
|
|
14520
14615
|
const {
|
|
14521
|
-
maxLines =
|
|
14616
|
+
maxLines: requestedMaxLines = DEFAULT_SNAPSHOT_LINES,
|
|
14522
14617
|
includeAttributes = false,
|
|
14523
14618
|
includeText = true,
|
|
14524
14619
|
maxDepth
|
|
14525
14620
|
} = args2 || {};
|
|
14621
|
+
const maxLines = Math.min(Math.max(1, requestedMaxLines), TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP);
|
|
14622
|
+
const wasCapped = requestedMaxLines > TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP;
|
|
14526
14623
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
14527
14624
|
const firefox3 = await getFirefox2();
|
|
14528
14625
|
const snapshot = await firefox3.takeSnapshot();
|
|
@@ -14539,6 +14636,11 @@ async function handleTakeSnapshot(args2) {
|
|
|
14539
14636
|
const truncated = lines.length > maxLines;
|
|
14540
14637
|
const displayLines = truncated ? lines.slice(0, maxLines) : lines;
|
|
14541
14638
|
let output = "\u{1F4F8} Snapshot taken\n\n";
|
|
14639
|
+
if (wasCapped) {
|
|
14640
|
+
output += `\u26A0\uFE0F maxLines capped at ${TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP} (requested: ${requestedMaxLines}) to prevent token overflow
|
|
14641
|
+
|
|
14642
|
+
`;
|
|
14643
|
+
}
|
|
14542
14644
|
output += "\u2550\u2550\u2550 HOW TO USE THIS SNAPSHOT \u2550\u2550\u2550\n";
|
|
14543
14645
|
output += "\u2022 To interact with elements: use click_by_uid, hover_by_uid, or fill_by_uid with the UID\n";
|
|
14544
14646
|
output += "\u2022 After navigation: always call take_snapshot again (UIDs become stale)\n";
|
|
@@ -14608,12 +14710,12 @@ async function handleClearSnapshot(_args) {
|
|
|
14608
14710
|
return errorResponse(error);
|
|
14609
14711
|
}
|
|
14610
14712
|
}
|
|
14611
|
-
var
|
|
14713
|
+
var DEFAULT_SNAPSHOT_LINES, takeSnapshotTool, resolveUidToSelectorTool, clearSnapshotTool;
|
|
14612
14714
|
var init_snapshot2 = __esm({
|
|
14613
14715
|
"src/tools/snapshot.ts"() {
|
|
14614
14716
|
"use strict";
|
|
14615
14717
|
init_response_helpers();
|
|
14616
|
-
|
|
14718
|
+
DEFAULT_SNAPSHOT_LINES = 100;
|
|
14617
14719
|
takeSnapshotTool = {
|
|
14618
14720
|
name: "take_snapshot",
|
|
14619
14721
|
description: "Capture a textual page snapshot with stable UIDs for elements. Always take a fresh snapshot after navigation or major DOM changes. TIP: Use the UIDs with click_by_uid / fill_by_uid / hover_by_uid. The output may be truncated for readability.",
|
|
@@ -14972,6 +15074,37 @@ var init_input = __esm({
|
|
|
14972
15074
|
});
|
|
14973
15075
|
|
|
14974
15076
|
// src/tools/screenshot.ts
|
|
15077
|
+
function buildScreenshotResponse(base64Png, context) {
|
|
15078
|
+
const sizeKB = Math.round(base64Png.length / 1024);
|
|
15079
|
+
const estimatedTokens = estimateTokens(base64Png);
|
|
15080
|
+
if (base64Png.length > TOKEN_LIMITS.MAX_SCREENSHOT_CHARS) {
|
|
15081
|
+
const truncatedData = base64Png.slice(0, TOKEN_LIMITS.MAX_SCREENSHOT_CHARS);
|
|
15082
|
+
return successResponse(
|
|
15083
|
+
`\u{1F4F8} ${context} (${sizeKB}KB)
|
|
15084
|
+
|
|
15085
|
+
\u26A0\uFE0F Screenshot truncated (~${Math.round(estimatedTokens / 1e3)}k tokens exceeds limit)
|
|
15086
|
+
Only first ${Math.round(TOKEN_LIMITS.MAX_SCREENSHOT_CHARS / 1024)}KB shown.
|
|
15087
|
+
TIP: For full screenshots, use a dedicated screenshot tool or save to file.
|
|
15088
|
+
|
|
15089
|
+
Base64 PNG data (truncated):
|
|
15090
|
+
${truncatedData}
|
|
15091
|
+
|
|
15092
|
+
[...truncated]`
|
|
15093
|
+
);
|
|
15094
|
+
}
|
|
15095
|
+
let warning = "";
|
|
15096
|
+
if (base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS) {
|
|
15097
|
+
warning = `\u26A0\uFE0F Large screenshot (~${Math.round(estimatedTokens / 1e3)}k tokens) - may fill context quickly
|
|
15098
|
+
|
|
15099
|
+
`;
|
|
15100
|
+
}
|
|
15101
|
+
return successResponse(
|
|
15102
|
+
`\u{1F4F8} ${context} (${sizeKB}KB)
|
|
15103
|
+
|
|
15104
|
+
` + warning + `Base64 PNG data:
|
|
15105
|
+
${base64Png}`
|
|
15106
|
+
);
|
|
15107
|
+
}
|
|
14975
15108
|
async function handleScreenshotPage(_args) {
|
|
14976
15109
|
try {
|
|
14977
15110
|
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
@@ -14980,12 +15113,7 @@ async function handleScreenshotPage(_args) {
|
|
|
14980
15113
|
if (!base64Png || typeof base64Png !== "string") {
|
|
14981
15114
|
throw new Error("Failed to capture screenshot: invalid data returned");
|
|
14982
15115
|
}
|
|
14983
|
-
return
|
|
14984
|
-
`\u{1F4F8} Page screenshot captured (${Math.round(base64Png.length / 1024)}KB)
|
|
14985
|
-
|
|
14986
|
-
Base64 PNG data:
|
|
14987
|
-
${base64Png}`
|
|
14988
|
-
);
|
|
15116
|
+
return buildScreenshotResponse(base64Png, "Page screenshot captured");
|
|
14989
15117
|
} catch (error) {
|
|
14990
15118
|
return errorResponse(
|
|
14991
15119
|
new Error(
|
|
@@ -15009,12 +15137,7 @@ async function handleScreenshotByUid(args2) {
|
|
|
15009
15137
|
if (!base64Png || typeof base64Png !== "string") {
|
|
15010
15138
|
throw new Error("Failed to capture screenshot: invalid data returned");
|
|
15011
15139
|
}
|
|
15012
|
-
return
|
|
15013
|
-
`\u{1F4F8} Element screenshot captured for UID "${uid}" (${Math.round(base64Png.length / 1024)}KB)
|
|
15014
|
-
|
|
15015
|
-
Base64 PNG data:
|
|
15016
|
-
${base64Png}`
|
|
15017
|
-
);
|
|
15140
|
+
return buildScreenshotResponse(base64Png, `Element screenshot captured for UID "${uid}"`);
|
|
15018
15141
|
} catch (error) {
|
|
15019
15142
|
const errorMsg = error.message;
|
|
15020
15143
|
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID") || errorMsg.includes("not found")) {
|
|
@@ -15238,6 +15361,33 @@ var init_tools = __esm({
|
|
|
15238
15361
|
}
|
|
15239
15362
|
});
|
|
15240
15363
|
|
|
15364
|
+
// src/utils/errors.ts
|
|
15365
|
+
function isDisconnectionError(error) {
|
|
15366
|
+
if (error instanceof FirefoxDisconnectedError) {
|
|
15367
|
+
return true;
|
|
15368
|
+
}
|
|
15369
|
+
if (error instanceof Error) {
|
|
15370
|
+
const message = error.message.toLowerCase();
|
|
15371
|
+
return message.includes("session deleted") || message.includes("session not created") || message.includes("no such window") || message.includes("no such session") || message.includes("target window already closed") || message.includes("unable to connect") || message.includes("connection refused") || message.includes("not connected") || message.includes("driver not connected") || message.includes("invalid session id") || message.includes("browsing context has been discarded");
|
|
15372
|
+
}
|
|
15373
|
+
return false;
|
|
15374
|
+
}
|
|
15375
|
+
var FirefoxDisconnectedError;
|
|
15376
|
+
var init_errors2 = __esm({
|
|
15377
|
+
"src/utils/errors.ts"() {
|
|
15378
|
+
"use strict";
|
|
15379
|
+
FirefoxDisconnectedError = class extends Error {
|
|
15380
|
+
constructor(reason) {
|
|
15381
|
+
const baseMessage = "Firefox browser is not connected";
|
|
15382
|
+
const instruction = "The Firefox browser window was closed by the user. To continue browser automation, ask the user to restart the firefox-devtools-mcp server (they need to restart Claude Code or the MCP connection). This will launch a new Firefox instance.";
|
|
15383
|
+
const fullMessage = reason ? `${baseMessage}: ${reason}. ${instruction}` : `${baseMessage}. ${instruction}`;
|
|
15384
|
+
super(fullMessage);
|
|
15385
|
+
this.name = "FirefoxDisconnectedError";
|
|
15386
|
+
}
|
|
15387
|
+
};
|
|
15388
|
+
}
|
|
15389
|
+
});
|
|
15390
|
+
|
|
15241
15391
|
// node_modules/dotenv/package.json
|
|
15242
15392
|
var require_package = __commonJS({
|
|
15243
15393
|
"node_modules/dotenv/package.json"(exports, module) {
|
|
@@ -15640,28 +15790,45 @@ var require_main = __commonJS({
|
|
|
15640
15790
|
var index_exports = {};
|
|
15641
15791
|
__export(index_exports, {
|
|
15642
15792
|
FirefoxDevTools: () => FirefoxClient,
|
|
15793
|
+
FirefoxDisconnectedError: () => FirefoxDisconnectedError,
|
|
15643
15794
|
args: () => args,
|
|
15644
|
-
getFirefox: () => getFirefox
|
|
15795
|
+
getFirefox: () => getFirefox,
|
|
15796
|
+
isDisconnectionError: () => isDisconnectionError,
|
|
15797
|
+
resetFirefox: () => resetFirefox
|
|
15645
15798
|
});
|
|
15646
15799
|
import { version } from "process";
|
|
15647
15800
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15648
15801
|
import { resolve as resolve2 } from "path";
|
|
15649
15802
|
import { realpathSync } from "fs";
|
|
15650
|
-
|
|
15651
|
-
if (
|
|
15652
|
-
|
|
15653
|
-
|
|
15654
|
-
firefoxPath: args.firefoxPath ?? void 0,
|
|
15655
|
-
headless: args.headless,
|
|
15656
|
-
profilePath: args.profilePath ?? void 0,
|
|
15657
|
-
viewport: args.viewport ?? void 0,
|
|
15658
|
-
args: args.firefoxArg ?? void 0,
|
|
15659
|
-
startUrl: args.startUrl ?? void 0
|
|
15660
|
-
};
|
|
15661
|
-
firefox2 = new FirefoxClient(options);
|
|
15662
|
-
await firefox2.connect();
|
|
15663
|
-
log("Firefox DevTools connection established");
|
|
15803
|
+
function resetFirefox() {
|
|
15804
|
+
if (firefox2) {
|
|
15805
|
+
firefox2.reset();
|
|
15806
|
+
firefox2 = null;
|
|
15664
15807
|
}
|
|
15808
|
+
log("Firefox instance reset - will reconnect on next tool call");
|
|
15809
|
+
}
|
|
15810
|
+
async function getFirefox() {
|
|
15811
|
+
if (firefox2) {
|
|
15812
|
+
const isConnected = await firefox2.isConnected();
|
|
15813
|
+
if (!isConnected) {
|
|
15814
|
+
log("Firefox connection lost - browser was closed or disconnected");
|
|
15815
|
+
resetFirefox();
|
|
15816
|
+
throw new FirefoxDisconnectedError("Browser was closed");
|
|
15817
|
+
}
|
|
15818
|
+
return firefox2;
|
|
15819
|
+
}
|
|
15820
|
+
log("Initializing Firefox DevTools connection...");
|
|
15821
|
+
const options = {
|
|
15822
|
+
firefoxPath: args.firefoxPath ?? void 0,
|
|
15823
|
+
headless: args.headless,
|
|
15824
|
+
profilePath: args.profilePath ?? void 0,
|
|
15825
|
+
viewport: args.viewport ?? void 0,
|
|
15826
|
+
args: args.firefoxArg ?? void 0,
|
|
15827
|
+
startUrl: args.startUrl ?? void 0
|
|
15828
|
+
};
|
|
15829
|
+
firefox2 = new FirefoxClient(options);
|
|
15830
|
+
await firefox2.connect();
|
|
15831
|
+
log("Firefox DevTools connection established");
|
|
15665
15832
|
return firefox2;
|
|
15666
15833
|
}
|
|
15667
15834
|
async function main() {
|
|
@@ -15729,7 +15896,9 @@ var init_index = __esm({
|
|
|
15729
15896
|
init_cli();
|
|
15730
15897
|
init_firefox();
|
|
15731
15898
|
init_tools();
|
|
15899
|
+
init_errors2();
|
|
15732
15900
|
init_firefox();
|
|
15901
|
+
init_errors2();
|
|
15733
15902
|
if (process.env.NODE_ENV !== "production") {
|
|
15734
15903
|
try {
|
|
15735
15904
|
const { config } = await Promise.resolve().then(() => __toESM(require_main(), 1));
|
|
@@ -15829,8 +15998,11 @@ var init_index = __esm({
|
|
|
15829
15998
|
await init_index();
|
|
15830
15999
|
export {
|
|
15831
16000
|
FirefoxClient as FirefoxDevTools,
|
|
16001
|
+
FirefoxDisconnectedError,
|
|
15832
16002
|
args,
|
|
15833
|
-
getFirefox
|
|
16003
|
+
getFirefox,
|
|
16004
|
+
isDisconnectionError,
|
|
16005
|
+
resetFirefox
|
|
15834
16006
|
};
|
|
15835
16007
|
/*! Bundled license information:
|
|
15836
16008
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var __SnapshotInjected=(()=>{var h=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var q=(t,r)=>{for(var e in r)h(t,e,{get:r[e],enumerable:!0})},z=(t,r,e,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of V(r))!U.call(t,o)&&o!==e&&h(t,o,{get:()=>r[o],enumerable:!(i=W(r,o))||i.enumerable});return t};var B=t=>z(h({},"__esModule",{value:!0}),t);var rt={};q(rt,{createSnapshot:()=>P});var v=["a","button","input","select","textarea","img","video","audio","iframe"],J=["nav","main","section","article","header","footer"],K=["div","span","p","li","ul","ol"];function L(t){if(!t||t.nodeType!==Node.ELEMENT_NODE)return!1;try{let e=window.getComputedStyle(t);if(e.display==="none"||e.visibility==="hidden"||e.opacity==="0")return!1}catch{return!1}let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1||t.hasAttribute("role")||t.hasAttribute("aria-label")||/^h[1-6]$/.test(r)||J.indexOf(r)!==-1)return!0;if(K.indexOf(r)!==-1){let e=(t.textContent||"").trim();if(e.length>0&&e.length<500||t.id||t.className)return!0}return!1}function M(t){if(t.tabIndex>=0)return!0;let e=t.tagName.toLowerCase();return["a","button","input","select","textarea"].indexOf(e)!==-1}function O(t){let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1)return!0;let e=t.getAttribute("role");return!!(e&&["button","link","menuitem","tab"].indexOf(e)!==-1||t.hasAttribute("onclick"))}var Q=100;function k(t){if(t.hasAttribute("aria-label"))return t.getAttribute("aria-label")||void 0;let e=t.id;if(e){let o=document.querySelector(`label[for="${e}"]`);if(o?.textContent)return o.textContent.trim()}if(t.hasAttribute("placeholder"))return t.getAttribute("placeholder")||void 0;if(t.hasAttribute("title"))return t.getAttribute("title")||void 0;if(t.hasAttribute("alt"))return t.getAttribute("alt")||void 0;let i=t.tagName.toLowerCase();if(["button","a","h1","h2","h3","h4","h5","h6"].indexOf(i)!==-1)return g(t)}function g(t){let r="";for(let i=0;i<t.childNodes.length;i++){let o=t.childNodes[i];o&&o.nodeType===Node.TEXT_NODE&&(r+=o.textContent||"")}let e=r.trim();if(e)return e.substring(0,Q)}function $(t){let r={},e=!1,i=["disabled","hidden","selected","expanded"];for(let a of i){let n=t.getAttribute(`aria-${a}`);n!==null&&(r[a]=n==="true",e=!0)}let o=["checked","pressed"];for(let a of o){let n=t.getAttribute(`aria-${a}`);n!==null&&(n==="mixed"?r[a]="mixed":r[a]=n==="true",e=!0)}let l=["autocomplete","haspopup","invalid","label","labelledby","describedby","controls"];for(let a of l){let n=t.getAttribute(`aria-${a}`);n&&(r[a]=n,e=!0)}let s=t.getAttribute("aria-level");if(s){let a=parseInt(s,10);isNaN(a)||(r.level=a,e=!0)}return e?r:void 0}function I(t){let r={};try{let e=window.getComputedStyle(t);r.visible=e.display!=="none"&&e.visibility!=="hidden"&&e.opacity!=="0"}catch{r.visible=!1}return r.accessible=r.visible&&!t.getAttribute("aria-hidden"),r.focusable=M(t),r.interactive=O(t),r}var Y=["id","data-testid","data-test-id"];function R(t){let r=[],e=t;for(;e&&e.nodeType===Node.ELEMENT_NODE;){let i=e.nodeName.toLowerCase(),o=!1;for(let n of Y){let u=e.getAttribute(n);if(u){n==="id"?i+="#"+CSS.escape(u):i+=`[${n}="${X(u)}"]`,r.unshift(i),o=!0;break}}if(o)break;let l=e.getAttribute("aria-label"),s=e.getAttribute("role");if(l&&s){i+=`[role="${s}"][aria-label="${X(l)}"]`,r.unshift(i),e=e.parentElement;continue}let a=e.parentElement?.children;if(a&&a.length>1){let n=1;for(let u=0;u<a.length;u++){let d=a[u];if(d){if(d===e)break;d.nodeName===e.nodeName&&n++}}(n>1||a.length>1&&a[0]!==e)&&(i+=`:nth-of-type(${n})`)}if(r.unshift(tt(i)),e=e.parentElement,e&&e.nodeName.toLowerCase()==="body"){r.unshift("body");break}}return r.join(" > ")}function G(t){let r=t.id;if(r)return`//*[@id="${Z(r)}"]`;let e=[],i=t;for(;i&&i.nodeType===Node.ELEMENT_NODE;){let o=i.nodeName.toLowerCase(),l=1,s=i.previousElementSibling;for(;s;)s.nodeName.toLowerCase()===o&&l++,s=s.previousElementSibling;let a=i.parentElement,n=!1;a&&(n=Array.from(a.children).filter(E=>E.nodeName.toLowerCase()===o).length>1);let u=n?`${o}[${l}]`:o;if(e.unshift(u),i=i.parentElement,i&&i.nodeName.toLowerCase()==="html"){e.unshift("html");break}}return"/"+e.join("/")}function X(t){return t.replace(/"/g,'\\"').substring(0,64)}function Z(t){return t.indexOf('"')===-1||t.indexOf("'")===-1?t:`concat(${t.split('"').map((e,i,o)=>i===o.length-1?e?`"${e}"`:"":e?`"${e}",'"'`:`"'"`).filter(e=>e).join(",")})`}function tt(t){return t.length<=64?t:t.substring(0,64)}var et=10,H=1e3;function D(t,r,e=!0){let i=0,o=[],l=!1;function s(n,u){if(u>et||i>=H)return l=!0,null;let d=n.tagName.toLowerCase();if(!(d==="body"||d==="html")&&!L(n))return null;let A=`${r}_${i++}`,j=R(n),F=G(n);o.push({uid:A,css:j,xpath:F});let b=n,N=n.getAttribute("role"),T=k(n),y=g(n),x=b.value,C=b.href,S=b.src,w=$(n),_=I(n),c={uid:A,tag:d,...N&&{role:N},...T&&{name:T},...x&&{value:x},...C&&{href:C},...S&&{src:S},...y&&{text:y},...w&&{aria:w},..._&&{computed:_},children:[]};if(d==="iframe"&&e){try{let f=n,p=f.contentDocument||f.contentWindow?.document;if(p?.body){let m=s(p.body,u+1);m&&(m.isIframe=!0,m.frameSrc=f.src,c.children.push(m))}else c.isIframe=!0,c.frameSrc=f.src,c.crossOrigin=!0}catch{c.isIframe=!0,c.frameSrc=n.src,c.crossOrigin=!0}return c}for(let f=0;f<n.children.length;f++){if(i>=H){l=!0;break}let p=n.children[f];if(!p)continue;let m=s(p,u+1);m&&c.children.push(m)}return c}return{tree:s(t,0),uidMap:o,truncated:l}}function P(t){try{let r=D(document.body,t,!0);if(!r.tree)throw new Error("Failed to generate tree");return r}catch{return{tree:null,uidMap:[],truncated:!1}}}typeof window<"u"&&(window.__createSnapshot=P);return B(rt);})();
|
|
1
|
+
"use strict";var __SnapshotInjected=(()=>{var h=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var q=(t,r)=>{for(var e in r)h(t,e,{get:r[e],enumerable:!0})},z=(t,r,e,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of V(r))!U.call(t,o)&&o!==e&&h(t,o,{get:()=>r[o],enumerable:!(i=W(r,o))||i.enumerable});return t};var B=t=>z(h({},"__esModule",{value:!0}),t);var rt={};q(rt,{createSnapshot:()=>P});var v=["a","button","input","select","textarea","img","video","audio","iframe"],J=["nav","main","section","article","header","footer","form"],K=["div","span","p","li","ul","ol"];function L(t){if(!t||t.nodeType!==Node.ELEMENT_NODE)return!1;try{let e=window.getComputedStyle(t);if(e.display==="none"||e.visibility==="hidden"||e.opacity==="0")return!1}catch{return!1}let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1||t.hasAttribute("role")||t.hasAttribute("aria-label")||/^h[1-6]$/.test(r)||J.indexOf(r)!==-1)return!0;if(K.indexOf(r)!==-1){let e=(t.textContent||"").trim();if(e.length>0&&e.length<500||t.id||t.className)return!0}return!1}function M(t){if(t.tabIndex>=0)return!0;let e=t.tagName.toLowerCase();return["a","button","input","select","textarea"].indexOf(e)!==-1}function O(t){let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1)return!0;let e=t.getAttribute("role");return!!(e&&["button","link","menuitem","tab"].indexOf(e)!==-1||t.hasAttribute("onclick"))}var Q=100;function k(t){if(t.hasAttribute("aria-label"))return t.getAttribute("aria-label")||void 0;let e=t.id;if(e){let o=document.querySelector(`label[for="${e}"]`);if(o?.textContent)return o.textContent.trim()}if(t.hasAttribute("placeholder"))return t.getAttribute("placeholder")||void 0;if(t.hasAttribute("title"))return t.getAttribute("title")||void 0;if(t.hasAttribute("alt"))return t.getAttribute("alt")||void 0;let i=t.tagName.toLowerCase();if(["button","a","h1","h2","h3","h4","h5","h6"].indexOf(i)!==-1)return g(t)}function g(t){let r="";for(let i=0;i<t.childNodes.length;i++){let o=t.childNodes[i];o&&o.nodeType===Node.TEXT_NODE&&(r+=o.textContent||"")}let e=r.trim();if(e)return e.substring(0,Q)}function $(t){let r={},e=!1,i=["disabled","hidden","selected","expanded"];for(let a of i){let n=t.getAttribute(`aria-${a}`);n!==null&&(r[a]=n==="true",e=!0)}let o=["checked","pressed"];for(let a of o){let n=t.getAttribute(`aria-${a}`);n!==null&&(n==="mixed"?r[a]="mixed":r[a]=n==="true",e=!0)}let l=["autocomplete","haspopup","invalid","label","labelledby","describedby","controls"];for(let a of l){let n=t.getAttribute(`aria-${a}`);n&&(r[a]=n,e=!0)}let s=t.getAttribute("aria-level");if(s){let a=parseInt(s,10);isNaN(a)||(r.level=a,e=!0)}return e?r:void 0}function I(t){let r={};try{let e=window.getComputedStyle(t);r.visible=e.display!=="none"&&e.visibility!=="hidden"&&e.opacity!=="0"}catch{r.visible=!1}return r.accessible=r.visible&&!t.getAttribute("aria-hidden"),r.focusable=M(t),r.interactive=O(t),r}var Y=["id","data-testid","data-test-id"];function R(t){let r=[],e=t;for(;e&&e.nodeType===Node.ELEMENT_NODE;){let i=e.nodeName.toLowerCase(),o=!1;for(let n of Y){let u=e.getAttribute(n);if(u){n==="id"?i+="#"+CSS.escape(u):i+=`[${n}="${X(u)}"]`,r.unshift(i),o=!0;break}}if(o)break;let l=e.getAttribute("aria-label"),s=e.getAttribute("role");if(l&&s){i+=`[role="${s}"][aria-label="${X(l)}"]`,r.unshift(i),e=e.parentElement;continue}let a=e.parentElement?.children;if(a&&a.length>1){let n=1;for(let u=0;u<a.length;u++){let d=a[u];if(d){if(d===e)break;d.nodeName===e.nodeName&&n++}}(n>1||a.length>1&&a[0]!==e)&&(i+=`:nth-of-type(${n})`)}if(r.unshift(tt(i)),e=e.parentElement,e&&e.nodeName.toLowerCase()==="body"){r.unshift("body");break}}return r.join(" > ")}function G(t){let r=t.id;if(r)return`//*[@id="${Z(r)}"]`;let e=[],i=t;for(;i&&i.nodeType===Node.ELEMENT_NODE;){let o=i.nodeName.toLowerCase(),l=1,s=i.previousElementSibling;for(;s;)s.nodeName.toLowerCase()===o&&l++,s=s.previousElementSibling;let a=i.parentElement,n=!1;a&&(n=Array.from(a.children).filter(E=>E.nodeName.toLowerCase()===o).length>1);let u=n?`${o}[${l}]`:o;if(e.unshift(u),i=i.parentElement,i&&i.nodeName.toLowerCase()==="html"){e.unshift("html");break}}return"/"+e.join("/")}function X(t){return t.replace(/"/g,'\\"').substring(0,64)}function Z(t){return t.indexOf('"')===-1||t.indexOf("'")===-1?t:`concat(${t.split('"').map((e,i,o)=>i===o.length-1?e?`"${e}"`:"":e?`"${e}",'"'`:`"'"`).filter(e=>e).join(",")})`}function tt(t){return t.length<=64?t:t.substring(0,64)}var et=10,H=1e3;function D(t,r,e=!0){let i=0,o=[],l=!1;function s(n,u){if(u>et||i>=H)return l=!0,null;let d=n.tagName.toLowerCase();if(!(d==="body"||d==="html")&&!L(n))return null;let A=`${r}_${i++}`,j=R(n),F=G(n);o.push({uid:A,css:j,xpath:F});let b=n,N=n.getAttribute("role"),T=k(n),y=g(n),x=b.value,C=b.href,S=b.src,w=$(n),_=I(n),c={uid:A,tag:d,...N&&{role:N},...T&&{name:T},...x&&{value:x},...C&&{href:C},...S&&{src:S},...y&&{text:y},...w&&{aria:w},..._&&{computed:_},children:[]};if(d==="iframe"&&e){try{let f=n,p=f.contentDocument||f.contentWindow?.document;if(p?.body){let m=s(p.body,u+1);m&&(m.isIframe=!0,m.frameSrc=f.src,c.children.push(m))}else c.isIframe=!0,c.frameSrc=f.src,c.crossOrigin=!0}catch{c.isIframe=!0,c.frameSrc=n.src,c.crossOrigin=!0}return c}for(let f=0;f<n.children.length;f++){if(i>=H){l=!0;break}let p=n.children[f];if(!p)continue;let m=s(p,u+1);m&&c.children.push(m)}return c}return{tree:s(t,0),uidMap:o,truncated:l}}function P(t){try{let r=D(document.body,t,!0);if(!r.tree)throw new Error("Failed to generate tree");return r}catch{return{tree:null,uidMap:[],truncated:!1}}}typeof window<"u"&&(window.__createSnapshot=P);return B(rt);})();
|