在介绍之前先来说一下HTML、Selenium、WebDriver的关系

HTML(超文本标记语言):它是一个网页的骨架,通过各种标签和属性(id、class)定义了页面的元素

DOM(模型):当浏览器解析HTML后会在内存中对HTML结构生成一个DOM树。如图所示:

Selenium:是一个自动化工具/API,通过它来抓取到DOM模型上的元素

WebDriver(驱动程序):是一个API或协议,selenium独有的,selenium需要用WebDriver控制浏览器做进行抓取,每一个浏览器都由一个特定的WebDriver实现支持(例如chrome需要chrome driver),它负责浏览器和Selenium之间的通信

Locator(定位器):Selenium实现抓取元素的具体工具,利用自身包含的8种定位器抓取元素

1、元素定位八大方法:

基础定位:

id:通过元素id属性定位

  driver.find_element(By.ID,"login_btn")

name:通过name属性定位,用于表单中的输入框

  driver.find_element(By.NAME,"username")

class_name: 通过class属性定位,如果有多个class用空格隔开,只能选其中一个

  driver.find_element(By.CLASS_NAME,"s_ipt")

链接定位(针对<a>标签)

link_text: 通过链接的完整文本定位

  driver.find_element(By.LINK_TEXT," 新闻 ")

partial_link_text:通过链接的部分文本定位

  driver.find_element(By.PARTIAL_LINKLTEXT,"新")

标签与结构定位(万能)

tag_name:通过标签名定位(如div,input)

  driver.find_element(By.TAG_NAME,"input")

xpath :万能定位器,而已根据层级、属性、文本等文件组合定位

  driver.find_element(By.XPATH,"//*[@id,'ui'] // a[text()="新闻"]")

css_selector:性能最高的复杂定位方法,速度比xpath快

  driver.find_element(By.CSS_SELECTOR,"#u1.s_ipt")

这里继续补充知识点:

1、Webdriver是驱动程序的统称,每个浏览器都有自己名字的Driver,为什么不能通用?

因为每个浏览器的内核不一样,它们解析出的DOM树逻辑是一样的,但每个浏览器内部调用底层节点的API不一样,为了抹平差异利用WebDriver写python代码实现在不同浏览器上运行

2、WebDriver没每次“抓取”信息过程:

发出请求(调用find_element),编码传输(selenium将动作打包成HTTP请求),驱动执行(收到请求后在浏览器生成的DOM树扫描),返回结果(将抓取到的内容打包成HTTP响应传回代码)

3、现在更流行另一种工具——PlayWright

为什么selenium比PlayWright慢?

Selenium基于WebDriver单向通信,每次操作又要发送一次请求,遇到网络延迟累加就会很慢

PlayWright是基于WebSocket双向通信,实时监听浏览器的动作,不需要下载匹配版本的驱动文件,效率更高

2、selenium的页面操作:点击、输入、下拉框、弹窗处理等怎么实现?

基础动作:

输入文本:send_keys

input_box=driver.find_element(By.ID,"kw")
input_box.send_keys("小徐学编程")

点击元素

search_btn=driver.find_element(By.ID,"xu")
search_btn.click()

清空输入框

input_box.clear()

下拉框处理(Select类)

普通HTML有<select>按钮可以直接用click 点击

from selenium.webdriver.support.ui import Select
#定位到select本身
dropdown =Select(driver.find_element(By.ID,"city_select"))

dropdown.select_by_index(1)  #根据索引选
dropdown.select_by_value("sh")  #根据value 属性选
dropdown.select_by_visible_text("北京")  #根据显示的文字选

        

网页弹窗(由Javascript触发的alert, confirm , prompt)不属于DOM树,鼠标无法点击,必须要用到 switch_to 切换到警告框

#切换到当前的弹窗
alert= driver.switch_to.alert

print(alert.text)  #输出弹窗里的文字
alert.accept() #点确定
#alert.dismiss() #点取消
#alert.send_keys("输入内容") #输入内容

鼠标悬停与拖拽

from selenium.webdriver.common.action_chains import ActionChains
menu=driver.find_element(By.ID,"user_menu")
#鼠标悬停在菜单上
ActionChains(driver).move_to_element(menu).perfom()
#从source元素拖拽到target
source=driver.find_element(By.ID,"draggable")
target=driver.find_element)BY.ID,"droppable")
ActionChains(driver).drag_and_drop(source,target).perfom()

重点:多窗口与Iframe切换

问题:某些元素定位是对的但找不动元素

Iframe切换:有些网页藏在<iframe>标签里

#切换进iframe
driver.switch_to.frame("login_frame")
#操作完成后,必须切回到主页面
driver.switch_to.default_content()

多窗口切换:点击链接跳出新的标签页,WebDriver的焦点还在旧页面

#获取所有窗口句柄
handles=driver.windows_handles
#切换到最新打开窗口
driver.switch_to.windows(handles[-1])

3、等待机制:

显示等待:

使用场景:异步类型的

  异步加载的元素:点击按钮后由Javascript生成的弹窗和列表项

  状态切换验证:按钮在点击后需要等进度条消失

  页面跳转:等URL包含某个特定的关键词、页面标题发生变化

  上传/下载:文件上传下载时出现提示文字

注意事项:必须配合定位器使用,传入By和expected_conditions

                  遇到长代码时和代码一起封装进去

                  针对不同的操作设置不同的超时

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriver
from selenium.webdriver.support import expected_conditions as EC
#【设置显示等待】具体针对登录操作的按钮
#显示等待用WebDriver(变量名,时间)来调用
#1、等待发布笔记的按钮在真正可以被点击时再点击
wait=WebDriverWait(driver,15)
pubilish_btn=wait.until(EC.element_to_be_clickable((By.XPATH,"//button[contains(text(),"发布笔记")]")))
publish_btn.click()
#2、等待登录成功的弹窗消失
wait.util(EC.invisibility_of_element_located((By.CLASS_NAME,"loading_mask")))

隐式等待:

在项目初期搭建:项目结构简单时,网页加载速度快、异步请求较少,可以用隐式等待做一个基本的全局保障

保底机制:设置一个较短时间(5″)防止网络抖动导致脚本崩塌

#隐式等待用umplicitly_wait()函数调用
from selenium import webdriver

driver=webdriver.Chrome()
#全局生效,设置一次其他命令都执行
driver.implicitly_wait(5)

driver.get("https://www.xioahongshu.com")
diver.find_element(By.ID,"search").send_keys("穿搭")
driver.find_element("class name","search").click()

注意:

1、显示等待和隐式等待不能混用在同一个项目

2、代码逻辑中先执行until拿到元素对象,再执行等待操作

3、EC(Expected Conditions)常用法:

  EC.presence_of_element_located:只要HTML树里有这个标签就行

  EC.visibility_of_element_located:HTML里有,用户能看到

  EC.element_to_be_clickable:不仅能看到还没有遮挡

4、显示等待抛出超时异常时要考虑代码逻辑中是否捕获做一些(截图、清除缓存)的处理

5、场景模拟:

场景需求 推荐方案 理由
页面跳转后的第一个元素 隐式等待 页面刚刷新,给所有基础元素一个通用的加载缓冲。
点赞后等待“点赞成功”提示消失 显示等待 隐式等待只能等“出现”,不能等“消失”
点击“发送验证码”后,按钮进入 60s 倒计时 显示等待 需要判断按钮的属性变为 disabled 或文本内容改变
网络环境极差,且页面有很多动态加载的小组件 显示等待 针对核心组件设置超长等待,非核心组件不等待,提高脚本效率。

4、POM框架封装

一个完整的POM框架包括四部分:

BasePage 封装

显示等待集成:将WebDriverWait 封装进find方法

截图/日志:在点击方法里植入日志记录,出现异常就自动截图

JavaScript 执行器:封装 js_clickscroll_to_element“解决看得见却点不到”的顽固元素。

链式调用:多个动作一行代码执行

login_page.input_user("admin").click_login().verify_welcome_msg()

数据驱动

将数据内容使用 pytest.mark.parametrize 配合外部文件YAML/JSON/Excel 存放

断言

写在Text Case类中,负责调用 Page 方法拿到结果用 assert 比对

实例:“小红书登录并进入个人中心” 的业务逻辑

BasePage  将selenium不稳定的API封装起来(如各种click)

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(self.driver,10)

  def find(self,locator):
       return self.wait.util(EC.presence_of_element_located(locator))
  
  def click(self,loctor):
       el=self.wait.util(EC.element_of_to_be_cickable(locator))
       el.click()
  
  def send_keys(self,locator,text):
       el=self.find(locator)
       el.clear()
       el.send_keys(text)

LoginPage.py  将页面的元素和逻辑装起来,解决“页面能做什么”的问题

from selenium.webdriver.commmen.by import By

class LoginPage(BasePage):
  user_name=(By.ID,"name")
  login_btn=(By.CLASS_SELECT,".login_btn")
  
  def login_action(self,user_name):
      self.send_keys(self.user_name,"xu")
      self.click(login_btn)
 
      # 【链式调用】:返回下一个页面的对象
      # 注意:在 Python 中为了避免循环导入,通常在方法内部 import
      from pages.index_page import IndexPage
      return IndexPage(self.driver)

test_login.py  判断功能对不对

import pytest
from pages.login_page import LoginPage

class Test_Login:
    @pytest.mark.parametize("user",["1234456","213248763"])
    def test_login(self,driver,user):
        #初始化登录页
        login_p=LoginPage(driver)
        # 【链式调用】:一行代码完成业务流
        # 执行登录动作,直接拿到首页对象,并调用首页的方法获取结果
        index_p=login_p.login_page(user)
        result_text=index_p.get_nickname()
     
        assert "我的小红书" in result_text

注:若ID信息发送改变,只需要修改LoginPage即可,不需要改其他两个class

5、yield用法

先说一下yield是做什么的

简单来说就是函数里的“暂停键”,保留当前运行的状态

在测开中,yield的场景主要是:

1、pytest 里的环境管理

在自动化测试里,我们经常需要“前置准备”(比如打开浏览器)和“后置清理”(比如关闭浏览器)。yield 完美地把这两步缝合在了一个函数里:

import pytest

@pytest.fixture()
def setup_database():
    print("\n1. 连接数据库...")  # 【前置操作】
    db = "MySQL_Connection"

    yield db  # 【暂停交接】把 db 交给测试用例去跑。用例在跑的时候,代码就停在这行等。

    print("\n2. 断开数据库连接...") # 【后置操作】测试用例全跑完了,它苏醒过来,接着往下走打扫战场。

2、生成器:节省内存

在多个测试样例时防止大量数据堆积,可以使用它批量加入数据,防止内存撑爆

def create_test_users(limit):
    for i in range(limit):
        yield f"test_user_{i}"  # 生产一个,交给你,暂停。下次要,再生产下一个。

# 调用它:
users = create_test_users(10000000)
print(next(users)) # 输出: test_user_0
print(next(users)) # 输出: test_user_1

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐