常见坑和解决方案

目录

09. 常见坑和解决方案

30+个常见问题和解决方案


Selenium常见坑

1. NoSuchElementException

问题: 元素找不到,抛出NoSuchElementException

原因:

  • 元素还未加载完成
  • 元素在iframe中
  • 元素被动态创建/删除
  • 定位器错误

解决方案:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

# 使用显式等待
wait = WebDriverWait(driver, 10)
element = wait.until(
    EC.presence_of_element_located((By.ID, 'element-id'))
)

# 或者检查元素是否存在
def element_exists(driver, locator, timeout=3):
    try:
        WebDriverWait(driver, timeout).until(
            EC.presence_of_element_located(locator)
        )
        return True
    except:
        return False

if element_exists(driver, (By.ID, 'element-id')):
    element = driver.find_element(By.ID, 'element-id')

2. ElementNotInteractableException

问题: 元素存在但无法交互

原因:

  • 元素被其他元素遮挡
  • 元素不可见(visibility:hidden, display:none)
  • 元素未完全加载

解决方案:

# 滚动到元素
driver.execute_script("arguments[0].scrollIntoView();", element)

# 等待元素可点击
wait = WebDriverWait(driver, 10)
clickable_element = wait.until(
    EC.element_to_be_clickable((By.ID, 'button-id'))
)
clickable_element.click()

# 使用JavaScript点击
driver.execute_script("arguments[0].click();", element)

3. StaleElementReferenceException

问题: 元素引用失效,页面已刷新

原因:

  • 页面刷新
  • DOM元素被重新创建
  • 动态内容更新

解决方案:

def safe_click(driver, locator):
    """安全点击,处理元素失效"""
    max_retries = 3
    for attempt in range(max_retries):
        try:
            element = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable(locator)
            )
            element.click()
            return True
        except StaleElementReferenceException:
            if attempt == max_retries - 1:
                raise
            time.sleep(0.5)
    return False

# 重新查找元素
def refresh_element(driver, locator):
    """刷新元素引用"""
    return driver.find_element(*locator)

4. TimeoutException

问题: 等待超时

原因:

  • 网络慢
  • 元素永远不会出现
  • 错误的等待条件

解决方案:

# 合理设置超时时间
wait = WebDriverWait(driver, 30)  # 增加超时时间

# 使用更精确的等待条件
wait.until(EC.text_to_be_present_in_element((By.ID, 'status'), '完成'))

# 组合等待条件
from selenium.webdriver.support import expected_conditions as EC

# 等待元素出现且可点击
wait.until(EC.element_to_be_clickable((By.ID, 'submit-btn')))

# 等待URL变化
wait.until(EC.url_contains('/dashboard'))

Appium常见坑

1. Element定位问题

问题: Android/iOS元素定位困难

解决方案:

from appium.webdriver.common.appiumby import AppiumBy

# Android推荐定位方式
# 1. Resource ID (最稳定)
element = driver.find_element(
    AppiumBy.ID, 'com.example.game:id/button_id'
)

# 2. UI Automator (Android特有)
element = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().text("开始游戏")'
)

# 3. Accessibility ID (跨平台)
element = driver.find_element(
    AppiumBy.ACCESSIBILITY_ID, 'start_button'
)

# iOS推荐定位方式
# 1. Accessibility ID
element = driver.find_element(
    AppiumBy.ACCESSIBILITY_ID, 'start_button'
)

# 2. Predicate (iOS特有)
element = driver.find_element(
    AppiumBy.IOS_PREDICATE, 'label == "开始游戏"'
)

2. 权限弹窗处理

问题: 应用启动时出现权限弹窗

解决方案:

def handle_permission_dialogs(driver):
    """处理权限弹窗"""
    permission_buttons = [
        'com.android.packageinstaller:id/permission_allow_button',
        'com.android.packageinstaller:id/permission_deny_button',
        'com.example.game:id/allow_location',
    ]
    
    for button_id in permission_buttons:
        try:
            allow_btn = WebDriverWait(driver, 3).until(
                EC.element_to_be_clickable((AppiumBy.ID, button_id))
            )
            allow_btn.click()
            break
        except:
            continue

# 在测试开始时调用
handle_permission_dialogs(driver)

3. 设备兼容性问题

问题: 不同设备上表现不一致

解决方案:

def get_device_info(driver):
    """获取设备信息"""
    # Android
    if driver.capabilities['platformName'] == 'Android':
        model = driver.execute_script('mobile: shell', {
            'command': 'getprop ro.product.model'
        })
        version = driver.execute_script('mobile: shell', {
            'command': 'getprop ro.build.version.release'
        })
    # iOS
    else:
        model = driver.capabilities.get('deviceName')
        version = driver.capabilities.get('platformVersion')
    
    return {'model': model, 'version': version}

def adaptive_tap(driver, x, y):
    """自适应点击,考虑不同屏幕尺寸"""
    size = driver.get_window_size()
    scale_x = x / 1080  # 基于1080宽度设计
    scale_y = y / 1920  # 基于1920高度设计
    
    actual_x = int(size['width'] * scale_x)
    actual_y = int(size['height'] * scale_y)
    
    driver.tap([(actual_x, actual_y)])

Pytest常见坑

1. fixture作用域问题

问题: fixture被多次创建

解决方案:

import pytest

# 错误示例: 每次测试都创建新driver
@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

# 正确示例: 使用适当作用域
@pytest.fixture(scope='session')  # 整个测试会话只创建一次
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

# 或者使用function作用域(每个测试函数)
@pytest.fixture(scope='function')
def fresh_page(driver):
    driver.get('https://game.com')
    yield driver
    # 清理操作

2. 测试依赖问题

问题: 测试之间有依赖,导致失败

解决方案:

# 错误: 测试依赖其他测试
def test_create_user():
    global user_id
    user_id = create_user('test_user')

def test_update_user():
    # 依赖test_create_user
    update_user(user_id, 'new_name')

# 正确: 每个测试独立
def test_create_user():
    user = create_user('test_user')
    assert user.name == 'test_user'

def test_update_user():
    # 独立创建用户
    user = create_user('test_user')
    updated_user = update_user(user.id, 'new_name')
    assert updated_user.name == 'new_name'

3. 并发测试问题

问题: 并行执行时出现数据冲突

解决方案:

import threading

# 使用锁保护共享资源
lock = threading.Lock()

def create_unique_user():
    with lock:
        # 生成唯一用户名
        timestamp = int(time.time() * 1000)
        username = f"test_user_{timestamp}"
        return create_user(username)

# 或者使用pytest-xdist的测试隔离
@pytest.fixture(scope='session')
def unique_test_data():
    """为每个进程生成唯一测试数据"""
    import os
    process_id = os.getpid()
    return f"test_data_{process_id}"

性能测试常见坑

1. 虚假性能结果

问题: 测试结果不准确

解决方案:

# 确保测试环境一致性
def setup_performance_test():
    """性能测试前的环境准备"""
    # 清理缓存
    clear_cache()
    
    # 重启服务
    restart_game_server()
    
    # 等待服务稳定
    time.sleep(5)
    
    # 预热
    for _ in range(10):
        make_test_request()

# 避免预热数据影响
def performance_test():
    setup_performance_test()
    
    # 预热请求
    for _ in range(100):
        make_test_request()
    
    # 实际测试
    results = []
    for _ in range(1000):
        start = time.time()
        response = make_test_request()
        response_time = (time.time() - start) * 1000
        results.append(response_time)
    
    return results

2. 资源泄露

问题: 长时间运行后内存/CPU占用过高

解决方案:

import gc
import psutil

def monitor_resources():
    """监控资源使用"""
    process = psutil.Process()
    memory_mb = process.memory_info().rss / 1024 / 1024
    cpu_percent = process.cpu_percent()
    
    print(f"Memory: {memory_mb:.2f}MB, CPU: {cpu_percent}%")
    
    # 强制垃圾回收
    if memory_mb > 1000:  # 超过1GB
        gc.collect()

# 在长时间测试中定期调用
def long_running_test():
    for i in range(10000):
        make_request()
        if i % 1000 == 0:  # 每1000次监控一次
            monitor_resources()

数据库测试常见坑

1. 测试数据污染

问题: 测试数据相互影响

解决方案:

import pytest

@pytest.fixture
def clean_database():
    """每次测试前清理数据库"""
    # 创建测试数据库
    test_db = create_test_database()
    
    yield test_db
    
    # 清理测试数据库
    drop_test_database(test_db)

@pytest.fixture
def transaction_rollback():
    """使用事务回滚"""
    conn = get_db_connection()
    conn.begin()
    
    try:
        yield conn
    finally:
        conn.rollback()  # 测试结束后回滚
        conn.close()

2. 并发访问冲突

问题: 多个测试同时访问数据库导致冲突

解决方案:

import threading
import sqlite3

# 使用连接池
class DatabasePool:
    def __init__(self, db_path, max_connections=10):
        self.db_path = db_path
        self.max_connections = max_connections
        self.pool = []
        self.lock = threading.Lock()
        
        # 预创建连接
        for _ in range(max_connections):
            conn = sqlite3.connect(db_path)
            self.pool.append(conn)
    
    def get_connection(self):
        with self.lock:
            if self.pool:
                return self.pool.pop()
            else:
                return sqlite3.connect(self.db_path)
    
    def return_connection(self, conn):
        with self.lock:
            if len(self.pool) < self.max_connections:
                self.pool.append(conn)
            else:
                conn.close()

API测试常见坑

1. 状态管理问题

问题: API调用间的状态依赖

解决方案:

class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.auth_token = None
    
    def login(self, username, password):
        """登录并保存token"""
        response = self.session.post(f"{self.base_url}/login", 
                                   json={'username': username, 'password': password})
        if response.status_code == 200:
            self.auth_token = response.json()['token']
            self.session.headers.update({'Authorization': f'Bearer {self.auth_token}'})
        return response
    
    def logout(self):
        """登出并清理token"""
        self.session.headers.pop('Authorization', None)
        self.auth_token = None

# 使用session管理状态
@pytest.fixture
def api_client():
    client = APIClient('https://api.game.com')
    yield client
    client.logout()  # 自动清理

2. 网络超时问题

问题: 网络不稳定导致测试失败

解决方案:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_robust_session():
    """创建具有重试机制的会话"""
    session = requests.Session()
    
    # 配置重试策略
    retry_strategy = Retry(
        total=3,  # 总重试次数
        backoff_factor=1,  # 重试间隔
        status_forcelist=[429, 500, 502, 503, 504],  # 需要重试的状态码
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    # 设置超时
    session.timeout = 30
    
    return session

def make_api_call_with_retry(url, max_retries=3):
    """带重试的API调用"""
    for attempt in range(max_retries):
        try:
            response = requests.get(url, timeout=30)
            response.raise_for_status()
            return response
        except requests.exceptions.RequestException as e:
            if attempt == max_retries - 1:
                raise e
            time.sleep(2 ** attempt)  # 指数退避

移动端特殊问题

1. 应用状态管理

问题: 应用状态影响测试结果

解决方案:

def reset_app_state(driver):
    """重置应用状态"""
    # 方式1: 使用noReset=false
    # 在capabilities中设置noReset=false
    
    # 方式2: 手动清理
    driver.remove_app('com.example.game')  # 卸载应用
    driver.install_app('/path/to/app.apk')  # 重新安装
    
    # 方式3: 清理应用数据
    driver.execute_script('mobile: shell', {
        'command': 'pm clear com.example.game'
    })

# 在测试前后调用
@pytest.fixture
def fresh_app(driver):
    reset_app_state(driver)
    driver.launch_app()
    yield driver
    driver.close_app()

2. 网络环境问题

问题: 不同网络环境下表现不一致

解决方案:

def set_network_conditions(driver, network_type='wifi'):
    """设置网络条件"""
    if driver.capabilities['platformName'] == 'Android':
        if network_type == '2g':
            driver.execute_script('mobile: shell', {
                'command': 'am start -a android.intent.action.VIEW -d "https://www.google.com"'
            })
        elif network_type == 'offline':
            driver.execute_script('mobile: shell', {
                'command': 'svc data disable && svc wifi disable'
            })
    else:  # iOS
        # iOS需要使用XCUITest的网络模拟
        pass

def test_with_network_conditions():
    """在不同网络条件下测试"""
    for network in ['wifi', '3g', '4g']:
        set_network_conditions(driver, network)
        # 执行测试
        result = test_login()
        print(f"网络类型: {network}, 结果: {result}")

环境配置问题

1. 浏览器驱动问题

问题: ChromeDriver版本不匹配

解决方案:

from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service

# 自动管理驱动版本
def create_driver():
    service = Service(ChromeDriverManager().install())
    options = webdriver.ChromeOptions()
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    return webdriver.Chrome(service=service, options=options)

# 或者指定特定版本
def create_driver_with_version(version):
    service = Service(ChromeDriverManager(version=version).install())
    return webdriver.Chrome(service=service)

2. 环境变量问题

问题: 测试环境配置不一致

解决方案:

import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

class Config:
    def __init__(self):
        self.base_url = os.getenv('BASE_URL', 'https://game.com')
        self.browser = os.getenv('BROWSER', 'chrome')
        self.headless = os.getenv('HEADLESS', 'false').lower() == 'true'
        self.timeout = int(os.getenv('TIMEOUT', '10'))

# .env文件
"""
BASE_URL=https://staging.game.com
BROWSER=chrome
HEADLESS=true
TIMEOUT=15
"""

数据分析常见坑

1. 数据质量问题

问题: 测试数据不准确影响分析结果

解决方案:

import pandas as pd
import numpy as np

def clean_performance_data(df):
    """清理性能测试数据"""
    # 移除异常值 (使用IQR方法)
    Q1 = df['response_time'].quantile(0.25)
    Q3 = df['response_time'].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    df_cleaned = df[
        (df['response_time'] >= lower_bound) & 
        (df['response_time'] <= upper_bound)
    ]
    
    # 处理缺失值
    df_cleaned = df_cleaned.dropna()
    
    # 检查数据一致性
    print(f"原始数据: {len(df)} 条")
    print(f"清理后: {len(df_cleaned)} 条")
    print(f"清理比例: {(len(df) - len(df_cleaned))/len(df)*100:.2f}%")
    
    return df_cleaned

2. 统计分析误区

问题: 错误的统计方法导致错误结论

解决方案:

def proper_performance_analysis(df):
    """正确的性能分析"""
    # 使用适当的统计量
    stats = {
        'mean': df['response_time'].mean(),
        'median': df['response_time'].median(),  # 中位数对异常值不敏感
        'p95': df['response_time'].quantile(0.95),  # P95更能反映用户体验
        'p99': df['response_time'].quantile(0.99),
        'std': df['response_time'].std(),
        'min': df['response_time'].min(),
        'max': df['response_time'].max()
    }
    
    # 避免平均值误导
    print(f"平均响应时间: {stats['mean']:.2f}ms")
    print(f"中位数响应时间: {stats['median']:.2f}ms")
    print(f"注意: 如果平均值远大于中位数,可能存在异常值")
    
    return stats

最佳实践总结

预防措施

  1. 充分的等待策略: 使用显式等待而非固定时间等待
  2. 异常处理: 为可能失败的操作添加重试机制
  3. 数据隔离: 确保测试数据相互独立
  4. 环境一致性: 测试环境应与生产环境尽可能一致
  5. 监控和日志: 记录详细的测试日志便于问题排查

调试技巧

def debug_element(driver, locator):
    """调试元素定位问题"""
    print(f"尝试定位: {locator}")
    
    # 检查页面源码
    print("页面源码片段:")
    print(driver.page_source[:500])
    
    # 尝试不同的定位方式
    try:
        element = driver.find_element(*locator)
        print(f"元素位置: {element.location}")
        print(f"元素大小: {element.size}")
        print(f"元素属性: {element.get_attributes()}")
    except Exception as e:
        print(f"定位失败: {e}")
    
    # 截图
    driver.save_screenshot('debug_screenshot.png')
    print("已保存调试截图")

下一步

继续学习: