get_text
Extract all visible text content from a web page for content analysis, text search, or text-only snapshots. Supports scoping to specific selectors and dialogs.
Instructions
[may return preview+token] ⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Auto-returns text if <2000 chars (small elements); if larger, returns a preview and a one-time token to fetch the full output via confirm_output. Supports testid shortcuts and the dialog::SELECTOR scope to read inside the topmost open dialog/sheet.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| selector | No | CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog. | |
| maxLength | No | Maximum number of characters to return (default: 20000) |
Implementation Reference
- The GetTextTool class containing both the execute() method (the main handler that extracts visible text from a page, supports selector scoping, auto-scoping to modals, preview for large text, and truncation) and the getMetadata() method defining the schema.
export class GetTextTool extends BrowserToolBase { static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "get_text", description: "[may return preview+token] ⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Auto-returns text if <2000 chars (small elements); if larger, returns a preview and a one-time token to fetch the full output via confirm_output. Supports testid shortcuts and the `dialog::SELECTOR` scope to read inside the topmost open dialog/sheet.", annotations: ANNOTATIONS.readOnly, inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog." }, maxLength: { type: "number", description: "Maximum number of characters to return (default: 20000)" } }, required: [], }, }; } async execute(args: any, context: ToolContext): Promise<ToolResponse> { const requestedMaxLength = typeof args.maxLength === 'number' && Number.isFinite(args.maxLength) && args.maxLength > 0 ? Math.floor(args.maxLength) : 20000; const PREVIEW_THRESHOLD = 2000; if (!context.page) { return createErrorResponse('Page is not available'); } if (context.browser && !context.browser.isConnected()) { return createErrorResponse('Browser is not connected'); } if (context.page.isClosed()) { return createErrorResponse('Page is not available or has been closed'); } return this.safeExecute(context, async (page) => { try { let effectiveSelector: string | undefined = typeof args.selector === 'string' && args.selector.length > 0 ? args.selector : undefined; // Auto-scope to the topmost open modal when no selector was given — // this matches what a human sees: when a sheet covers the page, only // the sheet's content is reachable. Reading the whole page would // hand the LLM mostly inert content behind the backdrop. let autoScopeNotice = ''; if (!effectiveSelector) { const modal = await this.detectActiveModal(page); if (modal) { effectiveSelector = 'dialog::'; autoScopeNotice = `🪟 Auto-scoped to open modal: ${modal.descriptor}. ` + `Pass an explicit selector to override (e.g. selector: 'body' for the full page).`; } } const hasSelector = !!effectiveSelector; const scopeLabel = hasSelector ? ` (from "${effectiveSelector}")` : ' (entire page)'; const lines: string[] = [`Visible text content${scopeLabel}`]; if (autoScopeNotice) lines.push(autoScopeNotice); let selectionWarning = ''; let textContent = ''; if (hasSelector) { const locator = await this.createScopedLocator(page, effectiveSelector!); const { element, elementIndex, totalCount } = await this.selectPreferredLocator(locator, { originalSelector: effectiveSelector!, }); selectionWarning = await this.formatElementSelectionInfo( effectiveSelector!, elementIndex, totalCount, true ); textContent = await element.evaluate((target: HTMLElement | null) => { if (!target) { return ''; } if (typeof target.innerText === 'string') { return target.innerText; } return target.textContent ?? ''; }); } else { textContent = await page.evaluate(() => document.body?.innerText ?? ''); } textContent = textContent ?? ''; if (selectionWarning) { lines.push(selectionWarning.trimEnd()); } const safeMaxLength = requestedMaxLength > 0 ? requestedMaxLength : 20000; const totalLength = textContent.length; // Large-output guard: return preview + confirm_output token when text is big if (totalLength >= PREVIEW_THRESHOLD) { const preview = makeConfirmPreview(() => textContent, { counts: { totalLength, shownLength: Math.min(500, totalLength), truncated: true, }, previewLines: [ 'Preview (first 500 chars):', textContent.slice(0, 500), ...(totalLength > 500 ? ['...'] : []), '', '⚠️ Full text not returned to save tokens', '', '💡 RECOMMENDED: Use token-efficient alternatives:', ' • inspect_dom() - structured view with positions and layout', ' • find_by_text() - locate specific text with context', ' • query_selector() - inspect specific elements', ], }); lines.push(`Text size: ${totalLength.toLocaleString()} characters (exceeds ${PREVIEW_THRESHOLD} char threshold)`); lines.push(''); lines.push(...preview.lines); return createSuccessResponse(lines.join('\n')); } lines.push(''); let displayText = textContent; const truncated = displayText.length > safeMaxLength; if (truncated) { displayText = `${displayText.slice(0, safeMaxLength)}\n[Output truncated due to size limits]`; } lines.push(displayText); if (truncated) { lines.push(''); lines.push( `Output truncated due to size limits (returned ${safeMaxLength} of ${textContent.length} characters)` ); } lines.push(''); lines.push('💡 TIP: If you need structured inspection, try inspect_dom(), find_by_text(), or query_selector().'); return createSuccessResponse(lines.join('\n')); } catch (error) { return createErrorResponse(`Failed to get visible text content: ${(error as Error).message}`); } }); } } - Input schema for get_text: optional 'selector' (CSS/text/testid selector) and optional 'maxLength' (number, default 20000).
inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog." }, maxLength: { type: "number", description: "Maximum number of characters to return (default: 20000)" } }, required: [], }, }; - src/tools/browser/register.ts:24-53 (registration)Import of GetTextTool in the browser tools registry (line 24) and its inclusion in the BROWSER_TOOL_CLASSES array (line 75).
import { GetTextTool } from './content/get_text.js'; import { GetHtmlTool } from './content/get_html.js'; // Inspection import { InspectDomTool } from './inspection/inspect_dom.js'; import { GetTestIdsTool } from './inspection/get_test_ids.js'; import { QuerySelectorTool } from './inspection/query_selector.js'; import { FindByTextTool } from './inspection/find_by_text.js'; import { CheckVisibilityTool } from './inspection/check_visibility.js'; import { CompareElementAlignmentTool } from './inspection/compare_element_alignment.js'; import { InspectAncestorsTool } from './inspection/inspect_ancestors.js'; import { ElementExistsTool } from './inspection/element_exists.js'; import { MeasureElementTool } from './inspection/measure_element.js'; import { GetComputedStylesTool } from './inspection/get_computed_styles.js'; // Evaluation import { EvaluateTool } from './evaluation/evaluate.js'; // Console import { GetConsoleLogsTool, ClearConsoleLogsTool } from './console/get_console_logs.js'; // Network import { ListNetworkRequestsTool } from './network/list_network_requests.js'; import { GetRequestDetailsTool } from './network/get_request_details.js'; // Waiting import { WaitForElementTool } from './waiting/wait_for_element.js'; import { WaitForNetworkIdleTool } from './waiting/wait_for_network_idle.js'; export const BROWSER_TOOL_CLASSES: ToolClass[] = [ - src/tools/browser/base.ts:203-226 (helper)detectActiveModal() method on BrowserToolBase (the parent class) used by get_text to auto-scope to the topmost open dialog/sheet when no explicit selector is given.
/** * Detect a "user-dominating" open modal — i.e. one that a human would * visually focus on and interact with to the exclusion of the rest of the * page. Used by inspect_dom / get_text / get_html to auto-scope when no * selector is provided, so the LLM's view matches the human's view. * * Strict criterion: requires `aria-modal="true"` (or native `dialog[open]`) * because non-modal `[role="dialog"]` includes things like side panels and * tooltips that don't dominate the page. * * Returns null if no active modal is open. Otherwise returns the topmost * one, ranked by the same z-index walk used by `createScopedLocator()`. */ protected async detectActiveModal(page: Page): Promise<{ descriptor: string; suggestion: string; } | null> { const ACTIVE_MODAL_SELECTOR = '[role="dialog"][aria-modal="true"]:not([aria-hidden="true"]),' + '[role="alertdialog"][aria-modal="true"]:not([aria-hidden="true"]),' + 'dialog[open]'; return await page.evaluate((rootsSelector: string) => { const isUserVisible = (el: Element): boolean => { - src/tools/browser/content/index.ts:2-3 (registration)Re-export of GetTextTool from the content barrel index file.
export { GetTextTool } from './get_text.js'; export { GetHtmlTool } from './get_html.js';