You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python Selenium自动化测试Jenkins流水线随机失败问题求助及代码优化疑问

解决Selenium在Jenkins中随机找不到Next按钮的问题

嘿,我碰到过几乎一模一样的问题——本地跑测试顺得不行,一上Jenkins就随机掉链子,尤其是涉及视频播放后的交互。结合你的描述,截图显示按钮明明可见却找不到,大概率是Jenkins环境的浏览器渲染特性等待策略不够精准导致的误报,下面一步步帮你梳理:

一、原始代码的核心问题

你的原始代码用了time.sleep()presence_of_element_located,这两个点都是随机失败的导火索:

  • time.sleep(120)是硬等待,视频时长可能有波动(比如Jenkins服务器负载高时,视频加载/播放变慢),但更关键的是,视频结束后Next按钮可能是动态添加到DOM或者从不可见变为可见,硬等待根本没法保证元素处于可点击状态。
  • presence_of_element_located只保证元素在DOM中存在,但不保证它可见、可点击——Jenkins里的浏览器通常是无头模式,默认窗口小得可怜,按钮可能直接在视口外,即使DOM存在也无法被Selenium定位到,或者被弹窗、遮罩层挡住。

二、针对随机失败的优化方案

1. 替换硬等待为智能等待,精准监听视频结束

别再死等120秒了,直接监听视频的ended事件,这样能精准等到视频真正播放完毕:

# 先找到视频元素
video_element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "video")))
# 等待视频播放结束
wait.until(lambda driver: driver.execute_script("return arguments[0].ended;", video_element))

这样不管视频在Jenkins里播放得快还是慢,都能准确触发后续操作,避免提前或滞后查找Next按钮。

2. 改用更靠谱的等待条件

presence_of_element_located换成element_to_be_clickable,这个条件会同时检查元素存在、可见且可交互,完美适配Jenkins的无头环境:

next_btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Next')]")))

3. 强制把元素滚动到视口内

Jenkins的无头浏览器默认窗口太小,很多元素会被挤出视口,Selenium点击时直接失败。每次点击前先把元素滚到屏幕中间:

driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", next_btn)
next_btn.click()

4. 配置Jenkins的无头浏览器参数

如果用Chrome,一定要设置合适的窗口大小和新版无头模式,减少和本地环境的渲染差异:

from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument("--headless=new")  # 新版无头模式,和正常浏览器行为几乎一致
chrome_options.add_argument("--window-size=1920,1080")  # 和本地测试用的窗口大小保持一致
chrome_options.add_argument("--disable-gpu")
driver = webdriver.Chrome(options=chrome_options)

三、对你更新后代码的疑问解答

先提个小bug:你代码里定义了play变量,但后面用next指代Play按钮,最后又用driver.execute_script操作play,这会直接抛出NameError,得先把变量名统一好。

然后回答你的核心疑问:是否需要在每个try块后都声明if语句?
是的,但可以优化得更简洁——你的if语句是为了避免None对象调用方法,这个逻辑是对的,但重复写太冗余,不如把查找+点击的逻辑封装成一个通用函数:

from selenium.common.exceptions import TimeoutException

def find_and_click(driver, locator, short_timeout=100, long_timeout=150):
    element = None
    try:
        # 先尝试短时间查找
        element = WebDriverWait(driver, short_timeout).until(EC.element_to_be_clickable(locator))
    except TimeoutException:
        # 先滚动页面再重试,别直接刷新(会丢视频进度)
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        try:
            element = WebDriverWait(driver, long_timeout).until(EC.element_to_be_clickable(locator))
        except TimeoutException:
            # 最后再兜底刷新页面
            driver.refresh()
            element = WebDriverWait(driver, long_timeout).until(EC.element_to_be_clickable(locator))
    if element:
        # 滚动到元素可见区域
        driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", element)
        element.click()
    return element

然后调用这个函数,代码瞬间清爽:

# 设置和本地一致的窗口大小
driver.set_window_size(1920, 1080)

# 点击Play按钮
play_locator = (By.XPATH, "//div[contains(text(),'Play')]")
find_and_click(driver, play_locator)

# 等待视频播放结束
video_element = WebDriverWait(driver, 150).until(EC.presence_of_element_located((By.TAG_NAME, "video")))
WebDriverWait(driver, 300).until(lambda d: d.execute_script("return arguments[0].ended;", video_element))

# 点击Next按钮
next_locator = (By.XPATH, "//button[contains(text(),'Next')]")
find_and_click(driver, next_locator)

这样既解决了重复代码的问题,也让重试逻辑更合理——先滚动页面重试,再考虑刷新,避免不必要的页面刷新导致测试流程中断。

四、额外的调试小技巧

  • 在Jenkins中保存失败时的截图和页面源码:可以在except块里加这两行,方便后续排查:
    driver.save_screenshot("next_btn_failure.png")
    with open("failure_page.html", "w", encoding="utf-8") as f:
        f.write(driver.page_source)
    
  • 开启Selenium的详细日志:启动浏览器时添加日志参数,能看到元素查找失败的具体原因(比如是否有多个匹配元素、元素是否被遮挡)。

内容的提问来源于stack exchange,提问作者Nayden Van

火山引擎 最新活动