StarrySkyWorld commited on
Commit
81b8721
·
1 Parent(s): bf999bf

Capture debug screenshot on failure

Browse files
backend/core/trae_bot.py CHANGED
@@ -10,6 +10,8 @@ import logging
10
  import threading
11
  import json
12
  import os
 
 
13
 
14
  logger = logging.getLogger("TraeBot")
15
 
@@ -24,6 +26,11 @@ class TraeBot:
24
  self.logs = []
25
  self.status = "idle" # idle, running, waiting_for_scan, success, error
26
  self.current_stage = "Initializing"
 
 
 
 
 
27
 
28
  def log(self, message: str, level="info"):
29
  entry = {"timestamp": time.time(), "message": message, "level": level}
@@ -40,6 +47,27 @@ class TraeBot:
40
  def update_stage(self, stage: str):
41
  self.current_stage = stage
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def generate_password(self, length: int = 12) -> str:
44
  characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()"
45
  return "".join(random.choice(characters) for _ in range(length))
@@ -48,6 +76,7 @@ class TraeBot:
48
  self.status = "running"
49
  self.update_stage("Starting Browser")
50
  start_time = time.time()
 
51
 
52
  try:
53
  self.log("Starting Browser and navigate to Trae.ai")
@@ -58,7 +87,7 @@ class TraeBot:
58
  window=(1280, 720),
59
  humanize=False
60
  ) as browser:
61
- page: Page = browser.new_page()
62
 
63
  def check_timeout():
64
  if self.status != "waiting_for_scan" and time.time() - start_time > 60:
@@ -254,6 +283,8 @@ class TraeBot:
254
  page.goto("https://www.trae.ai/account-settings")
255
 
256
  except Exception as e:
 
 
257
  self.log(f"Process failed inside browser: {e}", "error")
258
  self.status = "error"
259
  raise e
@@ -268,6 +299,8 @@ class TraeBot:
268
  self.log("Registration process completed successfully")
269
 
270
  except Exception as e:
 
 
271
  self.log(f"Process failed: {e}", "error")
272
  self.status = "error"
273
  self.update_stage(f"Failed: {str(e)}")
 
10
  import threading
11
  import json
12
  import os
13
+ import base64
14
+ import traceback
15
 
16
  logger = logging.getLogger("TraeBot")
17
 
 
26
  self.logs = []
27
  self.status = "idle" # idle, running, waiting_for_scan, success, error
28
  self.current_stage = "Initializing"
29
+ self.last_error = None
30
+ self.last_traceback = None
31
+ self.last_url = None
32
+ self.last_html = None
33
+ self.last_screenshot_png_b64 = None
34
 
35
  def log(self, message: str, level="info"):
36
  entry = {"timestamp": time.time(), "message": message, "level": level}
 
47
  def update_stage(self, stage: str):
48
  self.current_stage = stage
49
 
50
+ def _capture_debug(self, page, exc: Exception):
51
+ self.last_error = f"{type(exc).__name__}: {exc}"
52
+ self.last_traceback = traceback.format_exc()
53
+ try:
54
+ self.last_url = getattr(page, "url", None)
55
+ except Exception:
56
+ self.last_url = None
57
+ try:
58
+ html = page.content()
59
+ if isinstance(html, str) and len(html) > 100000:
60
+ html = html[:100000]
61
+ self.last_html = html
62
+ except Exception:
63
+ self.last_html = None
64
+ try:
65
+ png = page.screenshot(full_page=True, type="png")
66
+ if isinstance(png, (bytes, bytearray)):
67
+ self.last_screenshot_png_b64 = base64.b64encode(png).decode("ascii")
68
+ except Exception:
69
+ self.last_screenshot_png_b64 = None
70
+
71
  def generate_password(self, length: int = 12) -> str:
72
  characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()"
73
  return "".join(random.choice(characters) for _ in range(length))
 
76
  self.status = "running"
77
  self.update_stage("Starting Browser")
78
  start_time = time.time()
79
+ page = None
80
 
81
  try:
82
  self.log("Starting Browser and navigate to Trae.ai")
 
87
  window=(1280, 720),
88
  humanize=False
89
  ) as browser:
90
+ page = browser.new_page()
91
 
92
  def check_timeout():
93
  if self.status != "waiting_for_scan" and time.time() - start_time > 60:
 
283
  page.goto("https://www.trae.ai/account-settings")
284
 
285
  except Exception as e:
286
+ if page:
287
+ self._capture_debug(page, e)
288
  self.log(f"Process failed inside browser: {e}", "error")
289
  self.status = "error"
290
  raise e
 
299
  self.log("Registration process completed successfully")
300
 
301
  except Exception as e:
302
+ if page and not self.last_error:
303
+ self._capture_debug(page, e)
304
  self.log(f"Process failed: {e}", "error")
305
  self.status = "error"
306
  self.update_stage(f"Failed: {str(e)}")
backend/main.py CHANGED
@@ -48,6 +48,10 @@ class StatusResponse(BaseModel):
48
  queue_position: int | None = None
49
  active_slots: int
50
  max_slots: int
 
 
 
 
51
 
52
  def run_bot(session_id: str):
53
  with sessions_lock:
@@ -162,8 +166,51 @@ async def get_status(session_id: str):
162
  "queue_position": queue_position,
163
  "active_slots": active_slots,
164
  "max_slots": max_slots,
 
 
 
 
165
  }
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  @app.get("/api/download/{session_id}")
168
  async def download_token(session_id: str):
169
  if session_id not in sessions:
 
48
  queue_position: int | None = None
49
  active_slots: int
50
  max_slots: int
51
+ last_error: str | None = None
52
+ last_traceback: str | None = None
53
+ last_url: str | None = None
54
+ has_screenshot: bool = False
55
 
56
  def run_bot(session_id: str):
57
  with sessions_lock:
 
166
  "queue_position": queue_position,
167
  "active_slots": active_slots,
168
  "max_slots": max_slots,
169
+ "last_error": getattr(bot, "last_error", None),
170
+ "last_traceback": getattr(bot, "last_traceback", None),
171
+ "last_url": getattr(bot, "last_url", None),
172
+ "has_screenshot": bool(getattr(bot, "last_screenshot_png_b64", None)),
173
  }
174
 
175
+ @app.get("/api/debug/{session_id}")
176
+ async def get_debug(session_id: str):
177
+ with sessions_lock:
178
+ if session_id not in sessions:
179
+ raise HTTPException(status_code=404, detail="Session not found")
180
+ bot = sessions[session_id]["bot"]
181
+ return {
182
+ "session_id": session_id,
183
+ "status": bot.status,
184
+ "current_stage": bot.current_stage,
185
+ "last_error": getattr(bot, "last_error", None),
186
+ "last_traceback": getattr(bot, "last_traceback", None),
187
+ "last_url": getattr(bot, "last_url", None),
188
+ "last_html": getattr(bot, "last_html", None),
189
+ "has_screenshot": bool(getattr(bot, "last_screenshot_png_b64", None)),
190
+ }
191
+
192
+ @app.get("/api/screenshot/{session_id}")
193
+ async def get_screenshot(session_id: str):
194
+ with sessions_lock:
195
+ if session_id not in sessions:
196
+ raise HTTPException(status_code=404, detail="Session not found")
197
+ bot = sessions[session_id]["bot"]
198
+ b64 = getattr(bot, "last_screenshot_png_b64", None)
199
+ if not b64:
200
+ raise HTTPException(status_code=404, detail="Screenshot not available")
201
+ try:
202
+ import base64
203
+
204
+ png = base64.b64decode(b64.encode("ascii"))
205
+ except Exception:
206
+ raise HTTPException(status_code=500, detail="Screenshot decode failed")
207
+
208
+ return Response(
209
+ content=png,
210
+ media_type="image/png",
211
+ headers={"Content-Disposition": f'inline; filename="debug_{session_id}.png"'},
212
+ )
213
+
214
  @app.get("/api/download/{session_id}")
215
  async def download_token(session_id: str):
216
  if session_id not in sessions:
backend/tests/test_debug_capture.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import unittest
3
+
4
+ from core.trae_bot import TraeBot
5
+
6
+
7
+ class DummyPage:
8
+ def __init__(self):
9
+ self.url = "https://example.com/x"
10
+
11
+ def content(self):
12
+ return "<html><body>hi</body></html>"
13
+
14
+ def screenshot(self, full_page=True, type="png"):
15
+ return b"\x89PNG\r\n\x1a\nfake"
16
+
17
+
18
+ class DummyPageNoScreenshot(DummyPage):
19
+ def screenshot(self, full_page=True, type="png"):
20
+ raise RuntimeError("no screenshot")
21
+
22
+
23
+ class TestDebugCapture(unittest.TestCase):
24
+ def test_capture_sets_fields(self):
25
+ bot = TraeBot("sid")
26
+ try:
27
+ raise ValueError("boom")
28
+ except Exception as e:
29
+ bot._capture_debug(DummyPage(), e)
30
+ self.assertIn("ValueError", bot.last_error)
31
+ self.assertIsInstance(bot.last_traceback, str)
32
+ self.assertEqual(bot.last_url, "https://example.com/x")
33
+ self.assertIn("hi", bot.last_html)
34
+ png = base64.b64decode(bot.last_screenshot_png_b64.encode("ascii"))
35
+ self.assertTrue(png.startswith(b"\x89PNG"))
36
+
37
+ def test_capture_survives_screenshot_failure(self):
38
+ bot = TraeBot("sid")
39
+ try:
40
+ raise RuntimeError("boom")
41
+ except Exception as e:
42
+ bot._capture_debug(DummyPageNoScreenshot(), e)
43
+ self.assertIn("RuntimeError", bot.last_error)
44
+ self.assertIsNone(bot.last_screenshot_png_b64)
45
+