Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions agent-server/nodejs/src/api-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ class APIServer {
result = await this.executeJavaScript(JSON.parse(body));
break;

case '/page/dom-snapshot':
if (method !== 'POST') {
this.sendError(res, 405, 'Method not allowed');
return;
}
result = await this.getDOMSnapshot(JSON.parse(body));
break;

default:
this.sendError(res, 404, 'Not found');
return;
Expand Down Expand Up @@ -324,6 +332,49 @@ class APIServer {
return response;
}

async getDOMSnapshot(payload) {
const {
clientId,
tabId,
computedStyles = [],
includeDOMRects = true,
includePaintOrder = false
} = payload;

if (!clientId) {
throw new Error('Client ID is required');
}

if (!tabId) {
throw new Error('Tab ID is required');
}

const baseClientId = clientId.split(':')[0];

logger.info('Capturing DOM snapshot', {
baseClientId,
tabId,
computedStyleCount: computedStyles.length,
includeDOMRects,
includePaintOrder
});

// Call the captureDOMSnapshot method from BrowserAgentServer
const result = await this.browserAgentServer.captureDOMSnapshot(tabId, {
computedStyles,
includeDOMRects,
includePaintOrder
});

return {
clientId: baseClientId,
tabId: result.tabId,
snapshot: result.snapshot, // Contains { documents, strings }
format: 'dom-snapshot',
timestamp: Date.now()
};
}

async getScreenshot(payload) {
const { clientId, tabId, fullPage = false } = payload;

Expand Down
63 changes: 63 additions & 0 deletions agent-server/nodejs/src/lib/BrowserAgentServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,69 @@ export class BrowserAgentServer extends EventEmitter {
}
}

/**
* Capture DOM snapshot using CDP DOMSnapshot.captureSnapshot
* @param {string} tabId - Tab ID (target ID)
* @param {Object} options - Snapshot options
* @param {string[]} options.computedStyles - Array of computed style properties to capture (default: [])
* @param {boolean} options.includeDOMRects - Whether to include bounding boxes (default: true)
* @param {boolean} options.includePaintOrder - Whether to include paint order (default: false)
* @returns {Promise<Object>} Result with DOM snapshot data
*/
async captureDOMSnapshot(tabId, options = {}) {
const {
computedStyles = [],
includeDOMRects = true,
includePaintOrder = false
} = options;

try {
logger.info('Capturing DOM snapshot via CDP', {
tabId,
computedStyleCount: computedStyles.length,
includeDOMRects,
includePaintOrder
});

// Use DOMSnapshot.captureSnapshot CDP command
const result = await this.sendCDPCommandToTarget(tabId, 'DOMSnapshot.captureSnapshot', {
computedStyles,
includeDOMRects,
includePaintOrder
});

// Validate response structure
if (!result.documents || !result.strings) {
throw new Error('Invalid DOMSnapshot response: missing documents or strings array');
}

logger.info('DOM snapshot captured successfully', {
tabId,
documentCount: result.documents.length,
stringCount: result.strings.length,
totalNodes: result.documents.reduce((sum, doc) => sum + (doc.nodes?.nodeType?.length || 0), 0)
});

return {
tabId,
snapshot: result // Contains documents[] and strings[]
};
} catch (error) {
logger.error('Failed to capture DOM snapshot via CDP', {
tabId,
error: error.message,
stack: error.stack
});

// Check if DOMSnapshot domain is available
if (error.message && error.message.includes('was not found')) {
throw new Error('DOMSnapshot domain not available. Requires Chrome 74+ with CDP enabled.');
}

throw error;
}
}

/**
* Capture page screenshot using CDP
* @param {string} tabId - Tab ID (target ID)
Expand Down