Selenium Web自动化测试

目录

03. Selenium Web自动化测试

Web游戏自动化测试完全指南


Selenium简介

Selenium是Web自动化测试的行业标准,支持:

  • 所有主流浏览器(Chrome、Firefox、Safari、Edge)
  • 多种编程语言
  • 跨平台运行
  • 丰富的API和工具

应用场景

  • Web游戏功能测试
  • 浏览器游戏UI测试
  • 游戏官网测试
  • H5小游戏测试
  • 回归测试自动化

环境配置

安装Selenium

pip install selenium
pip install webdriver-manager  # 自动管理驱动

配置ChromeDriver

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

# 方式1: 自动管理驱动(推荐)
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)

# 方式2: 手动指定驱动路径
driver = webdriver.Chrome(executable_path='/path/to/chromedriver')

# 方式3: 驱动在PATH中
driver = webdriver.Chrome()

Chrome选项配置

from selenium.webdriver.chrome.options import Options

options = Options()

# 无头模式(后台运行)
options.add_argument('--headless')

# 窗口大小
options.add_argument('--window-size=1920,1080')

# 禁用GPU加速
options.add_argument('--disable-gpu')

# 禁用沙盒(Docker环境需要)
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

# 忽略证书错误
options.add_argument('--ignore-certificate-errors')

# 禁用图片加载(加速)
prefs = {'profile.managed_default_content_settings.images': 2}
options.add_experimental_option('prefs', prefs)

driver = webdriver.Chrome(options=options)

元素定位

8种定位方法

from selenium.webdriver.common.by import By

# 1. ID定位(最快最稳定)
element = driver.find_element(By.ID, 'username')

# 2. Name定位
element = driver.find_element(By.NAME, 'password')

# 3. Class Name定位
element = driver.find_element(By.CLASS_NAME, 'login-btn')

# 4. Tag Name定位
element = driver.find_element(By.TAG_NAME, 'input')

# 5. Link Text定位(完全匹配)
element = driver.find_element(By.LINK_TEXT, '登录')

# 6. Partial Link Text定位(部分匹配)
element = driver.find_element(By.PARTIAL_LINK_TEXT, '登')

# 7. CSS Selector定位(推荐)
element = driver.find_element(By.CSS_SELECTOR, '#username')
element = driver.find_element(By.CSS_SELECTOR, '.login-btn')
element = driver.find_element(By.CSS_SELECTOR, 'input[name="username"]')

# 8. XPath定位(最强大)
element = driver.find_element(By.XPATH, '//input[@id="username"]')
element = driver.find_element(By.XPATH, '//button[text()="登录"]')

CSS Selector技巧

# ID选择器
driver.find_element(By.CSS_SELECTOR, '#player-name')

# Class选择器
driver.find_element(By.CSS_SELECTOR, '.game-button')

# 属性选择器
driver.find_element(By.CSS_SELECTOR, '[data-test-id="login"]')
driver.find_element(By.CSS_SELECTOR, 'input[type="password"]')

# 层级选择器
driver.find_element(By.CSS_SELECTOR, 'div.container > button')
driver.find_element(By.CSS_SELECTOR, '#game-area .player-info')

# 伪类选择器
driver.find_element(By.CSS_SELECTOR, 'button:first-child')
driver.find_element(By.CSS_SELECTOR, 'li:nth-child(2)')

# 组合选择器
driver.find_element(By.CSS_SELECTOR, 'button.primary[type="submit"]')

XPath技巧

# 绝对路径(不推荐,脆弱)
element = driver.find_element(By.XPATH, '/html/body/div[1]/form/input[1]')

# 相对路径(推荐)
element = driver.find_element(By.XPATH, '//input[@id="username"]')

# 文本匹配
element = driver.find_element(By.XPATH, '//button[text()="开始游戏"]')
element = driver.find_element(By.XPATH, '//a[contains(text(), "登录")]')

# 属性匹配
element = driver.find_element(By.XPATH, '//input[@type="text"]')
element = driver.find_element(By.XPATH, '//div[@class="game-panel"]')

# 轴定位
element = driver.find_element(By.XPATH, '//label[text()="用户名"]/following-sibling::input')
element = driver.find_element(By.XPATH, '//button[@id="submit"]/../input')

# 逻辑运算
element = driver.find_element(By.XPATH, '//input[@type="text" and @name="username"]')
element = driver.find_element(By.XPATH, '//button[@class="btn" or @class="button"]')

元素操作

基础操作

# 输入文本
element.send_keys('test_user')

# 清空内容
element.clear()

# 点击
element.click()

# 提交表单
element.submit()

# 获取文本
text = element.text

# 获取属性
value = element.get_attribute('value')
class_name = element.get_attribute('class')

# 获取CSS属性
color = element.value_of_css_property('color')

# 判断元素状态
is_displayed = element.is_displayed()
is_enabled = element.is_enabled()
is_selected = element.is_selected()  # checkbox/radio

下拉框操作

from selenium.webdriver.support.ui import Select

select_element = driver.find_element(By.ID, 'server-select')
select = Select(select_element)

# 通过索引选择
select.select_by_index(0)

# 通过value选择
select.select_by_value('server1')

# 通过可见文本选择
select.select_by_visible_text('美国服务器')

# 获取所有选项
options = select.options
for option in options:
    print(option.text)

# 获取当前选中项
selected = select.first_selected_option

鼠标操作

from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)

# 移动到元素
element = driver.find_element(By.ID, 'menu')
actions.move_to_element(element).perform()

# 点击
actions.click(element).perform()

# 双击
actions.double_click(element).perform()

# 右键
actions.context_click(element).perform()

# 拖拽
source = driver.find_element(By.ID, 'item')
target = driver.find_element(By.ID, 'inventory')
actions.drag_and_drop(source, target).perform()

# 悬停
actions.move_to_element(element).pause(2).perform()

# 链式操作
actions.move_to_element(menu).click().move_to_element(submenu).click().perform()

键盘操作

from selenium.webdriver.common.keys import Keys

element = driver.find_element(By.ID, 'search')

# 输入并回车
element.send_keys('游戏攻略', Keys.ENTER)

# 组合键
element.send_keys(Keys.CONTROL, 'a')  # 全选
element.send_keys(Keys.CONTROL, 'c')  # 复制

# 特殊键
element.send_keys(Keys.TAB)
element.send_keys(Keys.ESCAPE)
element.send_keys(Keys.ARROW_DOWN)

等待策略

1. 隐式等待

# 全局设置,一次设置全局有效
driver.implicitly_wait(10)  # 等待最多10秒

# 查找元素时会自动等待
element = driver.find_element(By.ID, 'username')

注意: 不推荐使用,可能导致不必要的等待。

2. 显式等待(推荐)

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)  # 最多等待10秒

# 等待元素出现
element = wait.until(
    EC.presence_of_element_located((By.ID, 'username'))
)

# 等待元素可见
element = wait.until(
    EC.visibility_of_element_located((By.ID, 'message'))
)

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

# 等待文本出现
wait.until(
    EC.text_to_be_present_in_element((By.ID, 'status'), '成功')
)

# 等待标题包含
wait.until(EC.title_contains('游戏'))

# 等待URL包含
wait.until(EC.url_contains('/game'))

# 等待元素消失
wait.until(
    EC.invisibility_of_element_located((By.ID, 'loading'))
)

3. 自定义等待条件

from selenium.webdriver.support.ui import WebDriverWait

def element_has_css_class(locator, css_class):
    """等待元素具有指定class"""
    def _predicate(driver):
        element = driver.find_element(*locator)
        classes = element.get_attribute('class').split()
        return css_class in classes
    return _predicate

wait = WebDriverWait(driver, 10)
wait.until(element_has_css_class((By.ID, 'player'), 'active'))

窗口和框架

窗口操作

# 获取当前窗口句柄
current_window = driver.current_window_handle

# 获取所有窗口句柄
all_windows = driver.window_handles

# 打开新标签页
driver.execute_script('window.open("https://game.com")')

# 切换窗口
for window in driver.window_handles:
    if window != current_window:
        driver.switch_to.window(window)

# 关闭当前窗口
driver.close()

# 关闭浏览器
driver.quit()

# 窗口大小
driver.set_window_size(1920, 1080)
driver.maximize_window()

# 窗口位置
driver.set_window_position(0, 0)

iframe切换

# 切换到iframe (通过索引)
driver.switch_to.frame(0)

# 切换到iframe (通过name/id)
driver.switch_to.frame('game-frame')

# 切换到iframe (通过WebElement)
iframe = driver.find_element(By.ID, 'game-iframe')
driver.switch_to.frame(iframe)

# 切换回主文档
driver.switch_to.default_content()

# 切换到父框架
driver.switch_to.parent_frame()

Alert处理

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 等待alert出现
alert = WebDriverWait(driver, 10).until(EC.alert_is_present())

# 获取alert文本
text = alert.text

# 接受alert (点击确定)
alert.accept()

# 拒绝alert (点击取消)
alert.dismiss()

# 输入文本到prompt
alert.send_keys('test input')
alert.accept()

JavaScript执行

# 执行JavaScript
driver.execute_script('alert("Hello")')

# 滚动到元素
element = driver.find_element(By.ID, 'footer')
driver.execute_script('arguments[0].scrollIntoView()', element)

# 滚动到页面底部
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')

# 点击被遮挡的元素
driver.execute_script('arguments[0].click()', element)

# 修改元素属性
driver.execute_script('arguments[0].value = "test"', input_element)

# 获取返回值
title = driver.execute_script('return document.title')

# 异步JavaScript
driver.execute_async_script('''
    var callback = arguments[arguments.length - 1];
    setTimeout(function() {
        callback('done');
    }, 1000);
''')

Page Object模式

# pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:
    """页面基类"""
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
    
    def find_element(self, locator):
        return self.wait.until(
            EC.presence_of_element_located(locator)
        )
    
    def click(self, locator):
        element = self.wait.until(
            EC.element_to_be_clickable(locator)
        )
        element.click()
    
    def input_text(self, locator, text):
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)

# pages/login_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage

class LoginPage(BasePage):
    """登录页面"""
    URL = 'https://game.com/login'
    
    # 定位器
    USERNAME_INPUT = (By.ID, 'username')
    PASSWORD_INPUT = (By.ID, 'password')
    LOGIN_BUTTON = (By.ID, 'login-btn')
    ERROR_MESSAGE = (By.CLASS_NAME, 'error-msg')
    
    def open(self):
        self.driver.get(self.URL)
    
    def login(self, username, password):
        self.input_text(self.USERNAME_INPUT, username)
        self.input_text(self.PASSWORD_INPUT, password)
        self.click(self.LOGIN_BUTTON)
    
    def get_error_message(self):
        element = self.find_element(self.ERROR_MESSAGE)
        return element.text

# test_login.py
import pytest
from pages.login_page import LoginPage

class TestLogin:
    @pytest.fixture
    def login_page(self, driver):
        page = LoginPage(driver)
        page.open()
        return page
    
    def test_successful_login(self, login_page):
        login_page.login('valid_user', 'valid_pass')
        # 验证登录成功
        assert 'Welcome' in driver.title
    
    def test_invalid_login(self, login_page):
        login_page.login('invalid', 'invalid')
        error = login_page.get_error_message()
        assert '用户名或密码错误' in error

实战案例

Web游戏自动化测试

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

class TestWebGame:
    @pytest.fixture
    def driver(self):
        driver = webdriver.Chrome()
        driver.maximize_window()
        yield driver
        driver.quit()
    
    def test_game_launch(self, driver):
        """测试游戏启动"""
        driver.get('https://2048game.com')
        wait = WebDriverWait(driver, 10)
        
        # 验证页面加载
        assert '2048' in driver.title
        
        # 验证游戏容器存在
        game_container = wait.until(
            EC.presence_of_element_located(
                (By.CLASS_NAME, 'game-container')
            )
        )
        assert game_container.is_displayed()
    
    def test_game_interaction(self, driver):
        """测试游戏交互"""
        driver.get('https://2048game.com')
        wait = WebDriverWait(driver, 10)
        
        # 等待游戏加载
        wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'tile'))
        )
        
        # 获取初始分数
        score_elem = driver.find_element(By.CLASS_NAME, 'score-container')
        initial_score = int(score_elem.text.split('\n')[0])
        
        # 执行游戏操作
        from selenium.webdriver.common.keys import Keys
        body = driver.find_element(By.TAG_NAME, 'body')
        
        for _ in range(10):
            body.send_keys(Keys.ARROW_UP)
            body.send_keys(Keys.ARROW_LEFT)
        
        # 等待动画
        import time
        time.sleep(2)
        
        # 验证分数变化
        final_score = int(score_elem.text.split('\n')[0])
        assert final_score >= initial_score

常见问题

Q1: 元素找不到?

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

Q2: 元素被遮挡?

# 问题: ElementClickInterceptedException
# 解决1: 滚动到元素
driver.execute_script('arguments[0].scrollIntoView()', element)
element.click()

# 解决2: 用JS点击
driver.execute_script('arguments[0].click()', element)

Q3: StaleElementReferenceException?

# 问题: 页面刷新后元素引用失效
# 解决: 重新查找元素
def find_and_click(locator):
    element = driver.find_element(*locator)
    element.click()

下一步

继续学习: