Skip to main content
Glama

get_page_state

Retrieve current page URL, title, and a numbered list of interactive elements to enable automated clicking, typing, and selecting for web testing.

Instructions

Get the current page URL, title, and all interactive elements.

Returns a numbered list of elements you can interact with using click(index), type_text(index, text), or select_option(index, value).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The tool 'get_page_state' is registered as an MCP tool using @mcp.tool() decorator on the async function get_page_state().
    @mcp.tool()
  • The main handler function for get_page_state. It gets the page state from the browser (via s.browser.get_state() which calls BrowserDriver.get_state()), formats the output including URL, title, interactive elements, page text, toast messages, counts, and CSS indicators.
    async def get_page_state() -> str:
        """Get the current page URL, title, and all interactive elements.
    
        Returns a numbered list of elements you can interact with using
        click(index), type_text(index, text), or select_option(index, value).
        """
        s = _require_session()
        state = await s.browser.get_state()
        s._last_elements = state.elements
    
        if state.url not in s.pages_visited:
            s.pages_visited.append(state.url)
    
        lines = [f"URL: {state.url}", f"Title: {state.title}", "", "Interactive elements:"]
        if not state.elements:
            lines.append("  (none found)")
        for el in state.elements:
            parts = [f"  [{el.index}] <{el.tag}"]
            if el.type:
                parts.append(f' type="{el.type}"')
            parts.append(">")
            if el.text:
                parts.append(f' "{el.text}"')
            if el.placeholder:
                parts.append(f' (placeholder: "{el.placeholder}")')
            if el.href:
                parts.append(f" -> {el.href}")
            if el.disabled:
                parts.append(" [disabled]")
            if el.value:
                parts.append(f' value="{el.value}"')
            lines.append("".join(parts))
    
        # Form/action hints for AI
        form_inputs = [e for e in state.elements if e.tag in ("input", "textarea", "select") and e.type not in ("hidden", "submit", "button")]
        if form_inputs:
            lines.append("")
            lines.append(f"Forms detected: {len(form_inputs)} input fields")
            lines.append("  TIP: Use test_form() to fill and submit this form with auto-verification")
    
        # Page content analysis
        if state.page_text:
            lines.append("")
            lines.append(f"Page text (first 1500 chars):")
            lines.append(state.page_text[:1500])
        if state.toast_messages:
            lines.append("")
            lines.append("Visible toasts/notifications:")
            for toast in state.toast_messages:
                lines.append(f"  [TOAST] {toast}")
        if state.counts:
            lines.append("")
            lines.append("Displayed counts:")
            for label, val in state.counts.items():
                lines.append(f"  {val} {label}")
        if state.css_indicators:
            lines.append("")
            lines.append("CSS state indicators:")
            for ind in state.css_indicators:
                lines.append(f"  .{ind}")
    
        return "\n".join(lines)
  • BrowserDriver.get_state() is the helper method called by the get_page_state handler. It extracts interactive elements via JS (line 316-318) and full page content via JS (line 320-324), then constructs a PageState dataclass.
    async def get_state(self) -> PageState:
        elements = await self._extract_elements()
        content = await self._extract_page_content()
        return PageState(
            url=self._page.url,
            title=await self._page.title(),
            elements=elements,
            page_text=content.get("pageText", ""),
            toast_messages=[t["text"] for t in content.get("toasts", []) if t.get("visible")],
            counts=content.get("counts", {}),
            css_indicators=[
                f"{ind['cls']}:{ind['text']}"
                for ind in content.get("cssIndicators", [])
            ],
            item_lists=content.get("itemLists", {}),
            links=content.get("links", []),
            images=content.get("images", []),
            meta_tags=content.get("metaTags", {}),
            headings=content.get("headings", []),
            accessibility_issues=content.get("a11yIssues", []),
            mixed_content=content.get("mixedContent", []),
        )
  • JavaScript helper _EXTRACT_PAGE_CONTENT_JS that extracts page text, toasts, counts, CSS indicators, item lists, links, images, meta tags, headings, accessibility issues, and mixed content from the DOM.
    _EXTRACT_PAGE_CONTENT_JS = """
    () => {
        const result = { pageText: '', toasts: [], counts: {}, cssIndicators: [], itemLists: {},
                         links: [], images: [], metaTags: {}, headings: [], a11yIssues: [], mixedContent: [] };
    
        // 1. Full visible text — simple and robust
        try {
            result.pageText = (document.body.innerText || '').slice(0, 5000);
        } catch(e) {}
    
        // 2. Toast/notification messages
        try {
            const sels = '.toast, [role="alert"], .alert-success, .alert-error, .alert-warning, [class*="toast"]';
            document.querySelectorAll(sels).forEach(el => {
                const text = el.textContent.trim();
                if (text) {
                    const s = window.getComputedStyle(el);
                    result.toasts.push({
                        text: text.slice(0, 200),
                        visible: s.display !== 'none' && s.visibility !== 'hidden',
                        classes: el.className || ''
                    });
                }
            });
        } catch(e) {}
    
        // 3. Number + label counts
        try {
            document.querySelectorAll('.stat, .stat-val, .count, .badge, h1, h2, h3, p, span').forEach(el => {
                const text = el.textContent.trim();
                const m = text.match(/^(\\d+)\\s+(.+)$/);
                if (m) result.counts[m[2].trim()] = parseInt(m[1], 10);
            });
        } catch(e) {}
    
        // 4. Semantic CSS indicators
        try {
            ['remaining-zero','task-done','error','loading','spinner','alert-error','alert-success'].forEach(cls => {
                document.querySelectorAll('.' + cls).forEach(el => {
                    result.cssIndicators.push({
                        cls: cls,
                        text: el.textContent.trim().slice(0, 100),
                        tag: el.tagName.toLowerCase()
                    });
                });
            });
        } catch(e) {}
    
        // 5. Item lists
        try {
            document.querySelectorAll('.card, .list, ul, ol').forEach(container => {
                const items = container.querySelectorAll('.task-item, .list-item, li, tr');
                if (items.length >= 2) {
                    const key = (container.className || container.tagName).slice(0, 50);
                    result.itemLists[key] = Array.from(items).map(it => it.textContent.trim().slice(0, 200));
                }
            });
        } catch(e) {}
    
        // 6. All links on page
        try {
            document.querySelectorAll('a[href]').forEach(el => {
                const href = el.href;
                if (!href || href.startsWith('javascript:') || href === '#') return;
                result.links.push({
                    href: href,
                    text: (el.textContent || '').trim().slice(0, 100),
                    isInternal: href.startsWith(window.location.origin)
                });
            });
        } catch(e) {}
    
        // 7. Images
        try {
            document.querySelectorAll('img').forEach(el => {
                result.images.push({
                    src: el.src || el.getAttribute('src') || '',
                    alt: el.alt,
                    hasAlt: el.hasAttribute('alt'),
                    naturalWidth: el.naturalWidth,
                    naturalHeight: el.naturalHeight,
                    complete: el.complete,
                    loaded: el.complete && el.naturalWidth > 0
                });
            });
        } catch(e) {}
    
        // 8. Meta tags & headings (SEO)
        try {
            const gm = (n) => { const e = document.querySelector('meta[name=\"'+n+'\"], meta[property=\"'+n+'\"]'); return e ? (e.content||'') : ''; };
            result.metaTags = {
                title: document.title || '',
                description: gm('description'),
                ogTitle: gm('og:title'),
                ogDescription: gm('og:description'),
                ogImage: gm('og:image'),
                canonical: (document.querySelector('link[rel=\"canonical\"]') || {}).href || '',
                viewport: gm('viewport'),
                htmlLang: document.documentElement.lang || ''
            };
            document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(el => {
                result.headings.push({ level: parseInt(el.tagName[1]), text: el.textContent.trim().slice(0, 200) });
            });
        } catch(e) {}
    
        // 9. Accessibility basics
        try {
            document.querySelectorAll('img').forEach(el => {
                if (!el.hasAttribute('alt')) {
                    result.a11yIssues.push({type:'img_no_alt', src: (el.src||'').slice(0,100)});
                }
            });
            document.querySelectorAll('input,select,textarea').forEach(el => {
                if (el.type==='hidden'||el.type==='submit'||el.type==='button') return;
                const s = window.getComputedStyle(el);
                if (s.display==='none'||s.visibility==='hidden') return;
                const has = (el.id && document.querySelector('label[for=\"'+el.id+'\"]')) || el.getAttribute('aria-label') || el.getAttribute('aria-labelledby') || el.title || el.closest('label');
                if (!has) result.a11yIssues.push({type:'input_no_label', tag:el.tagName.toLowerCase(), inputType:el.type||'', name:el.name||'', placeholder:el.placeholder||''});
            });
            document.querySelectorAll('button, a, [role=\"button\"]').forEach(el => {
                const s = window.getComputedStyle(el);
                if (s.display==='none'||s.visibility==='hidden') return;
                if (!(el.textContent||'').trim() && !el.getAttribute('aria-label') && !el.title && !el.querySelector('img[alt]')) {
                    result.a11yIssues.push({type:'no_accessible_name', tag:el.tagName.toLowerCase(), html:el.outerHTML.slice(0,150)});
                }
            });
            if (!document.documentElement.lang) result.a11yIssues.push({type:'no_html_lang'});
        } catch(e) {}
    
        // 10. Mixed content
        try {
            if (window.location.protocol === 'https:') {
                const ck = (sel, attr) => { document.querySelectorAll(sel).forEach(el => { const u=el.getAttribute(attr)||''; if(u.startsWith('http://')) result.mixedContent.push({url:u.slice(0,200),tag:el.tagName.toLowerCase(),attr:attr}); }); };
                ck('img[src]','src'); ck('script[src]','src'); ck('link[href]','href'); ck('iframe[src]','src'); ck('video[src]','src'); ck('audio[src]','src');
            }
        } catch(e) {}
    
        return result;
    }
    """
  • PageState dataclass definition — the schema/model that represents the page state returned by get_page_state. Contains url, title, elements, page_text, toast_messages, counts, css_indicators, item_lists, links, images, meta_tags, headings, accessibility_issues, mixed_content, and timestamp.
    @dataclass
    class PageState:
        url: str
        title: str
        elements: List[InteractiveElement]
        page_text: str = ""
        toast_messages: List[str] = field(default_factory=list)
        counts: Dict[str, int] = field(default_factory=dict)
        css_indicators: List[str] = field(default_factory=list)
        item_lists: Dict[str, List[str]] = field(default_factory=dict)
        links: List[Dict] = field(default_factory=list)
        images: List[Dict] = field(default_factory=list)
        meta_tags: Dict[str, str] = field(default_factory=dict)
        headings: List[Dict] = field(default_factory=list)
        accessibility_issues: List[Dict] = field(default_factory=list)
        mixed_content: List[Dict] = field(default_factory=list)
        timestamp: datetime = field(default_factory=datetime.now)
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without annotations, the description clearly indicates the tool returns a snapshot of interactive elements without mentioning side effects or destructive actions. It does not describe caching or refresh behavior, but for a getter this is acceptable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: first states what is retrieved, second describes how the output can be used. No wasted words, front-loaded with purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters and an output schema, the description covers the tool's purpose and output usage well. It could mention that a session must be active, but that is implied by sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no parameters (empty object), so the description adds value by explaining the output structure and how to use it. Baseline 4 for zero parameters is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves the current page URL, title, and all interactive elements, and explains the output format as a numbered list usable with sibling tools. This distinguishes it well from other tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains that the output can be used with click, type_text, and select_option, implying it should be used before those interactions. However, it lacks explicit guidance on when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chriswu727/argus'

If you have feedback or need assistance with the MCP directory API, please join our Discord server