浏览器操作综合作业

练习 Selenium 浏览器 9 大核心操作:配置 · 打开关闭 · 导航 · 窗口 · 信息 · 交互 · Cookie · 截图 · 等待

练习说明

本页面提供了丰富的交互式场景,用于练习 Selenium 9 大浏览器核心操作。每个章节都包含可操作的前端 demo 和对应的 Python Selenium 代码模板。

参数配置 打开关闭 页面导航 窗口控制 页面信息 元素交互 Cookie操作 截图保存 超时等待

操作 1:参数配置

通过 ChromeOptions / EdgeOptions / FirefoxOptions 配置。每开关一个选项 → 下方 Python 代码实时变化。

为什么要配置浏览器? Selenium 默认启动的是「裸」浏览器,真实测试往往需要定制: 无头模式 模拟手机 UA 固定下载路径 禁用图片加速

练习目标:打开 / 关闭下面开关 → 实时生成完整 Python 启动脚本

无头模式 默认关
不显示浏览器界面,后台静默运行(CI/服务器必备)
启动即最大化 默认开
Chrome/Edge: --start-maximized;Firefox: driver.maximize_window()
模拟手机 UA 默认关
伪装成 iPhone 14 Pro,触发移动端页面布局
反检测 Anti-Detect 默认关
隐藏 webdriver 特征 + 过滤 CDP,绕过简单风控(Chromium 专用)
IE 兼容模式 Edge 专属
访问 IE-only 老系统(ActiveX/JSP),企业内网刚需(仅 Edge 支持)
配置代理 Proxy 默认关
--proxy-server=http://127.0.0.1:7890,支持 HTTP/SOCKS5(全浏览器)
持久化用户数据目录 默认关
--user-data-dir,保留登录态、Cookie、扩展(Chromium 专用)
禁用图片加载 默认关
跳过所有 img 资源,页面加载快 30-50%(Chrome: --blink-settings;Firefox: profile.set_preference)
禁用 JavaScript Firefox 专属
profile.set_preference("javascript.enabled", false),测试无 JS 降级场景
禁用硬件加速 Firefox 专属
解决老机器无头模式白屏、WebGL 报错:layers.acceleration.disabled
隐身模式 默认关
不继承本机 cookie / 登录态(Chromium 专用)
禁用自动化提示 默认开
excludeSwitches + --disable-infobars,避免「Chrome 正受自动测试软件控制」横幅
自动下载到指定目录 默认关
prefs.default_directory + 跳过下载弹窗
# 点上方任意开关 → 这里立即显示完整 Python 启动脚本
# 默认:Chrome + 启动最大化 + 禁用自动化提示(推荐基础配置)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--start-maximized")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
print("标题:", driver.title)
driver.quit()

📝 作业要求

  1. 写一个 create_driver(profile) 工厂函数:参数是字典(如 {"headless":True, "mobile":True}),内部根据字典内容自动组装 ChromeOptions,返回配置好的 driver
  2. 分别写两段代码:Chrome 用 --blink-settings 禁图,Firefox 用 FirefoxProfile.set_preference 禁图 → 把两者都 print(options) → 直观对比 Selenium 底层对不同引擎的参数传递方式差异
  3. 开启 headless + maximized + nodialog + antidetect 四个开关 → 用 options.to_capabilities() 导出 JSON → json.dump(..., indent=2) 漂亮打印,看最终传给 chromedriver 的完整 capabilities
  4. 禁用图片加载 vs 不禁 —— 用 time.time() 分别测加载同一页面花了多少秒,写出加速比例
💡 提示:第 2 题是本文核心教学点 —— 同样效果,Chromium 走 add_argument 命令行,Firefox 走 profile.set_preference

操作 2:打开 / 关闭页面

get() 打开 URL · close() 关闭当前标签 · quit() 退出整个浏览器进程。

close() vs quit() 区别
close() —— 只关当前标签页,浏览器进程继续活着
Tab A Tab B ✖ Tab C
quit() —— 关所有标签 + 杀掉 chromedriver 进程
Tab A Tab B Tab C
⚠️ 脚本结束必须调 quit(),否则任务管理器里会残留 chromedriver.exe

练习目标:模拟多标签打开,对比 close() 只关当前 vs quit() 全关

浏览器已就绪,点下面「打开」
from selenium import webdriver

driver = webdriver.Chrome()

# ① 打开页面
driver.get("https://www.example.com")
print(driver.title)

# ② 新开标签(JS 方式,最常用)
driver.execute_script("window.open('https://www.baidu.com');")

# ③ 切换到新标签
handles = driver.window_handles
driver.switch_to.window(handles[-1])
print(driver.current_url)     # → 百度

# ④ 切回原来的标签
driver.switch_to.window(handles[0])

# ⑤ 关闭当前标签(如果只剩一个,driver.close() 之后 driver 会失效!)
driver.close()

# ⑥ 退出整个浏览器(关所有标签 + 杀 chromedriver)
driver.quit()

# ⚠️ close() 只关当前 tab;quit() 全关
# ⚠️ 脚本结束必须调 quit(),否则 chromedriver 残留进程!

📝 作业要求

  1. 写一个脚本:先后打开 www.baidu.com 和 www.example.com(用 JS 新开标签),然后 只关闭 example 那个标签,最后 print 当前还剩几个窗口
  2. 验证:忘记写 driver.quit() 会怎样?任务管理器里会多出什么进程?
  3. 在 Selenium 4 里,driver.get("https://...") 和 driver.execute_script("window.location='...'") 有什么区别?
💡 提示:多标签场景下用 driver.window_handles + switch_to.window() 切换

操作 3:页面导航 back · forward · refresh

driver.back() 返回上一页 · forward() 前进到下一页 · refresh() 刷新当前页面。

History Stack(历史栈)—— 理解 back/forward 的关键
每个标签页内部维护一个 历史栈,当前光标指向其中某一项:
[A] → [B] → [C] ← 当前(cursor)
back() = cursor 左移一位 → 显示 B;forward() = cursor 右移一位 → 显示 C
driver.get(D) 会清空 cursor 右边的所有记录,然后把 D 追加到末尾
⚠️ back() 到最左边不会报错;forward() 到最右边也不会报错 —— 静默 no-op

练习目标:访问 A → B → C,然后 back / forward / refresh

"""
历史导航三部曲 —— 完整示例
"""
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(1)

# ===== 构建 history stack =====
driver.get("https://www.page-a.com")     # stack: [A]         cursor → A
driver.get("https://www.page-b.com")     # stack: [A, B]      cursor → B
driver.get("https://www.page-c.com")     # stack: [A, B, C]   cursor → C

# ===== back() / forward() =====
driver.back()                            # cursor → B, 显示 page-b
driver.back()                            # cursor → A, 显示 page-a
driver.back()                            # ⚠️ 到顶了!不会报错,静默 no-op
print(driver.current_url)                # → 还是 page-a

driver.forward()                         # cursor → B
driver.forward()                         # cursor → C
driver.forward()                         # ⚠️ 到底了!也不会报错

# ===== refresh() =====
driver.refresh()
# 等价于:在当前页重新发送一次请求(浏览器的 F5)
# ⚠️ 不等价于 driver.get(driver.current_url)
#    refresh() 会带上 cache-control / 表单重提交提示
#    get() 就是一次全新的导航

# ===== get() 会截断 forward 队列 =====
driver.back()                            # cursor → B
driver.get("https://www.page-d.com")     # stack 变成 [A, B, D] ← C 被砍掉!
# 此时再 forward() 不会回到 C 了 —— 它已经被 purge 掉

driver.quit()

📝 作业要求

  1. 写脚本:连续 get(A) → get(B) → get(C) 三次,然后依次 back()、back()、forward(),每步 print(driver.current_url) 验证位置
    (A、B、C 是 3 个不同的网址,例如:https://www.baidu.com/、https://www.deepseek.com/、https://www.doubao.com/chat/
  2. 刷新当前页面 driver.refresh() 和重新 driver.get(driver.current_url) 有区别吗?用浏览器开发者工具看 Network 面板
  3. 打开新标签后 back() 能不能回到上一个标签的历史?为什么?
💡 提示:每个标签页有独立的 history stack —— 跨标签 back 是不可能的

操作 4:窗口尺寸与位置

maximize_window 最大化 · minimize_window 最小化 · set_window_size / set_window_position / set_window_rect 精确控制。

size + position vs set_window_rect —— 先看 Selenium 源码
set_window_size(w,h) + set_window_position(x,y) 是两个独立动作,可能被浏览器事件循环插队 → 窗口先闪到新尺寸,再跳到新位置
set_window_rect(x,y,w,h) 是原子操作,驱动一次性更新 x/y/w/h,位置和尺寸同时生效 ✅
💡 读操作也有 3 套:get_window_size() / get_window_position() / get_window_rect(),Selenium 4 推荐统一用 rect

练习目标:点击按钮观察下方模拟浏览器窗口的实时变化

macOS Demo 文件 编辑 视图 窗口 帮助 9:41
窗口左上角坐标:(0, 0) 屏幕 (1920×1080)
Selenium 练习页
https://www.example.com
1600 × 900
位置 (160, 90)
"""
窗口控制完整示例
- maximize/minimize 直接调 driver 方法
- size/position/rect 需要 Options 或 driver 方法
"""
from selenium import webdriver

driver = webdriver.Chrome()

# ===== 方式 1:快捷方法 =====
driver.maximize_window()               # 一键最大化到屏幕
driver.minimize_window()               # 最小化到任务栏(Selenium 4)
driver.fullscreen_window()             # 全屏(F11 效果,Chrome 支持)

# ===== 方式 2:精确控制 =====
driver.set_window_size(1200, 800)      # 宽 × 高
driver.set_window_position(100, 100)   # 左上角 x, y
# ⚠️ 上面两次调用 → 可能有闪烁(独立动作)

# ===== 方式 3:set_window_rect(推荐 Selenium 4 用法)=====
driver.set_window_rect(x=900, y=300, width=600, height=500)
# 原子操作!位置和尺寸同时生效,无闪烁

# ===== 读取当前窗口信息 =====
size = driver.get_window_size()        # {'width': 1920, 'height': 1080}
pos  = driver.get_window_position()    # {'x': 0, 'y': 0}
rect = driver.get_window_rect()        # {'x': 0, 'y': 0, 'width': 1920, 'height': 1080}
# 💡 Selenium 4 推荐统一用 get_window_rect(),包含所有字段

# ===== 居中窗口实战 =====
screen_w = driver.execute_script("return window.screen.width")
screen_h = driver.execute_script("return window.screen.height")
win_w, win_h = 1200, 800
x = (screen_w - win_w) // 2
y = (screen_h - win_h) // 2
driver.set_window_rect(x, y, win_w, win_h)

driver.quit()

📝 作业要求

  1. 写脚本:打开浏览器 → maximize_window() → 读取当前窗口尺寸 → set_window_size(800,600) → 验证尺寸是否真变了(误差不超过 ±5 px)
  2. 实现"居中窗口":先拿屏幕分辨率(driver.execute_script("return screen.width + ',' + screen.height")),再用 set_window_rect 把窗口挪到屏幕正中央
  3. 同一个目标:把窗口改成 600×500 放在 (100,100)。分别用 size + position 两次调用 和 set_window_rect 一次调用 —— 观察肉眼是否有闪烁差异
  4. 全屏 vs 最大化:fullscreen_window() 和 maximize_window() 看起来都把窗口拉满屏幕,但实际像素不一样!写脚本:先 maximize → 读 rect → fullscreen → 再读 rect,对比两次的 width / height 差值。哪个更大?fullscreen 会把地址栏、标签栏也藏起来(类似 F11),验证一下
  5. 进阶 · 可选 窗口状态保存 & 恢复:写脚本模拟用户操作轨迹 —— ① 启动浏览器默认尺寸 → ② 记录当前 rect(存成一个 dict) → ③ maximize → ④ 改 800×600 放到 (50,50) → ⑤ 再改 1200×700 → ⑥ 用 set_window_rect(**saved_rect) 把第 ② 步存的值恢复回去。最后 assert 一下恢复后的 rect 和第 ② 步几乎完全一致
💡 提示:set_window_rect 是 Selenium 4 主推的原子方法,面试经常问它为什么比 size + position 分开调更好。driver.get_window_rect() 返回的 dict 可直接 **dict 解包当参数传回去什么是 **dict 解包?

Python 里 **dict 会把 dict 的 key-value 展开成关键字参数。

get 返回:{'x':100,'y':50,'width':1280,'height':720}
set 需要:set_window_rect(x=100, y=50, width=1280, height=720)

💡 这两句完全等价:
set_window_rect(**saved)
set_window_rect(x=100, y=50, width=1280, height=720)

❌ 方式 A:手动写(容易漏 key)
saved = driver.get_window_rect()
driver.maximize_window()
driver.set_window_rect(
    x=saved['x'], y=saved['y'],
    width=saved['width'], height=saved['height']
)
✅ 方式 B:** 解包(一行搞定)
saved = driver.get_window_rect()
driver.maximize_window()
driver.set_window_rect(**saved)   # 一行!不会漏 key
💡 特别适合:finally 里恢复窗口——读了个 dict,写的时候直接扔回去。
,非常适合写"记住窗口位置再恢复"的场景

操作 5:页面信息获取

读取 title 标题、current_url 当前地址、page_source 源码;Selenium 4 新方法:print_page_source / get_log。

title / url / page_source —— Selenium 的「页面三件套」
driver.title
HTML <title> 标签里的文字
driver.current_url
浏览器地址栏里完整 URL
driver.page_source
整个 DOM 的 HTML 字符串
⚠️ page_source 返回的是浏览器渲染后的 DOM 快照(含 JS 修改),不是服务器原始 HTML
💡 Selenium 4 新增 driver.print_page_source("page.html"),一行代码保存源码到文件

练习目标:逐项获取当前页面信息,观察每条字段的实时变化

Selenium 练习页
点击下方按钮 → 右侧信息卡片实时更新
driver.title
—— 等待获取 ——
driver.current_url
—— 等待获取 ——
page_source 长度
—— 等待获取 ——
window_handles
—— 等待获取 ——
current_window_handle
—— 等待获取 ———
session_id
—— 等待获取 ——
// 点击「page_source 摘要」按钮,这里会显示 HTML 前 15 行
# 页面信息获取 + Selenium 4 新方法
print("标题:",  driver.title)
print("URL:",   driver.current_url)
print("源码长度:", len(driver.page_source))
print("所有标签:", driver.window_handles)
print("当前句柄:", driver.current_window_handle)
print("Session:", driver.session_id)

# Selenium 4 新写法
driver.print_page_source("page.html")   # 一行保存源码到文件
logs = driver.get_log("browser")        # 读取浏览器控制台日志

# ✅ 实战:一次性打包成 JSON 输出
import json
info = {
    "title": driver.title,
    "url": driver.current_url,
    "source_len": len(driver.page_source),
    "handles": driver.window_handles,
    "session": driver.session_id,
}

📝 作业要求

  1. 打开 https://autotest-505.pages.dev/,用 6 个方法分别读取 title / url / source 长度 / window_handles / current_window_handle / session_id,一次性 json.dumps() 漂亮打印成 JSON
  2. 把第 1 题打开的网站 title 里挑一个关键词,写 if "你的关键词" in driver.title: print("✅"),再把判断封装成 check_title(keyword) 函数(复用上面的 driver)
  3. page_source 返回的是整个 HTML 字符串 —— 试用 Python 的 len()、正则、BeautifulSoup 分别统计页面有多少个 <img> 标签,并把三种方法的耗时用 time.perf_counter() 对比
  4. 新开标签后比较 driver.window_handles 和 driver.current_window_handle 的区别 —— 哪个是列表?哪个是当前标签句柄?
  5. 进阶 · 可选 logs = driver.get_log("browser") 读取 3 种级别日志(browser / performance / driver),分别打印看看内容差异
💡 提示:Selenium 4 新增 driver.print_page_source(),比自己 open().write() 更方便;current_window_handle 是切换标签时判断「我在哪」的唯一凭据

操作 6:元素交互 click · send_keys · clear

元素定位后最常用的三件套:点击、输入、清空;配合 get_attribute / is_displayed / is_enabled 查询状态。

元素交互三件套 + 状态查询
.click() 点击 .send_keys() 输入(⚠️ 追加) .clear() 清空 .get_attribute() 读属性 .is_displayed() 是否可见 .is_enabled() 是否可用
⚠️ send_keys 是追加不是替换,输入新内容前记得先 clear();Selenium 4 新增 el.send_keys(Keys.CONTROL, "a") 全选后再输入。

练习目标:操作模拟搜索页,观察元素状态实时变化

https://www.example.com/search
输入关键词后点击「搜索」查看模拟结果...
get_attribute("value")
selenium
is_displayed()
true
is_enabled()
true
tag_name
input
get_attribute("type")
text
get_attribute("placeholder")
输入关键词开始搜索...
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

el = driver.find_element(By.ID, "searchInput")

# === 三件套 ===
el.click()                              # 聚焦并点击
el.send_keys("Python")                  # 输入(追加!)
el.send_keys(Keys.CONTROL, "a")         # 全选
el.send_keys("新内容")                   # 替换(先全选再输入)
el.clear()                              # 清空

# === 状态 / 属性查询 ===
print(el.is_displayed())                # 是否在页面可见
print(el.is_enabled())                  # 是否可交互(非 disabled)
print(el.get_attribute("value"))        # 读 value 属性
print(el.get_attribute("placeholder"))  # 读 placeholder
print(el.tag_name)                      # 标签名 "input"
print(el.text)                          # 可见文本(input 元素通常为空)

# ⚠️ send_keys 是追加!输入前通常先 clear()
# ✅ 最佳实践:el.clear(); el.send_keys("新内容")

# submit() 等价于点击表单里的提交按钮
# el.submit()  # 按 Enter 提交
🛠 下方是「实战演练表单」—— 完成作业就定位这些真实元素 表单内所有元素都写了明确的 id / name 属性,可直接用 By.ID / By.NAME 定位。

实战演练表单 —— 完成作业的元素在这里

📋 Selenium 元素定位速查(全部可用 By.ID 或 By.NAME)
By.ID, "hwForm"
整个 <form>(第 4 题 submit)
By.ID, "hwUsername"
用户名 text 输入框
By.ID, "hwEmail"
邮箱 email 输入框
By.NAME, "gender"
性别 radio
By.NAME, "hobby"
爱好 checkbox(可多选)
By.ID, "hwCity"
城市 select 下拉框
By.ID, "hwBio"
个人简介 textarea
By.ID, "hwSubmit"
submit 按钮(type="submit")

📝 作业要求

  1. 用 Selenium driver.get() 打开本 HTML 页面 → 定位实战表单的 用户名输入框 → send_keys("李白") → get_attribute("value") 读回 → 验证两者一致
  2. 写一个 safe_click(el) 包装函数:先判断 is_displayed() 和 is_enabled(),再 click;异常时打印元素的 tag_name + get_attribute('id') + get_attribute('class') —— 用 提交 按钮测试
  3. 在 用户名输入框 里连续调 send_keys("a") send_keys("b") send_keys("c"),验证它是追加不是替换;再 clear() 清空输入框
  4. submit() vs click() vs Keys.ENTER 区别:定位 表单 里的 用户名输入框,分别调 el.submit() / driver.find_element(By.ID,"hwSubmit").click() / el.send_keys(Keys.ENTER),对比三种触发表单提交的行为差异(页面底部都会显示「表单已提交」)
  5. 进阶 · 可选 ActionChains 键盘组合:先 el.click() 聚焦 用户名输入框 → key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL) 全选 → send_keys("杜甫") 替换内容;对比直接用 el.send_keys(Keys.CONTROL, "a") 的简洁写法,理解 ActionChains 的"发送到 body"和 el.send_keys 的"发送到元素"区别
💡 提示:定位策略优先级 ID > name > class > CSS > XPath,能用 ID 就别用 XPath

操作 8:页面截图

save_screenshot 保存为文件 · get_screenshot_as_file / base64 / png 三种格式;元素级截图 save_screenshot(element)。

为什么截图是自动化测试的必备?
自动化测试执行完毕后(尤其是失败时),一张截图是"留证据"的核心手段——让你直观看到当时页面状态,是调试和测试报告的关键素材。
save_screenshot
最常用,存本地文件,返回 True/False
get_screenshot_as_file
等价写法(遗留 API),同 save_screenshot
get_screenshot_as_base64
返回 base64 字符串,嵌入 Allure/pytest-html 报告
get_screenshot_as_png
返回 PNG bytes 二进制,上传 AI/OCR
element.screenshot
只截某个元素,Selenium 4+ 独有
⚠️ 重要认知:所有 driver 级截图都是视口截图(viewport)——只截取当前视口的浏览器内容可见区域,不包含浏览器 UI(地址栏、标签栏、工具栏),也不会自动滚动去截上下方的内容!全页截图需 headless Chrome --full-page-screenshot 或 JS 滚动拼接。

练习目标:点击上方 API 卡片或下方按钮,体验不同截图方式

👆 driver 截图只截这里
https://demo.autotest.com/index.html
🏠 自动化测试首页
📷 灰顶=地址栏(不截图)· 白底=视口(截图范围)
截图结果
—
—
点击上方按钮生成截图
# 截图 4 种方式
driver.save_screenshot("page.png")          # 方式 1:最常用
driver.get_screenshot_as_file("page.png")   # 方式 2:等价写法
b64 = driver.get_screenshot_as_base64()     # 方式 3:返回 base64 字符串(邮件/API 上传)
png = driver.get_screenshot_as_png()        # 方式 4:返回 PNG bytes

# 元素级截图(Selenium 4)
el = driver.find_element(By.ID, "demoInput")
el.save_screenshot("element.png")

# ✅ 最佳实践:截图 + 时间戳命名
import os, time
name = f"fail_{int(time.time())}.png"
driver.save_screenshot(os.path.join("screenshots", name))

📝 作业要求

  1. 打开页面 → 用 3 种不同方法截图:save_screenshot() / get_screenshot_as_file() / get_screenshot_as_png() → 对比生成文件的大小
  2. 写一个"异常自动截图"装饰器:函数里 try/except,except 里自动截图 + 把文件名写到日志里,装饰任意一个测试函数试试
  3. 元素级截图:打开本页面 → 定位模拟浏览器里的 .mb-logo("AT"圆形 logo)或 #shotMockBtn("登录 / 注册"按钮),直接 el.save_screenshot("logo.png"),验证它只截元素本身不是整页
  4. driver.get_screenshot_as_base64() 实战:拿到 base64 字符串 → import base64; open("shot.png","wb").write(base64.b64decode(b64_str)) 解码保存 → 对比和 save_screenshot() 生成的文件是否一样大;再用它拼一个 HTML:f'<img src="data:image/png;base64,{b64_str}">',直接把截图嵌进测试报告正文
  5. 进阶 · 可选 Selenium 默认只能截视口(屏幕可见区域),要截整页需用 CDP(Chrome DevTools Protocol):driver.execute_cdp_cmd("Page.captureScreenshot", {"captureBeyondViewport": True}),拿到 base64 后自己解码存文件,对比视口和整页截图的差异
💡 提示:base64 截图适合发邮件 / 上传到接口,不用保存文件

操作 9:超时控制与等待机制

三种等待方式对比:强制 sleep(最烂)→ 隐式 implicitly_wait(全局)→ 显式 WebDriverWait+EC(最专业)。

💡 核心差异:sleep 硬等 2 秒 → implicitly_wait 最多等 10 秒、元素一出现就返回 → WebDriverWait+EC 条件满足立即返回,元素 3.5 秒出现就只等 3.5 秒,绝不浪费。
time.sleep(t)
强制硬等 t 秒,不管元素什么时候出现,页面快了浪费、慢了不够,生产代码禁止使用
❌ 最差
效率★☆☆☆☆
implicitly_wait(t)
全局生效,元素一出现立即返回,最长等 t 秒。能接受但不够灵活
⚠️ 可用
效率★★★☆☆
WebDriverWait(driver, t).until(...)
显式条件等待,设定最长等待 t 秒,条件满足立即返回,绝不浪费一秒。最推荐、最专业
✅ 推荐
效率★★★★★
0.00s
0s5s10s
👆 点击上方任意卡片开始模拟
被测页面
⏳ 等待目标按钮 ["登录 / 注册"] 出现中...
# ===== 三种等待方式对比 =====

# ❌ 方式 1:强制等待 —— 写死时间,页面快了浪费、慢了不够
import time
time.sleep(2)

# ⚠️ 方式 2:隐式等待 —— 全局生效,元素出现就返回,最长等 10 秒
driver.implicitly_wait(10)
# 之后所有 find_element 都自动应用
# 缺点:① 全局影响难控制 ② 不擅长等 URL / 标题 / 元素消失

# ✅ 方式 3:显式等待 —— 最灵活、最推荐
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)   # 最多等 10 秒
el = wait.until(EC.presence_of_element_located((By.ID, "demoInput")))
el.click()

# 常用 EC 条件
# EC.presence_of_element_located     元素在 DOM 中
# EC.visibility_of_element_located   元素可见(宽高>0)
# EC.element_to_be_clickable         元素可点击
# EC.url_contains("xxx")             URL 包含
# EC.title_contains("xxx")           标题包含
# EC.alert_is_present()              出现弹窗
# EC.invisibility_of_element_located 元素消失

# ⚠️ 三种超时配置
driver.set_page_load_timeout(30)    # 页面加载超时
driver.set_script_timeout(20)       # JS 执行超时
driver.implicitly_wait(10)          # 隐式等待

🧰 作业素材区(提供实验环境,不给答案)

复制下方代码/HTML 到本地文件,用 Selenium 打开测试;或点 demo-card 的三种等待按钮自动记录数据到作业 1 对比表
📌 作业 1:等待耗时对比表 (点上面 demo-card 的 sleep / implicit / explicit 按钮 → 自动填入你自己的实验数据)
等待方式设定 t 秒实际耗时效率你的备注
👆 请点击上方 demo-card 的 sleep / implicit / explicit 按钮,结果将自动填入
📌 作业 2:手写 wait_until —— 你的实现(Python)
# 👇 在下方写出你的 wait_until 实现,Python 版(和 demo-card 演示的 JS 逻辑一致)
import time

def wait_until(callable, timeout=10, poll=0.5):
    # ===== 你来写 =====
    # 1. 记录 start 时间
    # 2. while 循环,每次 poll 秒
    # 3. callable() 返回真 → 立即 return True
    # 4. 超时 → raise TimeoutException("wait_until timeout")
    # 5. 用一个计数器变量记录总共轮询了多少次,最后 return 或打印
    pass  # ← 把这行换成你的代码

# ===== 测试用例(复制到本地运行)=====
def make_delayed_button(driver, delay=3):
    # 注入 JS,让一个按钮在 delay 秒后才出现在 DOM 中
    js = """
    var btn = document.createElement('button');
    btn.id = 'delayed-btn';
    btn.textContent = '我是延迟出现的按钮';
    btn.style.padding = '10px 20px'; btn.style.fontSize = '16px';
    document.body.appendChild(btn);
    setTimeout(function() { btn.disabled = false; btn.style.background = '#10b981'; }, delay*1000);
    """
    driver.execute_script(js, delay)

# 用法:
# driver.get('about:blank')
# make_delayed_button(driver, 3)
# wait_until(lambda: driver.find_element(By.ID, 'delayed-btn').is_enabled(), timeout=10, poll=0.2)
# print("总共轮询了多少次?")
在 demo-card 里模拟 callable 延迟 秒
📌 作业 3:EC 条件选型 —— 3 个测试页面 HTML(复制到 .html 文件本地打开)
📄 场景 ①:页面加载到一半元素还没出现 —— half_loaded.html
<!DOCTYPE html>
<html><head><title>half_loaded</title></head><body>
  <p>页面标题、导航栏、图片等都已经出现了...</p>
  <p>但登录按钮还没渲染出来,需要等待前端 JS 加载完。</p>
  <script>
    setTimeout(function(){
      var b = document.createElement('button');
      b.id = 'login-btn';
      b.textContent = '登录 / 注册';
      document.body.appendChild(b);
    }, 4000);  // 按钮 4 秒后才插入 DOM
  </script>
</body></html>
📄 场景 ②:元素加载了但被遮罩层盖住 —— covered.html
<!DOCTYPE html>
<html><head><title>covered</title>
  <style>
    #login-btn { padding:10px 20px; font-size:16px; margin-top:40px; }
    #mask { position:fixed; top:0; left:0; width:100%; height:100%;
            background:rgba(0,0,0,0.7); display:flex; align-items:center;
            justify-content:center; color:white; font-size:24px; z-index:99; }
    .btn-close { margin-top:20px; padding:8px 16px; font-size:14px; }
  </style>
</head><body>
  <button id="login-btn">登录 / 注册</button>
  <div id="mask">请先登录!
    <br>
    <button class="btn-close" onclick="document.getElementById('mask').remove()">关闭遮罩</button>
  </div>
</body></html>
📄 场景 ③:元素存在但宽高都是 0(hidden) —— hidden.html
<!DOCTYPE html>
<html><head><title>hidden</title></head><body>
  <button id="secret-btn" style="visibility:hidden; width:0; height:0;">隐藏的按钮</button>
  <p>按钮 DOM 存在,visibility:hidden,宽高都是 0。3 秒后变可见:</p>
  <script>
    setTimeout(function(){
      var b = document.getElementById('secret-btn');
      b.style.visibility = 'visible';
      b.style.width = 'auto'; b.style.height = 'auto';
      b.style.padding = '10px 20px'; b.style.fontSize = '16px';
      b.style.background = '#10b981';
    }, 3000);
  </script>
</body></html>
💡 你的任务:用 Selenium + EC 打开这三个页面,分别写出你认为最合适的等待条件(presence / visibility / clickable / 其他),并解释为什么。
📌 作业 4:超时三件套 —— 慢响应 Flask 后端 + 测试代码模板
🔧 后端:slow_server.py(pip install flask 后运行)
from flask import Flask, Response
import time

app = Flask(__name__)

@app.route('/')
def slow_home():
    time.sleep(5)  # 首页故意慢 5 秒 → 触发 page_load_timeout
    return '<html><body><h1>Slow Page</h1></body></html>'

@app.route('/fast')
def fast():
    # fast 页面加载完了但 JS 死循环 → 触发 script_timeout
    return '<html><body><h1>Fast Page</h1>' \
           '<script>while(true){}</script></body></html>'

@app.route('/never-appear')
def never_appear():
    # implicitly_wait 找不到元素 → 抛什么异常?
    return '<html><body><p>页面里永远不会出现 #target-btn</p></body></html>'

if __name__ == '__main__':
    app.run(port=5000, debug=False)
🚀 启动后端(2 步):
① 安装:pip install flask(只需装一次)
② 启动:打开一个终端窗口,cd 到 slow_server.py 所在目录,运行 python slow_server.py,看到 Running on http://127.0.0.1:5000 即启动成功。
💡 保持这个终端开着! 另开一个终端跑你的 Selenium 测试脚本。Ctrl+C 才会停掉后端。
🔧 你的测试脚本骨架(填完 try/except 观察异常类型)
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, ScriptTimeoutException, NoSuchElementException

driver = webdriver.Chrome()
BASE = 'http://127.0.0.1:5000'

# ===== 实验 1:page_load_timeout =====
driver.set_page_load_timeout(3)  # 故意设小,首页要 5 秒才加载完
try:
    driver.get(BASE + '/')
except Exception as e:
    print("实验 1 异常类型:", type(e).__name__)  # ← 你观察到什么?

# ===== 实验 2:script_timeout =====
driver.set_script_timeout(2)
driver.get(BASE + '/fast')
try:
    driver.execute_script("while(true){}")
except Exception as e:
    print("实验 2 异常类型:", type(e).__name__)  # ← 你观察到什么?

# ===== 实验 3:implicitly_wait =====
driver.implicitly_wait(5)
driver.get(BASE + '/never-appear')
try:
    driver.find_element('id', 'target-btn')  # 永远找不到
except Exception as e:
    print("实验 3 异常类型:", type(e).__name__)  # ← 和前两个一样吗?

driver.quit()
📌 作业 5:隐式 + 显式混用陷阱(进阶 · 可选) —— 实验脚本
先跑一遍记录 elapsed_alone(只用显式),再跑一遍记录 elapsed_mixed(显式 + 隐式),对比两个时间的差。
# 实验:显式等待 3 秒 + 隐式等待 10 秒,元素永远不存在
# 显式等待实际会等多久?

import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

driver = webdriver.Chrome()
driver.get('about:blank')

# ===== 第一轮:只用显式等待 =====
start = time.perf_counter()
try:
    WebDriverWait(driver, 3).until(
        EC.presence_of_element_located(('id', 'never-exist'))
    )
except TimeoutException:
    pass
elapsed_alone = time.perf_counter() - start
print(f"单用显式等待耗时:{elapsed_alone:.2f}s")  # ← 你的答案:______ 秒

# ===== 第二轮:先设隐式等待,再用显式等待 =====
driver.implicitly_wait(10)  # ← 加了这一行
start = time.perf_counter()
try:
    WebDriverWait(driver, 3).until(
        EC.presence_of_element_located(('id', 'never-exist'))
    )
except TimeoutException:
    pass
elapsed_mixed = time.perf_counter() - start
print(f"显式 + 隐式混用耗时:{elapsed_mixed:.2f}s")  # ← 你的答案:______ 秒

print(f"差值:{elapsed_mixed - elapsed_alone:.2f}s")
# 混用后显式等待的实际时间变 ______ 了?

driver.quit()

📝 作业要求

  1. 写三段代码分别用 time.sleep(t) / implicitly_wait(t) / WebDriverWait(driver,t).until(...) 去等一个按钮出现,用 time.perf_counter() 精确统计各自花了多少秒,对比三者差异
  2. 实现一个自己的 wait_until(callable, timeout=10, poll=0.5) 函数:每 poll 秒轮询 callable() 返回值,为真立即返回;超时抛 TimeoutException;打印总等待了多少次
  3. 场景题:页面加载了一半时元素还没出现,应该用哪种等待?元素加载出来了但被其他 DOM 盖住了(click 报 ElementClickIntercepted),EC 用哪个条件最合适?元素加载了但宽高都是 0(hidden),presence 和 visibility 用哪个?
  4. 超时三件套实验:打开一个故意慢响应的页面,依次调 driver.set_page_load_timeout(3) / driver.set_script_timeout(2) / driver.implicitly_wait(5),分别观察超时抛的是什么异常(TimeoutException / ScriptTimeoutException)
  5. 进阶 · 可选 隐式 + 显式混用陷阱:先 implicitly_wait(10),再写一个 WebDriverWait(driver, 3).until(...) —— 如果元素不存在,显式等待实际会等 3 秒还是 10 秒?验证你的猜想
💡 提示:生产代码里 禁止 time.sleep(),全用 WebDriverWait + EC;隐式等待和显式等待不要混用,容易产生"显式等待不生效"的错觉

操作日志

下面会实时记录你在 9 个章节里的每一次操作。

⏳ 暂无任何操作,快去上面试试吧~

关于我们

Python 自动化测试学习平台是一个面向软件测试工程师、开发人员及在校学生的交互式在线学习平台,专注于 Selenium WebDriver 与 Python unittest 自动化测试技能的教学与实践。

我们以"理论 + 实战 + 可交互演示"为核心特色,将八种常规元素定位和Javascript定位、元素操作、自动化测试工具、单元测试、测试模型等知识体系拆分为模块化课程,每个模块都配有可点击、可输入、可运行的模拟练习页面。

平台致力于降低自动化测试的入门门槛,让学习者从"看懂代码"到"动手实操",逐步掌握企业级自动化测试框架的设计与落地能力。

联系方式

📧邮箱:lq@selenium-auto-test.com

📞电话:0755-12345678

📍地址:深圳

💬微信公众号:Selenium自动化测试

🐙GitHub:github.com/selenium-auto-test

免责声明

1. 本平台所有示例代码、演示页面及教程内容均仅供学习交流使用,不构成任何商业推广或技术服务承诺。

2. 平台中的 Selenium API 描述、Python 语法说明均以官方文档为准,如有版本差异请以对应官方文档为准。

3. 学习者在实际项目中应用平台所学内容时,应结合自身项目环境进行充分测试,本平台不对因直接使用示例代码而产生的任何直接或间接损失承担责任。

4. 本平台所引用的第三方库(Selenium、ChromeDriver、unittest 等)版权归其各自作者及维护者所有。

5. 如涉及版权问题或内容建议,请及时通过联系方式与我们沟通,我们将第一时间处理。