Skip to content

Commit 9291e00

Browse files
authored
feat: added read_logs function to browser tool (playwright backend) (gptme#553)
* feat: added read_logs function to browser tool (playwright backend) * fix: fixed backslash in f-string
1 parent a78c056 commit 9291e00

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

gptme/tools/_browser_playwright.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ._browser_thread import BrowserThread
1515

1616
_browser: BrowserThread | None = None
17+
_last_logs: dict = {"logs": [], "errors": [], "url": None}
1718
logger = logging.getLogger(__name__)
1819

1920

@@ -26,7 +27,9 @@ def get_browser() -> BrowserThread:
2627

2728

2829
def _load_page(browser: Browser, url: str) -> str:
29-
"""Load a page and return its body HTML"""
30+
"""Load a page and return its body HTML, always capturing logs"""
31+
global _last_logs
32+
3033
context = browser.new_context(
3134
locale="en-US",
3235
geolocation={"latitude": 37.773972, "longitude": 13.39},
@@ -35,7 +38,39 @@ def _load_page(browser: Browser, url: str) -> str:
3538

3639
logger.info(f"Loading page: {url}")
3740
page = context.new_page()
38-
page.goto(url)
41+
42+
# Always capture logs
43+
logs = []
44+
page_errors = []
45+
46+
def on_console(msg):
47+
logs.append(
48+
{
49+
"type": msg.type,
50+
"text": msg.text,
51+
"location": f"{msg.location.get('url', 'unknown')}:{msg.location.get('lineNumber', 'unknown')}:{msg.location.get('columnNumber', 'unknown')}"
52+
if msg.location
53+
else "unknown",
54+
}
55+
)
56+
57+
def on_page_error(error):
58+
page_errors.append(f"Page error: {error}")
59+
60+
page.on("console", on_console)
61+
page.on("pageerror", on_page_error)
62+
63+
# Navigate to the page
64+
try:
65+
page.goto(url)
66+
# Wait for page to be fully loaded (includes network idle)
67+
page.wait_for_load_state("networkidle")
68+
except Exception as e:
69+
page_errors.append(f"Navigation error: {str(e)}")
70+
# Don't re-raise, just capture the error
71+
72+
# Store logs globally
73+
_last_logs = {"logs": logs, "errors": page_errors, "url": url}
3974

4075
return page.inner_html("body")
4176

@@ -47,6 +82,31 @@ def read_url(url: str) -> str:
4782
return html_to_markdown(body_html)
4883

4984

85+
def read_logs() -> str:
86+
"""Read browser console logs from the last read URL."""
87+
global _last_logs
88+
89+
if not _last_logs["url"]:
90+
return "No URL has been read yet."
91+
92+
result = [f"=== Logs for {_last_logs['url']} ==="]
93+
94+
if _last_logs["logs"]:
95+
result.append("\n=== Console Logs ===")
96+
for log in _last_logs["logs"]:
97+
result.append(f"[{log['type'].upper()}] {log['text']} ({log['location']})")
98+
99+
if _last_logs["errors"]:
100+
result.append("\n=== Page Errors ===")
101+
for error in _last_logs["errors"]:
102+
result.append(error)
103+
104+
if not _last_logs["logs"] and not _last_logs["errors"]:
105+
result.append("\nNo logs or errors captured.")
106+
107+
return "\n".join(result)
108+
109+
50110
def _search_google(browser: Browser, query: str) -> str:
51111
query = urllib.parse.quote(query)
52112
url = f"https://www.google.com/search?q={query}&hl=en"

gptme/tools/browser.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656

5757
# noreorder
5858
if browser == "playwright":
59+
from ._browser_playwright import read_logs as read_logs_playwright # fmt: skip
5960
from ._browser_playwright import read_url as read_url_playwright # fmt: skip
6061
from ._browser_playwright import (
6162
screenshot_url as screenshot_url_playwright, # fmt: skip
@@ -107,6 +108,17 @@ def examples(tool_format):
107108
{ToolUse("ipython", [], "screenshot_url('https://activitywatch.net')").to_output(tool_format)}
108109
System:
109110
{ToolUse("result", [], "Screenshot saved to screenshot.png").to_output()}
111+
112+
### Read URL and check browser logs
113+
User: read this page and check if there are any console errors
114+
Assistant: I'll read the page first and then check the browser logs.
115+
{ToolUse("ipython", [], "read_url('https://example.com')").to_output(tool_format)}
116+
System:
117+
{ToolUse("https://example.com", [], "This domain is for use in illustrative examples...").to_output()}
118+
Assistant: Now let me check the browser console logs:
119+
{ToolUse("ipython", [], "read_logs()").to_output(tool_format)}
120+
System:
121+
{ToolUse("result", [], "No logs or errors captured.").to_output()}
110122
""".strip()
111123

112124

@@ -159,11 +171,19 @@ def screenshot_url(url: str, path: Path | str | None = None) -> Path:
159171
raise ValueError("Screenshot not supported with lynx backend")
160172

161173

174+
def read_logs() -> str:
175+
"""Read browser console logs from the last read URL."""
176+
assert browser
177+
if browser == "playwright":
178+
return read_logs_playwright() # type: ignore
179+
raise ValueError("Browser logs not supported with lynx backend")
180+
181+
162182
tool = ToolSpec(
163183
name="browser",
164184
desc="Browse, search or screenshot the web",
165185
examples=examples,
166-
functions=[read_url, search, screenshot_url],
186+
functions=[read_url, search, screenshot_url, read_logs],
167187
available=has_browser_tool,
168188
init=init,
169189
)

0 commit comments

Comments
 (0)