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
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- argus/mcp_server.py:249-249 (registration)The tool 'get_page_state' is registered as an MCP tool using @mcp.tool() decorator on the async function get_page_state().
@mcp.tool() - argus/mcp_server.py:250-311 (handler)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) - argus/browser.py:288-309 (helper)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", []), ) - argus/browser.py:42-181 (helper)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; } """ - argus/models.py:65-81 (schema)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)