from appium import webdriver
from appium.options.android import UiAutomator2Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotInteractableException
import time
# ==================== 目标应用配置 ====================
TARGET_PACKAGE = "com.brother.ptouch.iprintandlabel"
TARGET_ACTIVITY = ".module.home.home.HomeActivity" # 主页 Activity
MAX_RESTORE_ATTEMPTS = 2
MAX_HISTORY_REPEAT = 3
# ==================== 配置 Appium Options ====================
def create_driver():
options = UiAutomator2Options()
options.set_capability("platformName", "Android")
options.set_capability("deviceName", "emulator-5554")
options.set_capability("automationName", "UiAutomator2")
options.set_capability("noReset", True)
options.set_capability("fullReset", False)
options.set_capability("autoGrantPermissions", True)
options.set_capability("newCommandTimeout", 600)
options.set_capability("appPackage", TARGET_PACKAGE)
options.set_capability("appActivity", TARGET_ACTIVITY)
driver = webdriver.Remote('http://localhost:4723', options=options)
return driver
class AndroidAppExplorer:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
self.clicked_identifiers = set()
self.recovery_history = []
self.restore_attempt_count = 0
def get_current_state(self):
try:
current_package = self.driver.current_package
current_activity = self.driver.current_activity
return current_package, current_activity
except:
return None, None
def is_in_target_app(self):
pkg, _ = self.get_current_state()
return pkg == TARGET_PACKAGE
def wait_for_page_load(self, timeout=10):
"""等待页面加载完成,可通过是否存在关键 UI 元素判断"""
# print(f"⏳ 等待页面加载最多 {timeout}s...")
try:
# 等待任意一个可见的 ViewGroup 或 FrameLayout(代表页面结构已出现)
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((
'xpath',
'//android.view.ViewGroup[@displayed="true"] | '
'//android.widget.FrameLayout[@displayed="true"]'
))
)
time.sleep(1) # 额外缓冲
# print("✅ 页面加载完成")
except Exception as e:
print(f"⚠️ 页面等待超时或出错: {e}")
def ensure_in_target_app(self):
current_pkg, current_act = self.get_current_state()
if not self.is_in_target_app():
print(f"⚠️ 当前不在目标应用 ({current_pkg}/{current_act}),正在尝试恢复...")
current_key = (current_pkg, current_act)
recent_entries = self.recovery_history[-10:]
if recent_entries.count(current_key) >= MAX_HISTORY_REPEAT:
print(f"🛑 已多次回到 {current_key},疑似陷入循环,停止恢复。")
return False
self.recovery_history.append(current_key)
# 方法1:back 返回
if self.restore_attempt_count < MAX_RESTORE_ATTEMPTS:
try:
self.driver.back()
time.sleep(1.5)
if self.is_in_target_app():
print("✅ 通过 back 成功返回目标 App")
self.restore_attempt_count = 0
self.wait_for_page_load(10) # ✅ 关键:等待页面加载
return True
except:
pass
# 方法2:activate_app
if self.restore_attempt_count < MAX_RESTORE_ATTEMPTS:
try:
self.driver.activate_app(TARGET_PACKAGE)
print("🔁 正在通过 activate_app 恢复...")
time.sleep(2)
self.wait_for_page_load(10)
if self.is_in_target_app():
print("✅ 成功激活目标 App")
self.restore_attempt_count = 0
return True
except Exception as e:
print(f"❌ activate_app 失败: {e}")
# 方法3:冷重启
if self.restore_attempt_count >= MAX_RESTORE_ATTEMPTS:
try:
print("💀 执行冷重启...")
self.driver.terminate_app(TARGET_PACKAGE)
time.sleep(2)
self.driver.activate_app(TARGET_PACKAGE)
time.sleep(5)
self.wait_for_page_load(10)
if self.is_in_target_app():
print("✅ 冷重启成功")
self.restore_attempt_count = 0
return True
except Exception as e:
print(f"❌ 冷重启失败: {e}")
self.restore_attempt_count += 1
if self.restore_attempt_count > 5:
print("🛑 恢复尝试过多,终止运行")
return False
return True
else:
self.recovery_history.append((current_pkg, current_act))
self.wait_for_page_load(1) # 每次正常进入也稍等一下
return True
def get_element_identifier(self, element):
try:
attrs = [
element.get_attribute("resource-id") or "",
element.get_attribute("text") or "",
element.get_attribute("content-desc") or "",
element.tag_name or ""
]
return "/".join([a for a in attrs if a.strip()])
except:
return "unknown_element"
def find_clickable_elements(self):
xpath = """
//*[
(@clickable='true' or @focusable='true') and
@displayed='true' and
@enabled='true'
]
"""
try:
elements = self.driver.find_elements('xpath', xpath)
# 过滤掉不可见的
return [e for e in elements if e.is_displayed()]
except Exception as e:
print(f"❌ 查找元素失败: {e}")
return []
def classify_elements(self, elements):
bottom_nav_container = "com.brother.ptouch.iprintandlabel:id/bottomNavigationView"
nav_ids = [
"com.brother.ptouch.iprintandlabel:id/navigation_my_label",
"com.brother.ptouch.iprintandlabel:id/navigation_setting",
"com.brother.ptouch.iprintandlabel:id/navigation_buy"
]
nav_elements = []
non_nav_elements = []
for elem in elements:
try:
res_id = elem.get_attribute("resource-id") or ""
content_desc = elem.get_attribute("content-desc") or ""
text = elem.get_attribute("text") or ""
if (res_id == bottom_nav_container or
res_id in nav_ids or
content_desc in ["My Labels", "Settings", "Shop"]):
nav_elements.append(elem)
else:
non_nav_elements.append(elem)
except Exception as e:
print(f"⚠️ 分类元素时异常: {e}")
continue
return nav_elements, non_nav_elements
def handle_alert_popup(self):
alert_xpath = "//*[contains(@text, 'OK') or contains(@text, '允许') or contains(@text, 'Allow')]"
try:
button = self.driver.find_element('xpath', alert_xpath)
if button.is_displayed():
button.click()
print("✅ 自动处理了弹窗")
time.sleep(1)
except:
pass
def handle_input_field(self, input_elem):
hint = input_elem.get_attribute("hint") or ""
text = input_elem.get_attribute("text") or ""
print(f"\n🔍 发现输入框")
if hint: print(f" 提示: '{hint}'")
if text: print(f" 当前文本: '{text}'")
print("请在设备上手动输入内容(3秒内完成)...")
time.sleep(3)
entered_value = (input_elem.get_attribute("text") or
input_elem.get_attribute("value") or "")
print(f"📝 用户在输入框中输入的内容为: '{entered_value}'")
def click_element_safely(self, element):
try:
identifier = self.get_element_identifier(element)
if identifier in self.clicked_identifiers: #not identifier or
return False
try:
self.driver.execute_script("arguments[0].scrollIntoView(true);", element)
except:
pass
time.sleep(0.5)
element.click()
print(f"🟢 点击成功: {identifier}")
self.clicked_identifiers.add(identifier)
time.sleep(1.5)
return True
except Exception as e:
print(f"🔴 点击失败 {self.get_element_identifier(element)}: {str(e)}")
return False
def explore_page(self):
print("\n🔄 开始探索当前页面...")
while True:
if not self.ensure_in_target_app():
print("❌ 无法恢复至目标应用,退出。")
break
self.handle_alert_popup()
elements = self.find_clickable_elements()
if not elements:
print("📭 当前页面无可点击元素。")
try:
self.driver.back()
print("🔙 返回上一页...")
time.sleep(2)
continue
except:
break
nav_elements, non_nav_elements = self.classify_elements(elements)
print(f"📌 导航栏元素数量: {len(nav_elements)}, 非导航栏元素数量: {len(non_nav_elements)}")
# === 第一阶段:优先点击导航栏元素 ===
navigated = False
for elem in nav_elements:
try:
tag_name = elem.tag_name or ""
if "edit" in tag_name.lower() or "input" in tag_name.lower():
self.handle_input_field(elem)
continue
except:
pass
if self.click_element_safely(elem):
navigated = True
break
if navigated:
continue
# === 第二阶段:点击非导航栏元素 ===
any_clicked = False
for elem in non_nav_elements:
try:
tag_name = elem.tag_name or ""
if any(keyword in (tag_name.lower())
for keyword in ["edit", "input", "text"]):
self.handle_input_field(elem)
continue
except:
pass
if self.click_element_safely(elem):
any_clicked = True
break
if not any_clicked:
print("✅ 当前页面所有可点击元素已处理完毕。")
try:
self.driver.back()
print("🔙 返回上一页继续探索...")
time.sleep(2)
except:
print("🔚 无法返回,探索结束。")
break
time.sleep(1)
def run(self):
print("🚀 启动 Android 应用探索器...")
try:
self.explore_page()
except KeyboardInterrupt:
print("\n👋 用户中断执行。")
except Exception as e:
print(f"💥 执行过程中发生错误: {type(e).__name__}: {e}")
finally:
print("🔚 自动化结束。")
# =============== 主程序入口 ===============
if __name__ == "__main__":
driver = create_driver()
explorer = AndroidAppExplorer(driver)
try:
explorer.run()
finally:
driver.quit()
为什么在运行过程有看到输入框,却没有出发输入框处理?
C:\own\app\python_code\.venv\Scripts\python.exe C:\own\app\python_code\work\IBL_click_all\click_all.py
🚀 启动 Android 应用探索器...
🔄 开始探索当前页面...
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: null/Navigate up/Navigate up
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/navigation_my_label/My Labels/My Labels
📌 导航栏元素数量: 3, 非导航栏元素数量: 3
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/navigation_setting/Settings/Settings
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/navigation_buy/Shop/Shop
⚠️ 当前不在目标应用 (com.android.chrome/org.chromium.chrome.browser.firstrun.FirstRunActivity),正在尝试恢复...
✅ 通过 back 成功返回目标 App
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: null/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/setting_printer/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 5
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/wifi_printer_recycler_view/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 5
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/setting_unit/null
✅ 自动处理了弹窗
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/setting_support/Support/null
⚠️ 当前不在目标应用 (com.android.chrome/org.chromium.chrome.browser.firstrun.FirstRunActivity),正在尝试恢复...
✅ 通过 back 成功返回目标 App
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/setting_information/Information/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 13
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/info_list/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/send_info_check/Send Information/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/privacy_policy_text_view/Privacy Policy/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 21
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/privacy_policy_text2/When you use the SOFTWARE, information from the Brother Machine and the devices connected to the Brother Machine (“Device”), including but not limited to, product model, serial number, locale ID (regional information), OS type of your installation, firmware, use of each function of the SOFTWARE, may be recorded in our server.
Additionally, printing information such as printing date, quantity, the types and sizes of paper, and the types of files, etc. (collectively, “Device Data”) may be recorded in our server.
Device Data (except for the serial number) cannot be used in itself or in combination with any other information we may have to identify you as a person.
We reserve the right to use such Device Data solely for the improvement of the SOFTWARE or any other products/services of Brother, our marketing/research activities without identifying you as a person, product planning or any other related activities for our customer’s benefits (collectively “Purposes”).
We will not use your Device Data other than for the Purposes without your prior consent.
We may disclose your Device Data to third parties in an anonymous format solely for achieving the Purposes, and always by obliging such third parties to control such data as confidential.
Device Data includes the serial number of your Brother Machine.
The serial number can be associated with the data which you might register to our sales company’s product registration website.
However, we will not use the serial number to identify you or other than for the Purposes.
Serial number may be stored in Server located in countries without an adequate level of protection for personal data compared to that in your country, but we will control the serial number in strict accordance with “Brother’s Statement on Device Data Worldwide” which is available at Brother’s Website
<https://web.global.brother/aa/privacy.html> .
/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 21
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/navigation_layout/Create/Create
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/tabLayout/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: null/Templates/Templates
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/download_template_button/DOWNLOAD TEMPLATES/null
📌 导航栏元素数量: 3, 非导航栏元素数量: 5
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/actionButtonNewLabel/NEW LABEL/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/edit_save/Save/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 4
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/edit_save_label_edit/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 4
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/buttonPanel/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/edit_print/Print/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/printer_option_printer/PRINT/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 4
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_select_print_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 5
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_print_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 5
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_resolution_options/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 3
🟢 点击成功: android:id/text1/Normal/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_down/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_up/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 9
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/option_cut_expand/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/auto_cut_switch_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/auto_cut_swh/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/half_cut_switch_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/half_cut_swh/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/chain_print_switch_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/chain_print_swh/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/special_tape_switch_lay/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 14
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/special_tape_swh/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 17
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/add_btn/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 10
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/touch_outside/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 10
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/edit_bottom_sheet_lv/null
⚠️ 当前不在目标应用 (com.google.android.permissioncontroller/com.android.permissioncontroller.permission.ui.GrantPermissionsActivity),正在尝试恢复...
✅ 通过 back 成功返回目标 App
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/text_paper/1.4"/null
✅ 自动处理了弹窗
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
🟢 点击成功: com.brother.ptouch.iprintandlabel:id/text_scale/100%/null
📌 导航栏元素数量: 0, 非导航栏元素数量: 7
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
⚠️ 当前不在目标应用 (com.google.android.apps.nexuslauncher/.NexusLauncherActivity),正在尝试恢复...
🔁 正在通过 activate_app 恢复...
✅ 成功激活目标 App
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
⚠️ 当前不在目标应用 (com.google.android.apps.nexuslauncher/.NexusLauncherActivity),正在尝试恢复...
🔁 正在通过 activate_app 恢复...
✅ 成功激活目标 App
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
⚠️ 当前不在目标应用 (com.google.android.apps.nexuslauncher/.NexusLauncherActivity),正在尝试恢复...
🔁 正在通过 activate_app 恢复...
✅ 成功激活目标 App
📌 导航栏元素数量: 3, 非导航栏元素数量: 6
✅ 当前页面所有可点击元素已处理完毕。
🔙 返回上一页继续探索...
⚠️ 当前不在目标应用 (com.google.android.apps.nexuslauncher/.NexusLauncherActivity),正在尝试恢复...
🛑 已多次回到 ('com.google.android.apps.nexuslauncher', '.NexusLauncherActivity'),疑似陷入循环,停止恢复。
❌ 无法恢复至目标应用,退出。
🔚 自动化结束。
Process finished with exit code 0