Trong quá trình thực hiện kiểm thử tự động (Automation Testing) trên iOS bằng framework AirTest và Poco, bạn có thể gặp phải lỗi hệ thống dừng đột ngột với thông báo RuntimeError: call depth exceed 4. Lỗi này thường phát sinh khi script cố gắng tương tác hoặc kiểm tra sự tồn tại của một phần tử UI nhưng gặp vấn đề trong quá trình truy xuất dữ liệu từ WebDriverAgent (WDA).
Dưới đây là chi tiết log lỗi điển hình:
File "/site-packages/poco/proxy.py", line 657, in wait
while not self.exists():
File "/site-packages/poco/proxy.py", line 774, in exists
return self.attr('visible')
File "/site-packages/poco/proxy.py", line 734, in attr
nodes = self._do_query(multiple=False)
File "/site-packages/poco/proxy.py", line 884, in _do_query
self._nodes = self.poco.agent.hierarchy.select(self.query, multiple)
File "/site-packages/poco/sdk/Selector.py", line 77, in select
return self.selectImpl(cond, multiple, self.getRoot(), 9999, True, True)
File "/site-packages/poco/sdk/Selector.py", line 71, in getRoot
return self.dumper.getRoot()
File "/site-packages/poco/drivers/ios/__init__.py", line 54, in dumpHierarchy
jsonObj = self.client.driver.source(format='json')
File "/site-packages/wda/__init__.py", line 464, in source
return self.http.get('source?format=' + format).value
File "/site-packages/wda/utils.py", line 43, in _inner
raise RuntimeError("call depth exceed %d" % n)
RuntimeError: call depth exceed 4
Phân tích nguyên nhân gốc rễ
Vấn đề nằm ở cơ chế giao tiếp giữa thư viện Poco và driver điều khiển iOS (thường là facebook-wda). Cụ thể:
- Khi gọi
wait().exists(), Poco sẽ liên tục thực hiện dump cấu trúc UI (Hierarchy) từ thiết bị. - Mỗi thao tác dump sẽ gửi một yêu cầu HTTP đến WDA.
- Trong thư viện
wda, phương thức xử lý yêu cầu được bao bọc bởi một decorator có tên@limit_call_depth(4). - Nếu kết nối mạng không ổn định hoặc dịch vụ WDA trên iPhone phản hồi chậm, yêu cầu HTTP sẽ thất bại và kích hoạt cơ chế đệ quy để thử lại.
- Khi số lần đệ quy vượt quá giới hạn cho phép (mặc định là 4), decorator này sẽ ném ra lỗi
RuntimeErrorđể ngăn chặn việc treo tiến trình vô hạn.
Giải pháp xử lý an toàn
Để khắc phục tình trạng này, chúng ta cần xây dựng một hàm bao bọc (wrapper) để bắt ngoại lệ và thực hiện tái thử thi với khoảng nghỉ hợp lý, giúp script hoạt động ổn định hơn.
def verify_ui_element_safe(ui_proxy, wait_time=10, max_attempts=3, retry_delay=5):
"""
Kiểm tra sự tồn tại của phần tử Poco một cách an toàn,
xử lý ngoại lệ 'call depth exceed' từ driver iOS.
Args:
ui_proxy: Đối tượng Poco proxy (ví dụ: poco("Button_Login"))
wait_time: Thời gian chờ tối đa cho mỗi lần kiểm tra (giây)
max_attempts: Số lần thử lại tối đa khi gặp lỗi RuntimeError
retry_delay: Thời gian nghỉ giữa các lần thử lại (giây)
Returns:
bool: Trả về True nếu phần tử tồn tại, ngược lại trả về False.
"""
for i in range(max_attempts):
try:
# Thực hiện kiểm tra sự tồn tại của phần tử
return ui_proxy.wait(timeout=wait_time).exists()
except RuntimeError as err:
error_msg = str(err)
if "call depth exceed" in error_msg:
print(f"[Cảnh báo] Lỗi WDA (Attempt {i+1}/{max_attempts}): {error_msg}")
if i < max_attempts - 1:
sleep(retry_delay)
else:
print("[Lỗi] Đã đạt giới hạn thử lại tối đa. Bỏ qua phần tử.")
return False
else:
# Nếu là lỗi RuntimeError khác thì ném tiếp ra ngoài
raise err
except Exception as other_err:
print(f"[Lỗi không xác định]: {other_err}")
return False
return False
Bằng cách sử dụng hàm verify_ui_element_safe thay cho lời gọi trực tiếp, script của bạn sẽ có khả năng tự phục hồi khi gặp các lỗi kết nối tạm thời với WebDriverAgent, tránh việc toàn bộ quá trình kiểm thử bị gián đoạn chỉ vì một lỗi dump UI đơn lẻ.