Appium 移动端自动化测试(六):处理特殊情况
本篇讲解移动端测试中常见的棘手场景:系统弹窗、权限弹窗、Hybrid App 多上下文切换、以及通知栏和系统对话框的处理。
一、系统弹窗处理
1.1 Android 权限弹窗
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def handle_android_permission(driver, action="allow"):
"""
处理 Android 权限弹窗
:param action: "allow" 允许, "deny" 拒绝
"""
try:
if action == "allow":
# 尝试多种"允许"按钮定位方式
allow_buttons = [
(AppiumBy.ID, "com.android.packageinstaller:id/permission_allow_button"),
(AppiumBy.ID, "android:id/button1"),
(AppiumBy.XPATH, "//*[@text='允许']"),
(AppiumBy.XPATH, "//*[@text='ALLOW']"),
(AppiumBy.XPATH, "//*[@text='While using the app']"),
]
for locator in allow_buttons:
try:
WebDriverWait(driver, 2).until(
EC.element_to_be_clickable(locator)
).click()
return True
except:
continue
else:
deny_buttons = [
(AppiumBy.ID, "com.android.packageinstaller:id/permission_deny_button"),
(AppiumBy.ID, "android:id/button2"),
(AppiumBy.XPATH, "//*[@text='拒绝']"),
(AppiumBy.XPATH, "//*[@text='DENY']"),
]
for locator in deny_buttons:
try:
WebDriverWait(driver, 2).until(
EC.element_to_be_clickable(locator)
).click()
return True
except:
continue
except:
pass
return False
1.2 自动处理所有权限
def auto_handle_all_permissions(driver, max_permissions=5):
"""自动处理所有权限弹窗(全部允许)"""
for _ in range(max_permissions):
handled = handle_android_permission(driver, "allow")
if not handled:
break
1.3 通过 Capabilities 自动授予权限
# 方法一:通过 autoGrantPermissions
capabilities = {
"appium:autoGrantPermissions": True, # 自动授予所有权限
}
# 方法二:通过 adb 提前授权
import subprocess
def grant_permissions(package_name):
"""通过 adb 提前授予所有运行时权限"""
permissions = [
"android.permission.CAMERA",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.READ_CONTACTS",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.RECORD_AUDIO",
]
for perm in permissions:
subprocess.run(
["adb", "shell", "pm", "grant", package_name, perm],
capture_output=True
)
1.4 iOS 系统弹窗
def handle_ios_alert(driver, action="accept"):
"""
处理 iOS 系统弹窗
:param action: "accept" 接受, "decline" 拒绝
"""
try:
alert = WebDriverWait(driver, 3).until(
EC.alert_is_present()
)
if action == "accept":
alert.accept()
else:
alert.dismiss()
return True
except:
return False
二、应用内弹窗处理
2.1 通用弹窗处理器
class AppDialogHandler:
"""应用内弹窗处理器"""
def __init__(self, driver):
self.driver = driver
def close_popups(self):
"""尝试关闭所有已知的弹窗"""
close_strategies = [
# 通用关闭按钮
(AppiumBy.ACCESSIBILITY_ID, "Close"),
(AppiumBy.ACCESSIBILITY_ID, "关闭"),
(AppiumBy.ID, "com.example:id/close_btn"),
(AppiumBy.ID, "com.example:id/iv_close"),
# 跳过按钮
(AppiumBy.XPATH, "//*[@text='跳过']"),
(AppiumBy.XPATH, "//*[@text='Skip']"),
(AppiumBy.XPATH, "//*[@text='不再提示']"),
(AppiumBy.XPATH, "//*[@text="Don't show again"]"),
# 确认按钮
(AppiumBy.XPATH, "//*[@text='确定']"),
(AppiumBy.XPATH, "//*[@text='OK']"),
(AppiumBy.XPATH, "//*[@text='我知道了']"),
# 点击弹窗外区域关闭
]
closed = False
for locator in close_strategies:
try:
element = self.driver.find_element(*locator)
if element.is_displayed():
element.click()
closed = True
break
except:
continue
return closed
def dismiss_ad(self):
"""关闭广告弹窗"""
ad_close_locators = [
(AppiumBy.ID, "com.example:id/ad_close"),
(AppiumBy.XPATH, "//*[@text='关闭广告']"),
(AppiumBy.XPATH, "//*[@content-desc='close ad']"),
(AppiumBy.XPATH, "//*[@resource-id='com.example:id/iv_ad_close']"),
]
for locator in ad_close_locators:
try:
element = self.driver.find_element(*locator)
if element.is_displayed():
element.click()
return True
except:
continue
return False
def wait_and_dismiss(self, timeout=3):
"""等待弹窗出现并关闭"""
import time
start = time.time()
while time.time() - start < timeout:
if self.close_popups():
time.sleep(0.5) # 等待关闭动画
else:
break
2.2 首次启动引导页
def skip_onboarding(driver):
"""跳过首次启动引导页"""
from appium.webdriver.common.appiumby import AppiumBy
skip_locators = [
(AppiumBy.XPATH, "//*[@text='跳过']"),
(AppiumBy.XPATH, "//*[@text='Skip']"),
(AppiumBy.ACCESSIBILITY_ID, "Skip"),
(AppiumBy.ID, "com.example:id/skip_btn"),
]
for locator in skip_locators:
try:
element = driver.find_element(*locator)
if element.is_displayed():
element.click()
return True
except:
continue
# 尝试多次滑动跳过
from appium.webdriver.common.touch_action import TouchAction
size = driver.get_window_size()
for _ in range(5):
try:
driver.find_element(*skip_locators[0]).click()
return True
except:
TouchAction(driver) \
.press(x=size["width"]*0.9, y=size["height"]//2) \
.wait(ms=300) \
.move_to(x=size["width"]*0.1, y=size["height"]//2) \
.release() \
.perform()
return False
三、Hybrid App 多上下文处理
3.1 什么是 Hybrid App?
Hybrid App 是原生容器 + WebView 的混合应用。测试时需要在原生上下文(NATIVE_APP)和 WebView 上下文之间切换。
3.2 上下文管理
class HybridAppHandler:
"""Hybrid App 上下文管理器"""
def __init__(self, driver):
self.driver = driver
def get_all_contexts(self):
"""获取所有可用上下文"""
return self.driver.contexts
def get_current_context(self):
"""获取当前上下文"""
return self.driver.context
def get_webview_contexts(self):
"""获取所有 WebView 上下文"""
return [ctx for ctx in self.driver.contexts if "WEBVIEW" in ctx]
def switch_to_native(self):
"""切换到原生上下文"""
self.driver.switch_to.context("NATIVE_APP")
def switch_to_webview(self, index=0):
"""切换到 WebView 上下文"""
webviews = self.get_webview_contexts()
if webviews:
self.driver.switch_to.context(webviews[index])
return True
return False
def switch_to_webview_by_name(self, name):
"""按名称切换到 WebView"""
for ctx in self.driver.contexts:
if name in ctx:
self.driver.switch_to.context(ctx)
return True
return False
def execute_in_webview(self, script):
"""在 WebView 中执行 JavaScript"""
original_context = self.driver.context
try:
self.switch_to_webview()
return self.driver.execute_script(script)
finally:
self.driver.switch_to.context(original_context)
3.3 Hybrid App 测试示例
def test_hybrid_app_login(driver):
"""Hybrid App 登录测试"""
handler = HybridAppHandler(driver)
# 1. 在原生界面输入凭据
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "usernameInput").send_keys("admin")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "passwordInput").send_keys("123456")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "loginButton").click()
# 2. 等待 WebView 加载
import time
time.sleep(3)
# 3. 切换到 WebView
handler.switch_to_webview()
# 4. 在 WebView 中验证登录结果
welcome = driver.find_element(AppiumBy.CSS_SELECTOR, ".welcome-message")
assert "欢迎" in welcome.text
# 5. 在 WebView 中操作
driver.find_element(AppiumBy.CSS_SELECTOR, "#settings-link").click()
# 6. 切回原生
handler.switch_to_native()
3.4 WebView 等待加载
def wait_for_webview(driver, timeout=15):
"""等待 WebView 上下文可用"""
from selenium.webdriver.support.ui import WebDriverWait
return WebDriverWait(driver, timeout).until(
lambda d: any("WEBVIEW" in ctx for ctx in d.contexts)
)
# 使用
wait_for_webview(driver)
handler = HybridAppHandler(driver)
handler.switch_to_webview()
四、通知栏处理
4.1 打开和操作通知栏
def test_push_notification(driver):
"""测试推送通知"""
# 1. 打开通知栏
driver.open_notifications()
# 2. 查找通知
from appium.webdriver.common.appiumby import AppiumBy
try:
notification = driver.find_element(
AppiumBy.XPATH,
"//*[@text='新消息']"
)
notification.click()
except:
# 如果没有找到通知,关闭通知栏
driver.press_keycode(4) # 返回键
# 3. 验证通知跳转
# ...
4.2 通知栏工具封装
class NotificationHandler:
"""通知栏处理器"""
def __init__(self, driver):
self.driver = driver
def open(self):
"""打开通知栏"""
self.driver.open_notifications()
def close(self):
"""关闭通知栏"""
# Android: 按返回键
self.driver.press_keycode(4)
# 或向下滑动关闭
from appium.webdriver.common.touch_action import TouchAction
size = self.driver.get_window_size()
TouchAction(self.driver) \
.press(x=size["width"]//2, y=0) \
.move_to(x=size["width"]//2, y=size["height"]) \
.release() \
.perform()
def find_notification(self, text):
"""查找包含指定文本的通知"""
try:
return self.driver.find_element(
AppiumBy.XPATH, f"//*[@text='{text}']"
)
except:
return None
def clear_all(self):
"""清除所有通知"""
try:
clear_btn = self.driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "Clear all notifications"
)
clear_btn.click()
except:
pass
五、系统对话框处理
5.1 Android 系统对话框
# 处理 "应用未响应" 对话框
def handle_anr_dialog(driver):
try:
wait_btn = driver.find_element(
AppiumBy.ID, "android:id/button1"
)
if wait_btn.is_displayed():
wait_btn.click() # 点击"等待"
except:
pass
# 处理 "应用已停止" 对话框
def handle_crash_dialog(driver):
try:
ok_btn = driver.find_element(
AppiumBy.ID, "android:id/aerr_close"
)
if ok_btn.is_displayed():
ok_btn.click()
except:
pass
5.2 iOS 系统弹窗
def handle_ios_system_alert(driver):
"""处理 iOS 系统弹窗(需要 XCUITest)"""
try:
# 使用 XCUITest 处理系统弹窗
driver.execute_script("mobile: alert", {"action": "accept"})
except:
pass
六、总结
| 场景 | 解决方案 |
|---|---|
| 权限弹窗 | autoGrantPermissions / adb 提前授权 / 自动点击 |
| 应用弹窗 | 通用弹窗处理器 + 重试 |
| 引导页 | 查找跳过按钮 / 滑动跳过 |
| Hybrid App | 上下文切换(NATIVE_APP ↔ WEBVIEW) |
| 通知栏 | open_notifications + 元素操作 |
| 系统对话框 | 按钮定位 / mobile: alert |
核心原则:移动端弹窗处理要有防御性思维,不要假设弹窗一定出现或一定不出现。
下一篇:Appium 高级特性——复杂手势操作、设备操作和平台特定功能。

浙公网安备 33010602011771号