软件 测试

01 第一章测试基础

002 什么是软件测试

1.什么是软件

控制计算机硬件的工具

2.软件产生过程

需求产生->需求文档->设计效果图->产品开发->产品测试->部署上线

3.什么是软件测试?

使用技术手段验证软件是否满足使用需求

减少软件缺陷,保障软件质量!

003 测试主流技能

1.功能测试(测试主要验证程序的功能是否满足需求)

2.自动化测试(使用代码或工具代替手工,对项目进行测试)->归属功能测试

3.接口测试(使用代码或工具验证程序中的接口是否访问正常)

4.性能测试(模拟多人使用软件,查找服务器缺陷)->归属专项测试

004 测试的分类

1.按测试阶段划分

单元测试(针对程序源代码进行测试)

集成测试(又称接口测试,针对模块之间访问地址进行测试)

系统测试(对整个系统进行测试包括功能,兼容,文档等测试)

验收测试(主要分为内测,公测,使用不同人群来发掘项目缺陷)

2.按代码可见度划分

黑盒测试(源代码不可见,UI功能可见->系统测试)

灰盒测试(部分代码可见,功能不可见->集成测试)

白盒测试(全部代码可见,UI功能不可见->单元测试)

005 质量模型

功能性,性能,兼容性,易用性(简洁,友好,流畅,美观),安全(前5个必须测),可靠性,可维护性,可移植性

衡量一个优秀软件的维度

006 测试流程

需求评审(确保各部门需求理解一致),计划编写(测什么,谁来测,怎么测),用例设计(验证项目是否符合需求的操作文档),用例执行(项目模块开发完成开始执行用例文档实施测试),缺陷管理(对缺陷进行管理的过程),测试报告(实施测试结果文档)

007 用例

1.什么是用例

用例:用户使用的案例

2.什么是测试用例(为测试项目而设计的执行文档)

3.测试用例的作用(防止漏测,实施测试的标准

4.用例设计编写格式

用例编号,用例标题,项目/模块,优先级,前置条件,测试步骤,测试数据,预期结果

e4110841290d4dbbb8e9004c1d688373.png

用例编号: 项目_模块__编号

用例标题:预期结果(测试点)

模块/项目:所属项目或模块

优先级: 表示用例的重要程度或者影响力p0~p4(p0最高)

前置条件: 要执行此条用例,有哪些前置操作

测试步骤:描述操作步骤

测试数据: 操作的数据,没有的话可以为空

预期结果: 预期到达的结果

009 qq用例练习

需求:QQ登录(4条)

1.账号为空 2.账号未注册 3.密码为空 4.密码错误

b5624fad84004f6c807e8bd2cf4e745d.png

02 第二章 用例设计方法

2-1 等价类

d2f483206f104c7182bfeeaccb84bf80.png

功能性:插花、装水、养鱼、种菜

性能:防摔、耐压高温、低温

易用性:防滑、便携

属性(硬件):长、宽、高、样式、材质、重量

可移植性:不同的温度下是否正常使用

可维护性:修补

011 等价类

等价划分(对具有某种共同特征的数据集合进行划分)-->有效等价类、无效等价类

步骤:

  1. 明确需求

  2. 划分 有效 、无效

  3. 提取数据设计用例

a67e88055c36488fbfe92b8fa3cd3097.png

e31e122b85424d8d8398db50d096aa19.png

012 案例分析及测试点

051bcb9497e14d4384c63504020e141d.png

正向用例:一次尽可能将多个正确数据组合

反向用例:一次只能覆盖一个

9a23c960b0ff4f09918d536b2c4d1f89.png

675734f15b824002ab74f0bb80246f82.png

等价类适用场景:

针对:需要有大量数据测试输入,但是没法穷举测试的地方。

例如:输入框,下拉列表,单选复选框

典型代表:页面的输入框类测试。

948a57b0e9764a3788d8034c6b7572a0.png

016 边界值方法说明

1.边界范围节点

选定正好等于、刚好大于、刚好小于边界的值作为测试数据

上点:边界上的点(正好等于)

离点:距离上点最近的点(刚好大于、刚好小于)

内点:范围内的点

2.边界值设计用例的步骤

1、明确需求

2.确定有效和无效等价类-->等价类考虑类型,边界值考虑长度

3、确定边界范围

4、提取数据编写测试用例

边界值案例1

89a11fe766bb4db790fe5c4e62921dcf.png

边界值案例2

1cb83aaa91bf4ee1bc965d8236ce3030.png

边界值优化:

上点:必选(不考虑区间开闭)

内点:必选(建议选择中间范围)

离点:开内闭外(开区间选择内部离点,闭区间考虑外部离点)

例: 10<a<=20 即 (10,20]

闭区间:包含

上点:10,20

内点:15

离点:11,21(开内闭外)

2-3 判定表

判定表考虑输入条件之间的各种组合、输入条件和输出结果之间有相互制约关系的测试

判定表定义及其组成部分:

定义:是一种以表格形式多条件逻辑判断的工具

组成:条件桩:列出问题中所有的条件

动作桩:列出问题可能采取的措施

条件项:列出条件对应的取值,所有可能情况下的真假值

动作项: 列出条件项的、各种取值情况下应该采取的动作结果

规则:

判定表中贯穿条件项和动作项的一列就是一条规则

假设有n个条件,每个条件的取值有2个(0,1),全组合有2的n次方种规则

9d6aea227dc447b89b5b064861ce41cf.png

7d150bb025f8480db41c5f437aeb3749.png

2-4 场景法(流程图)

be758ddbf6fd411fa008eb6adc9941a3.jpeg

084d0d32bfdd491b8297267563cfcc00.png

bdab565a55304caeb46a80fff42b7475.png

03 第三章 缺陷管理

033 缺陷定义以及标准

1.缺陷的定义:

软件在使用过程中存在的任何问题都叫软件的缺陷,简称bug。

2.缺陷的判断标准

  • 软件未实现需求(规格)说明书中明确要求的功能-少功能

  • 软件出现了需求(规格)说明书中指明不应该出现的错误-功能错误

  • 软件实现的功能超出需求(规格)说明书指明的范围-多功能

  • 软件未实现需求(规格 )说明书中虽未明确指明但应该实现的要求-隐性功能错误

注册页面案例

08552564837542c791bcaf1933449c44.jpeg

90b84818c3894f228ccb5b032463e632.png

04 第四章 HTML入门

beb0243628ed41c0a4d27cfa4689dc51.png

03 阶段三 功能测试

1.web项目环境说明

环境:

  • 环境定义:项目运行所需要的所有的软件和硬件的组合

  • 如何部署环境:

    • 环境组成:操作系统,数据库,web应用程序,项目代码

    • Linux系统安装推荐yum

测试环境:专门提供给测试人员使用

开发环境:开发人员使用

生产环境:提供给用户

预生产环境:设置和数据一样

2.熟悉商城项目

新项目:需求文档+产品经理

已经存在的项目:文档(需求文档、使用说明书、测试用例)、人(测试员工、产品经理、开发) 项目软件

熟悉的思路:用户+用户如何使用

熟悉标准:核心业务+核心功能模块

核心业务:
  • 下单业务

  • 发货业务

  • 退换货业务

  • 订单查询业务

  • 会员管理业务

  • 商品管理业务

2.1 商城项目的核心业务:

  • 下单业务(登录-搜索-加入购物车-下单-支付)

  • 发货业务(登录-确认订单-填单号)

  • 售后业务(用户登录-发起申请-商家登录--商家确认申请-用户退货-商家确认收货-商家打款)

  • 查询订单(用户登录-我的页面-我的订单)

2.2 商城项目的核心功能模块

核心功能模块:
  • 注册

  • 登录

  • 轮播图

  • 搜索

  • 购物车

  • 抢购

  • 评论

测试流程的应用

1.需求评审:

前提:提前阅读需求文档,记录疑惑点

目的:知道有什么功能,规则是什么,最终各部门理解一致

测试需要知道;功能是什么,需求规则是什么

2.计划编写:

  • 测什么

  • 怎么测

  • 谁来测

  • 重点关注

    • 准入标准;研发提测标准,什么时候可以测试

      • 业务能跑通: p0

    • 准出标准:什么时候结束测试

      1. 数据化:用例(100%)\缺陷(解决率:s0 100%,s1 100% s2,s3 95%)

3.设计用例

  • (建议)先设计业务用例,后设计单功能模块用例

4.用例执行

  • 按优先级执行

    • 前提:写用例的时候标注清楚优先级并且明确优先级的定义

    • p0最高级别

    • 关键-如何定义p0- 一般是影响正常使用的用例

5.缺陷管理

  • 提交时间:用例执行失败的第一时间

  • 注意事项:

    • 唯一性

    • 可复现(明确复现步骤,问题发生时间\日志截图)

    • 注明版本号 (问题发生在哪个版本,修复在哪个版本)

6.测试报告

04 阶段四 测试辅助工具

请求:客户端向服务器发送消息

  • 请求行

    URL(网址)=协议://主机地址(IP地址或域名):端口号/路径?参数名=参数值&参数名=参数值

    http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618

    协议:数据传输的格式。如:http、https、ftp等

    路径:主机资源的具体位置,用/分隔开

    端口:一个应用程序对外访问的唯一标识

面试题:介绍一下http和https的区别

  1. 共同点:都是协议,都可以用来传输数据

  2. 不同点:

    • http不加密,不安全;https加密协议(公网使用)

    • http端口号是80,https端口号是443

  • 请求头

  • 请求体

响应:服务器向客户端返回数据的过程

  • 响应行

  • 响应头

  • 响应体

请求重点:

  • URL

  • 请求方法

  • 请求参数(请求体) key=values格式

unittest框架

框架

  1. framework

  2. 为了解决一类事情的功能集合

Unittest框架

  • 是python 自带的单元测试框架

  • 自带的,可以直接使用

  • 测试人员,用来做自动化测试,作为自动化测试的执行框架,即 管理和执行用例的

使用的原因

  1. 能够组织多个用例去执行

  2. 提供丰富的断言方法

  3. 能够生成测试报告

核心要素(组成)

  1. TestCase 测试用例,这个测试用例是unittest的组成部分,作用是 用来书写真正的用例代码(脚本)

  2. TestSuite 测试套件,作用是用来组装(打包) TestCase(测试用例)的,可以将多个用例脚本文件组装到一起

  3. TestRunner 测试执行(测试运行),作用是执行 TestSuite (测试套件)

  4. TestLoader 测试加载,是对TestSuite(测试套件)功能的补充,作用是 用来 组装(打包) TestCase(测试用例)的

  5. Fixture 测试夹具,是一种代码结构,书写 前置方法(执行用例之前的方法)代码和 后置方法(执行用例之后的方法)代码,用例执行顺序 前置------>用例 -----> 后置

TestCase 测试用例

书写真正的用例代码(脚本)

  1. 导包 unittest

  2. 定义测试类

  3. 书写测试方法

  4. 执行

注意事项

  1. 代码文件名字 要满足标识符规则

  2. 代码文件名 不要使用中文

image-20231016185846851

# 4.执行 unittest.main() 来执行,在主程序使用....或直接点击运行按钮
if __name__ =='__main__':
    unittest.main()

可能出现的错误

  • 文件名包含中文

TestSuite 和 TestRunner

TestSuite (测试套件)

将多条用例脚本集合在一起,就是套件,用来组装用例的

  1. 导包 unittest

  2. 实例化套件对象 unittest.TestSuite()

  3. 添加用例方法

TestRunner

用来执行套件对象

  1. 导包 unittest

  2. 实例化套件对象 unittest.TestSuite()

  3. 添加用例方法

  4. 实例化 执行对象 unittest.TextTestRunner()

  5. 执行对象 执行套件对象 执行对象.run(套件对象)

'''
TestSuite
TestRunner
'''
​
# 1. 导包 unittest
import unittest
# 2. 实例化套件对象 unittest.TestSuite()
from testcase import TestDemo
from testcase2 import TestDemo2
​
suite = unittest.TestSuite()
# 3. 添加用例方法
# 3.1 套件对象.addTest(测试类名('测试方法名'))
suite.addTest(TestDemo('test_method1'))
suite.addTest(TestDemo('test_method2'))
suite.addTest(TestDemo2('test_method1_1'))
suite.addTest(TestDemo2('test_method2_2'))
​
# 实例化 执行对象 unittest.TextTestRunner()
runner = unittest.TextTestRunner()
# 执行对象 执行套件对象   执行对象.run(套件对象)
runner.run(suite)
# 3.2 添加整个测试类
# 套件对象.addTest(unittest.makeSuite(测试类名))
suite.addTest(unittest.makeSuite(TestDemo))

image-20231016204955781

TestLoader 测试加载

作用和 TestSuite 作用一样,组装用例代码,同样也需要使用 TextTestRunner()去执行

  1. 导包 unittest

  2. 实例化加载对象,并加载用例 ----> 得到的是 套件对象

  3. 实例化执行对象并执行

import unittest
# 实例化加载对象,并加载用例     ----> 得到的是  套件对象
# suite = unittest.TestLoader().discover('用例所在的目录','用例代码文件名*.py')
suite = unittest.TestLoader().discover('./','testcase*.py')
​
# 实例化执行对象,并执行
# runner = unittest.TextTestRunner()
# runner.run(suite)
unittest.TextTestRunner().run(suite)
#tools.py
#1,1,2
#1,2,3
def add(a, b):
    return a + b
#testloader_add.py
import unittest
# suite = unittest.TestLoader().discover('./','test_add.py')
# unittest.TextTestRunner().run(suite)
​
from test_add import test_add
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(test_add))
runner = unittest.TextTestRunner()
runner.run(suite)
#test_add.py
import unittest
from tools import add
class test_add(unittest.TestCase):
    def test_1(self):
        # 1,1,2
        print(f'测试数据为{1},{1},{2}')
        if 2==add(1,1):
            print(f'用例{1},{1},{2}通过')
        else:
            print(f'用例{1},{1},{2}不通过')
​
    def test_2(self):
        # 1,2,3
        print(f'测试数据为{1},{2},{3}')
        if 3==add(1,2):
            print(f'用例{1},{2},{3}通过')
        else:
            print(f'用例{1},{2},{3}不通过')
            
            
Ran 2 tests in 0.000s
​
OK
测试数据为1,1,2
用例1,1,2通过
测试数据为1,2,3
用例1,2,3通过            

Fixture

代码结构,在用例执行前后会自动执行的代码结构

tpshop登录

  1. 打开浏览器(1次)

  2. 打开网页,点击登录(每次)

  3. 输入用户名密码验证码1,点击登录(每次,测试方法)

  4. 关闭页面(每次)

  5. 打开网页,点击登录(每次)

  6. 输入用户名密码验证码2,点击登录(每次,测试方法)

  7. 关闭页面(每次)

  8. 打开网页,点击登录(每次)

  9. 输入用户名密码验证码3,点击登录(每次,测试方法)

  10. 关闭页面(每次)

  11. 关闭浏览器(1次)

方法级别Fixture

在每个用例执行前后都会自动调用,方法名是固定的

def setUp(self) :#前置
    #每个用例执行之前都会自动调用
    pass
def tearDown(self):#后置
    # 每个用例执行之后都会自动调用
    pass
类级别Fixture

在类中所有的测试方法执行前后 会自动执行的代码,只执行一次

#类级别的 Fixture需要写作类方法
@classmethod
def setUp(self): #类前置
    pass
@classmethod
def tearDown(self): #类后置
    pass
模块级别Fixture(了解)

模块,就是代码文件

模块级别 ,在这个代码文件,执行前后执行一次

#在类外部定义函数
def setUpModule(): #类前置
    pass
def tearDownMoudle():#类后置
    pass

import unittest
​
class TestLogin(unittest.TestCase):
    def setUp(self) -> None:
        print('\n1.打开网页,点击登录')
    def test_1(self):
        print('2.输入用户名密码验证码1,点击登录')
    def tearDown(self):
        print('3.关闭网页')
    @classmethod
    def setUpClass(cls) -> None:
        print('\n打开浏览器')
    @classmethod
    def tearDownClass(cls) -> None:
        print('关闭浏览器')
​
    def test_2(self):
        print('2.输入用户名密码验证码2,点击登录')
​
    def test_3(self):
        print('2.输入用户名密码验证码3,点击登录')
if __name__=='__main__':
    unittest.main()
    
打开浏览器
PASSED                                     [ 33%]
1.打开网页,点击登录
2.输入用户名密码验证码1,点击登录
3.关闭网页
PASSED                                     [ 66%]
1.打开网页,点击登录
2.输入用户名密码验证码2,点击登录
3.关闭网页

断言

用例脚本中

  • 使用代码自动的判断预期结果实际结果是否相符

  • 参数化(将测试数据定义到 json 文件中去使用)

  • 跳过(某些用例由于某种原因不想执行,设置为跳过)

生成测试报告(suite 和 runner(第三方))

assertEqual(预期结果,实际结果)

判断预期结果和实际结果是否相等,如果相等,用例通过,如果不相等,抛出异常,用例不通过

assertIn(预期结果,实际结果)

判断预期结果是否包含在实际结果中,如果存在,用例通过,如果不在,抛出异常,用例不通过

import unittest
class TestAssert(unittest.TestCase):
    def test_equal_1(self):
        self.assertEqual(10, 10);  # 用例通过
​
    def test_assert_2(self):
        self.assertEqual(10, 11)  # AssertionError
    def test_in_1(self):
        self.assertIn('admin','欢迎admin登录')
    def test_in_2(self):
        self.assertIn('ad','aaaabdaa')# 用例不通过

参数化

通过参数的方式来传递数据,从而实现数据和脚本分离。并且可以实现用例的重复执行。(在书写用例方法的时候,测试数据使用变量代替,在执行的时候进行参数传递)

unittest测试框架,本身不支持参数化,但是可以通过安装unittest扩展插件,parameterized来实现。

pip install parameterized

使用 parameterized
  1. 导包 from parameterized import parameterized

  2. 修改测试方法,将测试方法中的测试数据使用 变量 表示

  3. 组织测试数据,格式[(),(),()] 一个元组就是一组测试数据

  4. 参数化,在测试方法上方使用装饰器@parameterized.expand(测试数据)

  5. 运行(直接 TestCase或者使用suite运行)

import unittest
from tools import add
from parameterized import parameterized
data = [(1,1,2),(1,2,3),(2,3,5),(4,5,9)]
​
class TestToolsAdd(unittest.TestCase):
    @parameterized.expand(data)
    def test_tools_add(self,a,b,expect):
         print(f'a:{a},b:{b},expect:{expect}')
         self.assertEqual(expect,add(a,b))
​
if __name__=='__main__':
    unittest.main()
    
PASSED                 [ 25%]a:1,b:1,expect:2
PASSED                 [ 50%]a:1,b:2,expect:3
PASSED                 [ 75%]a:2,b:3,expect:5
PASSED                 [100%]a:4,b:5,expect:9
​
#read_data.json
[
    [1,1,2],
    [2,3,5],
    [3,4,7],
    [4,5,9]
]
#build_add_data.py
import json
def build_add_data():
    with open('read_data.json') as f:
        data = json.load(f)#[[],[]]--->[(),()]
    return data
#test_tools_add.py
import unittest
from tools import add
from parameterized import parameterized
from build_add_data import build_add_data
class TestToolsAdd(unittest.TestCase):
    @parameterized.expand(build_add_data())
    def test_tools_add(self,a,b,expect):
         print(f'a:{a},b:{b},expect:{expect}')
         self.assertEqual(expect,add(a,b))
​
if __name__=='__main__':
    unittest.main()

生成HTML测试报告

使用第三方报告模板,生成报告,本质是TestRunner

pip install -i https://pypi.douban.com/simple/ HTMLTestReport

使用

  1. 导包 unittest.HTMLTestReport

  2. 组装用例(套件suite,loader)

  3. 使用HTMLTestReport中的runner执行套件

  4. 查看报告

import unittest
from htmltestreport import HTMLTestReport
from test_tools_add import TestToolsAdd
​
#套件
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestToolsAdd))
​
#运行对象
# runner = HTMLTestReport(报告的文件路径.html,报告的标题,其他的描述信息)
runner = HTMLTestReport('test_add_report.html','加法用例测试报告','xxx')
runner.run(suite)

使用绝对路径

import os

import os
# __file__ 特殊变量,表示当前代码文件名
path1 = os.path.abspath(__file__)
print('path1',path1)
path2 = os.path.dirname(path1)
print('path2',path2)
# BASE_DIR =r"C:\\Users\\Admin\\Desktop\\Unittest\\"
#BASE_DIR = os.path.dirname(os.path.abspath(__file__)) #当前主文件名
BASE_DIR = os.path.dirname(__file__)
print('BASE_DIR',BASE_DIR)
​
#path1 C:\Users\Admin\Desktop\Unittest\path.py
#path2 C:\Users\Admin\Desktop\Unittest
#BASE_DIR C:\Users\Admin\Desktop\Unittest
跳过

跳过:对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行(不想执行的测试方法)

  1. 直接将测试函数标记成跳过

    @unittest.skip('跳过的原因')

  2. 根据条件判断测试函数是否跳过

    @unittest.skipIf(判断条件,reason='原因') #判断条件为true,执行跳过

    import unittest
    version = 30
    class TestSkip(unittest.TestCase):
        @unittest.skip('没有原因')
        def test_1(self):
            print('方法一')
        @unittest.skipIf(version>=30,'版本号大于等于30')
        def test_2(self):
            print('方法二')
    ​
        def test_3(self):
            print('方法三')
    ​
            ss.
    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s
    ​
    OK (skipped=2)
    方法三
    ​

Selenium

pip install selenium -i https://pypi.douban.com/simple/

from selenium import webdriver 
from selenium.webdriver.chrome.service import Service
# 创建 WebDriver 对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome(service=Service(r'd:\tools\chromedriver.exe')) 
# 调用WebDriver 对象的get方法 可以让浏览器打开指定网址 
wd.get('https://www.baidu.com') 
# 根据id选择元素,返回的就是该元素对应的WebElement对象
element = wd.find_element(By.ID, 'kw')
​
# 通过该 WebElement对象,就可以对页面元素进行操作了
# 比如输入字符串到 这个 输入框里
element.send_keys('通讯\n')
element.clear() # 清除输入框已有的字符串
wd.find_element(By.ID, 'username').send_keys('byhy')
wd.find_element(By.CLASS_NAME, 'password').send_keys('sdfsdf')
wd.find_element(By.TAG_NAME, 'input').send_keys('sdfsdf')
wd.find_element(By.CSS_SELECTOR,'button[type=submit]').click()
​
​
# WebDriver 实例对象的get方法 可以让浏览器打开指定网址
wd.get('https://cdn2.byhy.net/files/selenium/sample1.html')
​
# 根据 class name 选择元素,返回的是 一个列表
# 里面 都是class 属性值为 animal的元素对应的 WebElement对象
elements = wd.find_elements(By.CLASS_NAME, 'animal')
​
# 取出列表中的每个 WebElement对象,打印出其text属性的值
# text属性就是该 WebElement对象对应的元素在网页中的文本内容
for element in elements:
    print(element.text)

要获取整个元素对应的HTML文本内容,可以使用 element.get_attribute('outerHTML')

如果,只是想获取某个元素 内部 的HTML文本内容,可以使用 element.get_attribute('innerHTML')

对于input输入框的元素,要获取里面的输入文本,用text属性是不行的,这时可以使用 element.get_attribute('value')

element = wd.find_element(By.ID, "input1")
print(element.get_attribute('value')) # 获取输入框中的文本

通过WebElement对象的 text 属性,可以获取元素 展示在界面上的 文本内容。

但是,有时候,元素的文本内容没有展示在界面上,或者没有完全完全展示在界面上。 这时,用WebElement对象的text属性,获取文本内容,就会有问题。

出现这种情况,可以尝试使用 element.get_attribute('innerText') ,或者 element.get_attribute('textContent')

使用 innerText 和 textContent 的区别是,前者只显示元素可见文本内容,后者显示所有内容(包括display属性为none的部分)

通过 CSS Selector 选择单个元素的方法是

find_element(By.CSS_SELECTOR, CSS Selector参数)

选择所有元素的方法是

find_elements(By.CSS_SELECTOR, CSS Selector参数)

根据 tag名 选择元素的 CSS Selector 语法非常简单,直接写上tag名即可,

比如 要选择 所有的tag名为div的元素,就可以是这样

elements = wd.find_elements(By.CSS_SELECTOR, 'div')

等价于

elements = wd.find_elements(By.TAG_NAME, 'div')

HTML中, 元素 内部可以 包含其他元素, 比如 下面的 HTML片段

<div id='container'>
    
    <div id='layer1'>
        <div id='inner11'>
            <span>内层11</span>
        </div>
        <div id='inner12'>
            <span>内层12</span>
        </div>
    </div>
​
    <div id='layer2'>
        <div id='inner21'>
            <span>内层21</span>
        </div>
    </div>
    
</div>

id 为 container 的div元素 包含了 id 为 layer1layer2 的两个div元素。

这种包含是直接包含, 中间没有其他的层次的元素了。 所以 id 为 layer1layer2 的两个div元素 是 id 为 container 的div元素 的 直接子元素

而对于 id 为 container 的div元素来说, id 为 inner11inner12inner22 的元素 和 两个 span类型的元素 都不是 它的直接子元素, 因为中间隔了 几层。

虽然不是直接子元素, 但是 它们还是在 container 的内部, 可以称之为它 的 后代元素

如果 元素2元素1 的 直接子元素, CSS Selector 选择子元素的语法是这样的

元素1 > 元素2

中间用一个大于号 (我们可以理解为箭头号)

注意,最终选择的元素是 元素2, 并且要求这个 元素2元素1 的直接子元素

如果 元素2元素1 的 后代元素, CSS Selector 选择后代元素的语法是这样的

元素1   元素2

<a href="http://www.miitbeian.gov.cn">苏ICP备88885574号</a>
#根据属性选择元素 
element = wd.find_element(By.CSS_SELECTOR, '[href="http://www.miitbeian.gov.cn"]') 
​
#打印出元素对应的htmL
 print(element.get_attribute('outerHTML'))#<a href="http://www.miitbeian.gov.cn">苏ICP备88885574号</a>
 Print(element.get_attribute('innerHTML'))#苏ICP备88885574号   

当然,前面可以加上标签名的限制,比如 div[class='SKnet'] 表示 选择所有 标签名为div,且class属性值为SKnet的元素。

属性值用单引号,双引号都可以。

根据属性选择,还可以不指定属性值,比如 [href] , 表示选择 所有 具有 属性名 为href 的元素,不管它们的值是什么。

CSS 还可以选择 属性值 包含 某个字符串 的元素

比如, 要选择a节点,里面的href属性包含了 miitbeian 字符串,就可以这样写

a[href*="miitbeian"]

还可以 选择 属性值 以某个字符串 开头 的元素

比如, 要选择a节点,里面的href属性以 http 开头 ,就可以这样写

a[href^="http"]

还可以 选择 属性值 以某个字符串 结尾 的元素

比如, 要选择a节点,里面的href属性以 gov.cn 结尾 ,就可以这样写

a[href$="gov.cn"]

如果一个元素具有多个属性

<div class="misc" ctype="gun">沙漠之鹰</div>

CSS 选择器 可以指定 选择的元素要 同时具有多个属性的限制,像这样 div[class=misc][ctype=gun]

CSS selector的另一个强大之处在于: 选择语法 可以 联合使用

比如, 我们要选择 网页 html 中的元素 <span class='copyright'>版权</span>

<div id='bottom'>
    <div class='footer1'>
        <span class='copyright'>版权</span>
        <span class='date'>发布日期:2018-03-03</span>
    </div>
    <div class='footer2'>
        <span>备案号
            <a href="http://www.miitbeian.gov.cn">苏ICP备88885574号</a>
        </span>
    </div>        
</div>         

CSS selector 表达式 可以这样写:

div.footer1 > span.copyright

就是 选择 一个class 属性值为 copyright 的 span 节点, 并且要求其 必须是 class 属性值为 footer1 的 div节点 的子节点

也可以更简单:

.footer1 > .copyright

就是 选择 一个class 属性值为copyright 的节点(不限类型), 并且要求其 必须是 class 属性值为 footer1 的节点的 子节点

当然 这样也是可以的:

.footer1  .copyright

因为子元素同时也是后代元素

如果我们要 同时选择所有class 为 plant class 为 animal 的元素。怎么办?

这种情况,css选择器可以 使用 逗号 ,称之为 组选择 ,像这样

.plant , .animal

我们要选择所有 唐诗里面的作者和诗名, 也就是选择所有 id 为 t1 里面的 span 和 p 元素

我们是不是应该这样写呢?

#t1 > span,p

不行哦,这样写的意思是 选择所有 id 为 t1 里面的 span所有的 p 元素

只能这样写

#t1 > span , #t1 > p

image-20231022104218705

按次序选择子节点

对应的html如下,关键信息如下

    <body>  
       <div id='t1'>
           <h3> 唐诗 </h3>
           <span>李白</span>
           <p>静夜思</p>
           <span>杜甫</span>
           <p>春夜喜雨</p>              
       </div>      
        
       <div id='t2'>
           <h3> 宋词 </h3>
           <span>苏轼</span>
           <p>赤壁怀古</p>
           <p>明月几时有</p>
           <p>江城子·乙卯正月二十日夜记梦</p>
           <p>蝶恋花·春景</p>
           <span>辛弃疾</span>
           <p>京口北固亭怀古</p>
           <p>青玉案·元夕</p>
           <p>西江月·夜行黄沙道中</p>
       </div>             
​
    </body>

父元素的第n个子节点

我们可以指定选择的元素 是父元素的第几个子节点

使用 nth-child

比如,

我们要选择 唐诗 和宋词 的第一个 作者,

也就是说 选择的是 第2个子元素,并且是span类型

所以这样可以这样写 span:nth-child(2) ,输出为李白,苏轼这两个,他们父元素是div

#t1 > :nth-child(2) 就是限制,id为t1,它后代元素的第二个子元素, 输出 李白

如果你不加节点类型限制,直接这样写 :nth-child(2)

就是选择所有位置为第2个的所有元素

也可以反过来, 选择的是父元素的 倒数第几个子节点 ,使用 nth-last-child

比如:

p:nth-last-child(1)#春夜喜雨,西江月·夜行黄沙道中

就是选择第倒数第1个子元素,并且是p元素

我们可以指定选择的元素 是父元素的第几个 某类型的 子节点

使用 nth-of-type

比如,

我们要选择 唐诗 和宋词 的第一个 作者,

可以像上面那样思考:选择的是 第2个子元素,并且是span类型

所以这样可以这样写 span:nth-child(2)

还可以这样思考,选择的是 第1个span类型 的子元素

所以也可以这样写 span:nth-of-type(1)

父元素的倒数第几个某类型的子节点

当然也可以反过来, 选择父元素的 倒数第几个某类型 的子节点

使用 nth-last-of-type

像这样

p:nth-last-of-type(2)

奇数节点和偶数节点

如果要选择的是父元素的 偶数节点,使用 nth-child(even)

比如

p:nth-child(even)

如果要选择的是父元素的 奇数节点,使用 nth-child(odd)

p:nth-child(odd)

如果要选择的是父元素的 某类型偶数节点,使用 nth-of-type(even)

如果要选择的是父元素的 某类型奇数节点,使用 nth-of-type(odd)

兄弟节点选择

相邻兄弟节点选择

上面的例子里面,我们要选择 唐诗 和宋词 的第一个 作者

还有一种思考方法,就是选择 h3 后面紧跟着的兄弟节点 span。

这就是一种 相邻兄弟 关系,可以这样写 h3 + span

表示元素 紧跟关系的 是 加号

后续所有兄弟节点选择

如果要选择是 选择 h3 后面所有的兄弟节点 span,可以这样写 h3 ~ span

iframe

image-20231022113711967

在我们使用selenium打开一个网页是, 我们的操作范围 缺省是当前的 html , 并不包含被嵌入的html文档里面的内容。

如果我们要 操作 被嵌入的 html 文档 中的元素, 就必须 切换操作范围 到 被嵌入的文档中。

怎么切换呢?

使用 WebDriver 对象的 switch_to 属性,像这样

wd.switch_to.frame(frame_reference)

其中, frame_reference 可以是 frame 元素的属性 name 或者 ID 。

比如这里,就可以填写 iframe元素的id值 ‘frame1’ 或者 name属性值 ‘innerFrame’。

像这样

wd.switch_to.frame('frame1')

或者

wd.switch_to.frame('innerFrame')

也可以填写frame 所对应的 WebElement 对象。

我们可以根据frame的元素位置或者属性特性,使用find系列的方法,选择到该元素,得到对应的WebElement对象

比如,这里就可以写

wd.switch_to.frame(wd.find_element(By.TAG_NAME, "iframe"))

然后,就可以进行后续操作frame里面的元素了。

上面的例子的正确代码如下

from selenium import webdriver
from selenium.webdriver.common.by import By
​
wd = webdriver.Chrome()
​
wd.get('https://cdn2.byhy.net/files/selenium/sample2.html')
​
​
# 先根据name属性值 'innerFrame',切换到iframe中
wd.switch_to.frame('innerFrame')
​
# 根据 class name 选择元素,返回的是 一个列表
elements = wd.find_elements(By.CLASS_NAME, '.plant')
​
for element in elements:
    print(element.text)

如果我们已经切换到某个iframe里面进行操作了,那么后续选择和操作界面元素 就都是在这个frame里面进行的。

这时候,如果我们又需要操作 主html(我们把最外部的html称之为主html) 里面的元素了呢?

怎么切换回原来的主html呢?

很简单,写如下代码即可

wd.switch_to.default_content()

例如,在上面 代码 操作完 frame里面的元素后, 需要 点击 主html 里面的按钮,就可以这样写

from selenium import webdriver
from selenium.webdriver.common.by import By
​
wd = webdriver.Chrome()
​
wd.get('https://cdn2.byhy.net/files/selenium/sample2.html')
​
​
# 先根据name属性值 'innerFrame',切换到iframe中
wd.switch_to.frame('innerFrame')
​
# 根据 class name 选择元素,返回的是 一个列表
elements = wd.find_elements(By.CLASS_NAME, 'plant')
​
for element in elements:
    print(element.text)
​
# 切换回 最外部的 HTML 中
wd.switch_to.default_content()
​
# 然后再 选择操作 外部的 HTML 中 的元素
wd.find_element_by_id('outerbutton').click()
​
wd.quit()

切换到新的窗口

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
​
wd =webdriver.Chrome(service=Service('chromedriver.exe'))
wd.implicitly_wait(5)
wd.get('https://cdn2.byhy.net/files/selenium/sample3.html')
# wd.title属性是当前窗口的标题栏 文本
print(wd.title)
# mainWindow变量保存当前窗口的句柄
mainWindow = wd.current_window_handle
element = wd.find_element(By.TAG_NAME,'a').click()
​
for handle in wd.window_handles:
    # 先切换到该窗口
    wd.switch_to.window(handle)
    # 得到该窗口的标题栏字符串,判断是不是我们要操作的那个窗口
    if '必应' in wd.title:
        # 如果是,那么这时候WebDriver对象就是对应的该该窗口,正好,跳出循环
        break
wd.switch_to.window(handle)
wd.find_element(By.CSS_SELECTOR,'#sb_form_c  input').send_keys(111)
wd.find_element(By.CSS_SELECTOR,'#search_icon svg').click()
#通过前面保存的老窗口的句柄,自己切换到老窗口
wd.switch_to.window(mainWindow)
wd.find_element(By.ID,'outerbutton').click()
wd.quit()

radio 框

radio框选择选项,直接用WebElement的click方法,模拟用户点击就可以了。

比如, 我们要在下面的html中:

  • 先打印当前选中的老师名字

  • 再选择 小雷老师

<div id="s_radio">
  <input type="radio" name="teacher" value="小江老师">小江老师<br>
  <input type="radio" name="teacher" value="小雷老师">小雷老师<br>
  <input type="radio" name="teacher" value="小凯老师" checked="checked">小凯老师
</div>

对应的代码如下

# 获取当前选中的元素
element = wd.find_element(By.CSS_SELECTOR, 
  '#s_radio input[name="teacher"]:checked')
print('当前选中的是: ' + element.get_attribute('value'))
​
# 点选 小雷老师
wd.find_element(By.CSS_SELECTOR, 
  '#s_radio input[value="小雷老师"]').click()

其中 #s_radio input[name="teacher"]:checked 里面的 :checked 是CSS伪类选择

表示选择 checked 状态的元素,对 radiocheckbox 类型的input有效

checkbox框

对checkbox进行选择,也是直接用 WebElement 的 click 方法,模拟用户点击选择。

需要注意的是,要选中checkbox的一个选项,必须 先获取当前该复选框的状态 ,如果该选项已经勾选了,就不能再点击。否则反而会取消选择。

只选中小雷老师:

wd = webdriver.Chrome(service=Service('chromedriver.exe'))
wd.implicitly_wait(5)
wd.get('https://cdn2.byhy.net/files/selenium/test2.html')
# 先把 已经选中的选项全部点击一下,取消选中状态
elements = wd.find_elements(By.CSS_SELECTOR, '#s_checkbox input[name="teacher"]:checked')
for element in elements:
    element.click()
​
# 再点击 小雷老师
wd.find_element(By.CSS_SELECTOR, "#s_checkbox input[value='小雷老师']").click()
wd.quit()

select

  • select_by_value

根据选项的 value属性值 ,选择元素。

比如,下面的HTML,

<option value="foo">Bar</option>

就可以根据 foo 这个值选择该选项,

s.select_by_value('foo')
  • select_by_visible_text

根据选项的 可见文本 ,选择元素。

比如,下面的HTML,

<option value="foo">Bar</option>

就可以根据 Bar 这个内容,选择该选项

s.select_by_visible_text('Bar')

Select单选框

对于 select单选框,操作比较简单:

不管原来选的是什么,直接用Select方法选择即可。

例如,选择示例里面的小雷老师,示例代码如下

# 导入Select类
from selenium.webdriver.support.ui import Select
​
# 创建Select对象
select = Select(wd.find_element(By.ID, "ss_single"))
​
# 通过 Select 对象选中小雷老师
select.select_by_visible_text("小雷老师")

Select多选框

对于select多选框,要选中某几个选项,要注意去掉原来已经选中的选项。

例如,我们选择示例多选框中的 小雷老师 和 小凯老师

可以用select类 的deselect_all方法,清除所有 已经选中 的选项。

然后再通过 select_by_visible_text方法 选择 小雷老师 和 小凯老师。

示例代码如下:

# 导入Select类
from selenium.webdriver.support.ui import Select
​
# 创建Select对象
select = Select(wd.find_element(By.ID, "ss_multi"))
​
# 清除所有 已经选中 的选项
select.deselect_all()
​
# 选择小雷老师 和 小凯老师
select.select_by_visible_text("小雷老师")
select.select_by_visible_text("小凯老师")

ActionChains

百度首页的右上角,有个 更多产品 选项,如下图所示

白月黑羽Python3教程

如果我们把鼠标放在上边,就会弹出 下面的 糯米、音乐、图片 等图标。

使用 ActionChains 来 模拟鼠标移动 操作的代码如下:

from selenium import webdriver
from selenium.webdriver.common.by import By
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
​
driver.get('https://www.baidu.com/')
​
from selenium.webdriver.common.action_chains import ActionChains
​
ac = ActionChains(driver)
​
# 鼠标移动到 元素上
ac.move_to_element(
    driver.find_element(By.CSS_SELECTOR, '[name="tj_briicon"]')
).perform()

冻结界面

有些网站上面的元素, 我们鼠标放在上面,会动态弹出一些内容。

比如,百度首页的右上角,有个 更多产品 选项,如下图所示

白月黑羽Python3教程

如果我们把鼠标放在上边,就会弹出 下面的 糯米、音乐、图片 等图标。

如果我们要用 selenium 自动化 点击 糯米图标,就需要 F12 查看这个元素的特征。

但是 当我们的鼠标 从 糯米图标 移开, 这个 栏目就整个消失了, 就没法 查看 其对应的 HTML。

怎么办?

可以如下图所示:

白月黑羽Python3教程

在 开发者工具栏 console 里面执行如下js代码

setTimeout(function(){debugger}, 5000)

这句代码什么意思呢?

表示在 5000毫秒后,执行 debugger 命令

执行该命令会 浏览器会进入debug状态。 debug状态有个特性, 界面被冻住, 不管我们怎么点击界面都不会触发事件。

所以,我们可以在输入上面代码并回车 执行后, 立即 鼠标放在界面 右上角 更多产品处。

这时候,就会弹出 下面的 糯米、音乐、图片 等图标。

然后,我们仔细等待 5秒 到了以后, 界面就会因为执行了 debugger 命令而被冻住。

然后,我们就可以点击 开发者工具栏的 查看箭头, 再去 点击 糯米图标 ,查看其属性了。

弹出对话框

Alert

Alert 弹出框,目的就是显示通知信息,只需用户看完信息后,点击 OK(确定) 就可以了。

那么,自动化的时候,代码怎么模拟用户点击 OK 按钮呢?

selenium提供如下方法进行操作

driver.switch_to.alert.accept()

注意:如果我们不去点击它,页面的其它元素是不能操作的。

如果程序要获取弹出对话框中的信息内容, 可以通过 如下代码

driver.switch_to.alert.text

示例代码如下

from selenium import webdriver
from selenium.webdriver.common.by import By
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('https://cdn2.byhy.net/files/selenium/test4.html')
​
​
# --- alert ---
driver.find_element(By.ID, 'b1').click()
​
# 打印 弹出框 提示信息
print(driver.switch_to.alert.text) 
​
# 点击 OK 按钮
driver.switch_to.alert.accept()

Confirm

Confirm弹出框,主要是让用户确认是否要进行某个操作。

比如:当管理员在网站上选择删除某个账号时,就可能会弹出 Confirm弹出框, 要求确认是否确定要删除。

Confirm弹出框 有两个选择供用户选择,分别是 OK 和 Cancel, 分别代表 确定 和 取消 操作。

那么,自动化的时候,代码怎么模拟用户点击 OK 或者 Cancel 按钮呢?

selenium提供如下方法进行操作

如果我们想点击 OK 按钮, 还是用刚才的 accept方法,如下

driver.switch_to.alert.accept()

如果我们想点击 Cancel 按钮, 可以用 dismiss方法,如下

driver.switch_to.alert.dismiss()

示例代码如下

from selenium import webdriver
from selenium.webdriver.common.by import By
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
​
driver.get('https://cdn2.byhy.net/files/selenium/test4.html')
​
# --- confirm ---
driver.find_element(By.ID, 'b2').click()
​
# 打印 弹出框 提示信息
print(driver.switch_to.alert.text)
​
# 点击 OK 按钮 
driver.switch_to.alert.accept()
​
driver.find_element(By.ID, 'b2').click()
​
# 点击 取消 按钮
driver.switch_to.alert.dismiss()

Prompt

出现 Prompt 弹出框 是需要用户输入一些信息,提交上去。

比如:当管理员在网站上选择给某个账号延期时,就可能会弹出 Prompt 弹出框, 要求输入延期多长时间。

可以调用如下方法

driver.switch_to.alert.send_keys()

示例代码如下

from selenium import webdriver
from selenium.webdriver.common.by import By
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('https://cdn2.byhy.net/files/selenium/test4.html')
​
​
# --- prompt ---
driver.find_element(By.ID, 'b3').click()
​
# 获取 alert 对象
alert = driver.switch_to.alert
​
# 打印 弹出框 提示信息
print(alert.text)
​
# 输入信息,并且点击 OK 按钮 提交
alert.send_keys('web自动化 - selenium')
alert.accept()
​
# 点击 Cancel 按钮 取消
driver.find_element(By.ID, 'b3').click()
alert = driver.switch_to.alert
alert.dismiss()

窗口大小

有时间我们需要获取窗口的属性和相应的信息,并对窗口进行控制

  • 获取窗口大小

driver.get_window_size()
  • 改变窗口大小

driver.set_window_size(x, y)

获取当前窗口标题

浏览网页的时候,我们的窗口标题是不断变化的,可以使用WebDriver的title属性来获取当前窗口的标题栏字符串。

driver.title

获取当前窗口URL地址

driver.current_url

例如,访问网易,并获取当前窗口的标题和URL

from selenium import  webdriver
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
​
# 打开网站
driver.get('https://www.163.com')
​
# 获取网站标题栏文本
print(driver.title) 
​
# 获取网站地址栏文本
print(driver.current_url) 

截屏

有的时候,我们需要把浏览器屏幕内容保存为图片文件。

比如,做自动化测试时,一个测试用例检查点发现错误,我们可以截屏为文件,以便测试结束时进行人工核查。

可以使用 WebDriver 的 get_screenshot_as_file方法来截屏并保存为图片。

from selenium import  webdriver
​
driver = webdriver.Chrome()
driver.implicitly_wait(5)
​
# 打开网站
driver.get('https://www.baidu.com/')
​
# 截屏保存为图片文件
driver.get_screenshot_as_file('1.png')

上传文件

有时候,网站操作需要上传文件。

比如,著名的在线图片压缩网站: https://tinypng.com/

通常,网站页面上传文件的功能,是通过 type 属性 为 file 的 HTML input 元素实现的。

如下所示:

<input type="file" multiple="multiple">

使用selenium自动化上传文件,我们只需要定位到该input元素,然后通过 send_keys 方法传入要上传的文件路径即可。

如下所示:

# 先定位到上传文件的 input 元素
ele = wd.find_element(By.CSS_SELECTOR, 'input[type=file]')
​
# 再调用 WebElement 对象的 send_keys 方法
ele.send_keys(r'h:\g02.png')

如果需要上传多个文件,可以多次调用send_keys,如下

ele = wd.find_element(By.CSS_SELECTOR,  'input[type=file]')
ele.send_keys(r'h:\g01.png')
ele.send_keys(r'h:\g02.png')

但是,有的网页上传,是没有 file 类型 的 input 元素的。

如果是Windows上的自动化,可以采用 Windows 平台专用的方法:

执行

pip install pypiwin32

确保 pywin32 已经安装,然后参考如下示例代码

# 找到点击上传的元素,点击
driver.find_element(By.CSS_SELECTOR, '.dropzone').click()
​
sleep(2) # 等待上传选择文件对话框打开
​
# 直接发送键盘消息给 当前应用程序,
# 前提是浏览器必须是当前应用
import win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
​
# 输入文件路径,最后的'\n',表示回车确定,也可能是 '\r' 或者 '\r\n'
shell.Sendkeys(r"h:\a2.png" + '\n')
sleep(1)

pip 源

#清华源 https://pypi.tuna.tsinghua.edu.cn/simple
#pip 修改默认源为豆瓣 
pip config set global.index-url https://pypi.douban.com/simple/

自动化Edge浏览器

自动化基于Chromium内核的 微软最新Edge浏览器,首先需要查看Edge的版本。

点击菜单 帮助和反馈 > 关于Microsoft Edge ,在弹出界面中,查看到版本,比如

版本 95.0.1020.30 (官方内部版本) (64 位)

在自动化代码中,指定使用Edge Webdriver类,并且指定 Edge 驱动路径,如下所示

from selenium import webdriver
​
driver = webdriver.Edge(r'd:\tools\webdrivers\msedgedriver.exe')
​
driver.get('http://www.51job.com')

Xpath 选择器

XPath (XML Path Language) 是由国际标准化组织W3C指定的,用来在 XML 和 HTML 文档中选择节点的语言。

xpath 语法中,整个HTML文档根节点用’/‘表示,如果我们想选择的是根节点下面的html节点,则可以在搜索框输入

/html

如果输入下面的表达式

/html/body/div

这个表达式表示选择html下面的body下面的div元素。

注意 / 有点像 CSS中的 > , 表示直接子节点关系。

绝对路径选择

从根节点开始的,到某个节点,每层都依次写下来,每层之间用 / 分隔的表达式,就是某元素的 绝对路径

上面的xpath表达式 /html/body/div ,就是一个绝对路径的xpath表达式, 等价于 css表达式 html>body>div

自动化程序要使用Xpath来选择web元素,应该调用 WebDriver对象的方法 find_element_by_xpath 或者 find_elements_by_xpath,像这样:

elements = driver.find_elements(By.XPATH, "/html/body/div")

相对路径选择

有的时候,我们需要选择网页中某个元素, 不管它在什么位置

比如,选择示例页面的所有标签名为 div 的元素,如果使用css表达式,直接写一个 div 就行了。

那xpath怎么实现同样的功能呢? xpath需要前面加 // , 表示从当前节点往下寻找所有的后代元素,不管它在什么位置。

所以xpath表达式,应该这样写: //div

‘//’ 符号也可以继续加在后面,比如,要选择 所有的 div 元素里面的 所有的 p 元素 ,不管div 在什么位置,也不管p元素在div下面的什么位置,则可以这样写 //div//p

对应的自动化程序如下

elements = driver.find_elements(By.XPATH, "//div//p")

如果使用CSS选择器,对应代码如下

elements = driver.find_elements(By.CSS_SELECTOR,"div p")

如果,要选择 所有的 div 元素里面的 直接子节点 p , xpath,就应该这样写了 //div/p

如果使用CSS选择器,则为 div > p

通配符

如果要选择所有div节点的所有直接子节点,可以使用表达式 //div/*

* 是一个通配符,对应任意节点名的元素,等价于CSS选择器 div > *

代码如下:

elements = driver.find_elements(By.XPATH, "//div/*")
for element in elements:
    print(element.get_attribute('outerHTML'))

根据属性选择

Xpath 可以根据属性来选择元素。

根据属性来选择元素 是通过 这种格式来的 [@属性名='属性值']

注意:

  • 属性名注意前面有个@

  • 属性值一定要用引号, 可以是单引号,也可以是双引号

根据id属性选择

选择 id 为 west 的元素,可以这样 //*[@id='west']

根据class属性选择

选择所有 select 元素中 class为 single_choice 的元素,可以这样 //select[@class='single_choice']

如果一个元素class 有多个,比如

<p id="beijing" class='capital huge-city'>
    北京    
</p>

如果要选 它, 对应的 xpath 就应该是 //p[@class="capital huge-city"]

不能只写一个属性,像这样 //p[@class="capital"] 则不行

根据其他属性

同样的道理,我们也可以利用其它的属性选择

比如选择 具有multiple属性的所有页面元素 ,可以这样 //*[@multiple]

属性值包含字符串

要选择 style属性值 包含 color 字符串的 页面元素 ,可以这样 //*[contains(@style,'color')]

要选择 style属性值 以 color 字符串 开头 的 页面元素 ,可以这样 //*[starts-with(@style,'color')]

要选择 style属性值 以 某个 字符串 结尾 的 页面元素 ,大家可以推测是 //*[ends-with(@style,'color')], 但是,很遗憾,这是xpath 2.0 的语法 ,目前浏览器都不支持

按次序选择

前面学过css表达式可以根据元素在父节点中的次序选择, 非常实用。

xpath也可以根据次序选择元素。 语法比css更简洁,直接在方括号中使用数字表示次序

比如

某类型 第几个 子元素

比如

要选择 p类型第2个的子元素,就是

//p[2]

注意,选择的是 p类型第2个的子元素 , 不是 第2个子元素,并且是p类型

注意体会区别

再比如,要选取父元素为div 中的 p类型 第2个 子元素

//div/p[2]

第几个子元素

也可以选择第2个子元素,不管是什么类型,采用通配符

比如 选择父元素为div的第2个子元素,不管是什么类型

//div/*[2]

某类型 倒数第几个 子元素

当然也可以选取倒数第几个子元素

比如:

  • 选取p类型倒数第1个子元素

//p[last()]
  • 选取p类型倒数第2个子元素

//p[last()-1]
  • 选择父元素为div中p类型倒数第3个子元素

//div/p[last()-2]

范围选择

xpath还可以选择子元素的次序范围。

比如,

  • 选取option类型第1到2个子元素

//option[position()<=2]

或者

//option[position()<3]
  • 选择class属性为multi_choice的前3个子元素

//*[@class='multi_choice']/*[position()<=3]
  • 选择class属性为multi_choice的后3个子元素

//*[@class='multi_choice']/*[position()>=last()-2]

为什么不是 last()-3 呢? 因为

last() 本身代表最后一个元素

last()-1 本身代表倒数第2个元素

last()-2 本身代表倒数第3个元素

组选择、父节点、兄弟节点

组选择

css有组选择,可以同时使用多个表达式,多个表达式选择的结果都是要选择的元素

css 组选择,表达式之间用 逗号 隔开

xpath也有组选择, 是用 竖线 隔开多个表达式

比如,要选所有的option元素 和所有的 h4 元素,可以使用

//option | //h4

等同于CSS选择器

option , h4

再比如,要选所有的 class 为 single_choice 和 class 为 multi_choice 的元素,可以使用

//*[@class='single_choice'] | //*[@class='multi_choice']

等同于CSS选择器

.single_choice , .multi_choice

选择父节点

xpath可以选择父节点, 这是css做不到的。

某个元素的父节点用 /.. 表示

比如,要选择 id 为 china 的节点的父节点,可以这样写 //*[@id='china']/..

当某个元素没有特征可以直接选择,但是它有子节点有特征, 就可以采用这种方法,先选择子节点,再指定父节点。

还可以继续找上层父节点,比如 //*[@id='china']/../../..

兄弟节点选择

前面学过 css选择器,要选择某个节点的后续兄弟节点,用 波浪线

xpath也可以选择 后续 兄弟节点,用这样的语法 following-sibling::

比如,要选择 class 为 single_choice 的元素的所有后续兄弟节点 //*[@class='single_choice']/following-sibling::*

等同于CSS选择器 .single_choice ~ *

如果,要选择后续节点中的div节点, 就应该这样写 //*[@class='single_choice']/following-sibling::div

xpath还可以选择 前面的 兄弟节点,用这样的语法 preceding-sibling::

比如,要选择 class 为 single_choice 的元素的 所有 前面的兄弟节点,这样写

//*[@class='single_choice']/preceding-sibling::*

要选择 class 为 single_choice 的元素的

前面最靠近的兄弟节点 , 这样写

//*[@class='single_choice']/preceding-sibling::*[1]

前面第2靠近的兄弟节点 , 这样写

//*[@class='single_choice']/preceding-sibling::*[2]

而 CSS选择器 目前还没有方法选择 前面的 兄弟节点

# 先寻找id是china的元素
china = wd.find_element(By.ID, 'china')
​
# 再选择该元素内部的p元素
elements = china.find_elements(By.XPATH, '//p')
​
# 打印结果
for element in elements:
    print('----------------')
    print(element.get_attribute('outerHTML'))

运行发现,打印的 不仅仅是 china内部的p元素, 而是所有的p元素。

要在某个元素内部使用xpath选择元素, 需要 在xpath表达式最前面加个点

像这样

elements = china.find_elements(By.XPATH, './/p')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值